A lot of people, like me, just use Dubbo at work. In many cases, a new RPC interface must be opened, or a new RPC interface must be introduced by copying the previous configuration (all parameters are copied, but the interface name is changed, and then the ID is changed). In fact, if we calm down and think about it, we are slowly turning towards the people we despise most when we just graduated (the ignorant code farmers).
I was recently eating Dubbo with another friend from work. He took notes after he finished eating, while I probably read less source code, ate more tired, and probably wasn’t that deep. After all, this is the first RPC framework I’ve eaten thoroughly, and I’d like to leave a few words about the whole process. Maybe it can bring some ideas to the chicken like me, haha. Come on!
The most basic implementation of RPC
We all know that Dubbo is an RPC framework, so what is the most basic implementation of RPC framework? Here we can first think about, how to build the most simple RPC framework?
Those of us who have used dubbo should know that if A wants to call B’s methods, it must depend on B’s package (at least the API package), otherwise how would I know which method of B to call? In fact, you can not depend on, this will be covered later). So the simplest RPC framework would be for A to tell B which of your methods I’m going to execute, and then you tell me the result when you’re done. So what is the basic information that this method executes?
Basic information about method execution
If you think about it from the logic of calling a method forward, it can be hard to say exactly what is required to execute a method. So we can think about this in terms of reflection calls.
@Test
public void test(a) {
try{ Class<? > greetingService = Class.forName("org.apache.dubbo.common.extension.ExtensionTest$GreetingService"); Class<? >[] parameterTypes =new Class[]{String.class, String.class};
Method sayHello = greetingService.getMethod("sayHello", parameterTypes);
Object[] parameterObjects = new Object[]{"tom"."18"};
Object result = sayHello.invoke(greetingService.newInstance(), parameterObjects);
System.out.println(result);
} catch(ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); }}static class GreetingService {
public String sayHello(String name) {
return "hello " + name;
}
public String sayHello(String name, String age) {
return "hello " + name + ":"+ age; }}}Copy the code
By class name, method name, and parameter type array (to distinguish method overloading), we can determine a unique method. When we need to call this method, we need to pass in the specific parameters.
The code I wrote above shows how to use reflection from one class (consumer) to call a method from another class (provider). Although using unit test code, I believe you can also understand. I don’t understand. I think… This article is not for you. hahahaha
Let’s go back to RPC. At this point, the simplest RPC framework would be an extension of the code above. And the above is a stand-alone version, we just make it distributed, isn’t it? That’s right! This leads naturally to the next step, which is to separate the consumer from the provider and communicate with each other over the Internet.
The socket to join
To be honest, I don’t know if I should post large chunks of code here, Posting too much code does affect the look and feel… Since socket is not the focus of this article, I decided to post only some key code. At the end of this article, I will post my Git address. If you are interested, you can clone the code directly.
consumers
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket(host, port);
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream = newObjectOutputStream(outputStream); Class<? >[] paramTypes =new Class[args.length];
for (int i = 0; i < args.length; i++) {
paramTypes[i] = args[i].getClass();
}
Method.getdeclaringclass ().getName()
ServiceMetadata serviceMetadata = new ServiceMetadata(method.getDeclaringClass().getName(), method.getName(), paramTypes, args);
objectOutputStream.writeObject(serviceMetadata);
return getResultFromRemote(socket);
}
/** * Get the result of the method call from the remote service **@paramSocket Communication Socket *@returnCall result *@throws IOException
* @throws ClassNotFoundException
*/
private Object getResultFromRemote(Socket socket) throws IOException, ClassNotFoundException {
InputStream inputStream = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return objectInputStream.readObject();
}
Copy the code
Service provider
/** * start listening */
public void doListen(a) throws IOException, ClassNotFoundException {
Integer port = applicationRpcPort == null ? Integer.valueOf(20880) : applicationRpcPort;
ServerSocket serverSocket = new ServerSocket(port);
keepListening = new AtomicBoolean(true);
while (keepListening.get()) {
Socket accept = serverSocket.accept();
System.out.println("Message received");
InputStream inputStream = accept.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Object readObject = objectInputStream.readObject();
if (readObject instanceof ServiceMetadata) {
// When a consumer request is heard, it is placed in the thread pool to continue operation
threadPoolExecutor.submit(() -> {
Object result = RpcServiceInvoker.doInvoke((ServiceMetadata) readObject);
try {
OutputStream outputStream = accept.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(result);
} catch(IOException e) { e.printStackTrace(); }}); } } threadPoolExecutor.shutdown(); }Copy the code
At this point, my advice is to get started! It’s really important. If you don’t do it, it feels different. You should be able to write a simple RPC framework.
Start of framework
The framework we wrote in the previous section can only be called through main() or unit tests. Our next step is to embed our framework into the Web application so that the service starts and our project starts (just like Dubbo).
The introduction of the spring
Considering that most of our back-end services will integrate with Spring, it will also make it easier for us to validate our results directly through Web requests. So we can make the consumer a controller by introducing the Spring-boot-starter-Web, and then hide the provider in another Tomcat project.
The next thing we need to do is to import the interface from the provider through the proxy, through the remote invocation rather than through the interface directly (direct invocation will give an error, because the consumer can only get the interface, not the implementation).
The steps for the consumer side are as follows:
- Use custom annotations to find out which requests need RPC calls. The advantage of annotations is that you can get all the annotated information (the basic service call information we mentioned above) through reflection.
- Proxy the imported interface (Java’s native proxy, InvocationHandler) and then set the proxy object to the original object;
- Write the above socket call logic in your own proxy class.
The steps on the provider side are as follows:
- All we have to do is turn our monitors on when the project starts.
What? You don’t know how to call your own program when the project starts? There are thousands of spring extensions, and one of them is right for you
conclusion
At this point, our RPC framework prototype has come out. And this is a real framework to work with. There is nothing about Dubbo in this article, but if you can read it and write it down, it will be inspiring. (If you’re a god, forget it!)
Finally, attach the address of my project gitee.com/hanochMa/rp… . Remember to cut the branch to V1.0.
The difference between the code in V1.0 and the code in this article is that the v1.0 code does a lot of object wrapping, so there are a little more classes than expected.