Easy to understand RPC explanation

Local procedure call

RPC calls remote functions as if they were local. Before looking at RPC, let’s look at how local calls are called. Suppose we want to call Multiply to compute the result of lvalue * rvalue:

  int Multiply(int l, int r) {
      int y = l * r;
      return y;
  }

  int lvalue = 10;
  int rvalue = 20;
  int l_times_r = Multiply(lvalue, rvalue);
Copy the code

So in line 8, we actually do the following:

  1. Push lValue and rvalue values;
  2. Enter the Multiply function, take the values 10 and 20 from the stack and assign them to L and R;
  3. Execute line 2, calculate L * r, and store the result in y;
  4. Pushes the value of y and returns from Multiply;
  5. At line 8, the return value 200 is taken from the stack and assigned to l_times_r.

The above five steps are the process of performing the local call. (20190116 Note: The above steps are just to illustrate the principle. In fact, the compiler often optimizes to place a few parameters and return values directly in the register, without the need to stack, or even call inline. As a matter of principle, these five steps are fine.)


New problems with remote procedure calls

When called remotely, the body of the function we need to execute is on a remote machine, that is, Multiply is executed in another process. This raises several new questions:

  1. Call ID mapping. How do we tell the remote machine that we want to call Multiply instead of Add or FooBar? In local calls, the body of a function is specified directly by a function pointer. When we call Multiply, the compiler automatically calls its corresponding function pointer for us. However, in remote calls, function Pointers are not available because the address Spaces of the two processes are completely different. Therefore, in RPC, all functions must have an ID of their own. This ID is uniquely determined in all processes. The client must attach this ID when making a remote procedure call. Then we need to maintain a table of {function <–> Call ID} on the client side and on the server side. The tables do not have to be identical, but the same function must have the same Call ID. When the client needs to make a remote Call, it looks up the table, finds the corresponding Call ID, and passes it to the server. The server also looks up the table, determines the function that the client needs to Call, and then executes the code for that function.

  2. Serialization and deserialization. How do clients pass parameter values to remote functions? In local calls, we simply push the arguments onto the stack and let the function read from the stack itself. However, in remote procedure calls, the client and server are different processes and cannot pass parameters through memory. Sometimes the client and server even use different languages (such as C++ on the server and Java or Python on the client). In this case, the client needs to convert the parameter into a byte stream, pass it to the server, and then convert the byte stream into a format that it can read. This process is called serialization and deserialization. Similarly, the value returned from the server also needs to be serialized through deserialization.

  3. Network transmission. Remote calls are often used over a network, where clients and servers are connected. All data needs to travel over the network, so there needs to be a network transport layer. The network transport layer passes the Call ID and serialized parameter bytes to the server, which then passes the serialized Call result back to the client. Anything that can do both can be used as a transport layer. Therefore, the protocol it uses is actually unlimited, as long as it can complete the transfer. While most RPC frameworks use TCP, UDP works as well, and gRPC uses HTTP2. Java’s Netty also falls into this category.

With these three mechanisms, RPC can be implemented as follows:

Int l_times_r = Call(ServerAddr, Multiply, lvalue, rvalue) 1. Map this Call to a Call ID. 2. Serialize Call ID, lValue, and rValue. You can package their values directly in binary form 3. Send the packets obtained in 2 to ServerAddr, which requires the use of the network transport layer 4. 5. If the Server call succeeds, deserialize the result and assign it to l_times_r // Server 1. Use STD ::map< STD ::string, STD ::function<>> 2. Wait for request 3. After receiving a request, deserialize its packet to get the Call ID 4. By looking in call_ID_map, we get the corresponding function pointer 5. After deserializing lvalue and rvalue, the Multiply function is called locally, resulting in 6. Serialized results are returned to the Client over the networkCopy the code

Therefore, to achieve an RPC framework, in fact, only need to follow the above process is basically completed.

Among them:

  • Call ID mappings can use function strings directly or integer ids. A mapping table is generally just a hash table.
  • Serialization Deserialization can be written yourself, or it can use things like Protobuf or FlatBuffers.
  • Network transport libraries can write their own sockets, or use ASio, ZeroMQ, Netty and so on.

Of course, there are some details you can fill in, such as how to handle network errors, how to prevent attacks, how to do traffic control, and so on. But with the architecture above, these can be added continuously.

This part comes from: author: HongChunTao links: www.zhihu.com/question/25…






Explanation of proxy Mode

The proxy pattern is a better understood design pattern. To put it simply, we use proxy objects to replace access to real objects. In this way, we can provide additional function operations and extend the function of the target object without modifying the original target object.

The main purpose of the proxy mode is to extend the functionality of the target object. For example, you can add custom operations before and after a method of the target object is executed.

Take an example: you found a small red to help you ask, small red is regarded as the agent of my agent object, the agent’s behavior (method) is to ask.

Proxy mode has static proxy and dynamic proxy two ways to achieve, we first look at the implementation of the static proxy mode.

1. Static proxy

In static proxies, the enhancements we make to each method of the target object are done manually (see the code below), very inflexible (for example, when the interface adds new methods, both the target object and the proxy object need to be modified) and cumbersome (requiring a separate proxy class for each target class). There are very, very few practical application scenarios, and the use of static proxies is rarely seen in daily development.

From the implementation and application perspective, static proxies turn interfaces, implementation classes, and proxy classes into actual class files at compile time.

Static proxy implementation steps:

  1. Define an interface and its implementation class;
  2. Creating a proxy class also implements this interface
  3. The target object injection is injected into the proxy class, and the corresponding method in the target class is called in the corresponding method of the proxy class. This way, we can mask access to the target object through the proxy class and do whatever we want before and after the target method executes.

The following code shows!

1. Define the interface for sending SMS messages

  public interface SmsService {
      String send(String message);
  }
Copy the code

2. Implement the INTERFACE for sending SMS messages

public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; }}Copy the code

3. Create a proxy class and implement the interface for sending SMS messages

public class SmsProxy implements SmsService { private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @override public String send(String message) {system.out.println ("before method send()"); smsService.send(message); System.out.println("after method send()")); return null; }}Copy the code

4. Actual use

public class Main { public static void main(String[] args) { SmsService smsService = new SmsServiceImpl(); SmsProxy smsProxy = new SmsProxy(smsService); smsProxy.send("java"); }}Copy the code

After running the above code, the console prints:

before method send()
send message:java
after method send()
Copy the code

As you can see from the output, we have added the send() method to SmsServiceImpl.

2. Dynamic proxy

Dynamic proxies are more flexible than static proxies. Instead of creating a separate proxy class for each target class, and instead of having to implement the interface, we can directly proxy the implementation class (CGLIB dynamic proxy mechanism).

From the JVM’s perspective, dynamic proxies dynamically generate bytecode classes at run time and load them into the JVM.

When it comes to dynamic proxies, Spring AOP and THE RPC framework are two of the most obvious, both of whose implementations rely on dynamic proxies.

Dynamic proxies are a relatively small part of our daily development, but are almost mandatory in frameworks. Learning dynamic proxies is also very helpful in understanding and learning the principles of various frameworks.

For Java, there are many ways to implement dynamic proxy, such as JDK dynamic proxy, CGLIB dynamic proxy and so on.

Guide-rpc-framework uses JDK dynamic proxy. Let’s take a look at the use of JDK dynamic proxy.

In addition, although guide-Rpc-Framework does not use CGLIB dynamic proxies, here is a brief overview of its use and its comparison with JDK dynamic proxies.

2.1. JDK dynamic proxy mechanism

introduce

The InvocationHandler interface and Proxy class are the core of the Java dynamic Proxy mechanism.

The most frequently used method in the Proxy class is newProxyInstance(), which is used to generate a Proxy object.

public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... }Copy the code

This method takes three parameters:

  • Loader: class loader used to load proxy objects;
  • Interfaces: interfaces implemented by proxy classes;
  • H: An object that implements the InvocationHandler interface.

To implement dynamic proxies, you must also implement InvocationHandler to customize the processing logic. When our dynamic proxy object calls a method, the call to that method is forwarded to the Invoke method that implements the InvocationHandler interface class.

Public interface InvocationHandler {/** * this method is invoked when you invoke a method using a proxy Object. Method method, Object[] args) throws Throwable; }Copy the code

The invoke() method takes the following three arguments:

  • Proxy: dynamically generated proxy class
  • Method: Corresponds to the method called by the proxy class object
  • Args: parameter of the current method

That is: the Proxy object you create with the newProxyInstance() of the Proxy class actually calls the invoke() method of the class that implements the InvocationHandler interface when calling a method. You can customize the processing logic in the invoke() method, such as what to do before and after the method is executed.


JDK dynamic proxy class usage steps

1. Define an interface and its implementation class; 2. Customize the InvocationHandler and rewrite the Invoke method, where we invoke native methods (methods of the propeted class) and customize some processing logic; NewProxyInstance (ClassLoader loader,Class<? > [] interfaces, InvocationHandler h) method to create a proxy object.Copy the code


Code sample

This may sound a little hollow and hard to understand, but I gave you an example.

1. Define the interface for sending SMS messages:

public interface SmsService {
    String send(String message);
}
Copy the code

2. Realize the interface for sending SMS messages:

public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; }}Copy the code

3. Define a JDK dynamic proxy class:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author Shuang. Kou * @createTime 2020年05月11 11:23:00 */ Public Class DebugInvocationHandler implements InvocationHandler {/** * real Object in the proxy class */ private Final Object target; public DebugInvocationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// Before calling the method, we can add our own operation System.out.println("before method "+ method.getName()); Object result = method.invoke(target, args); Println ("after method "+ method.getName())); system.out.println ("after method" + method.getName())); return result; }}Copy the code

Invoke () method: When our dynamic proxy object calls a native method, we actually end up calling the invoke() method, which then calls the proxied object’s native method instead of us.

4. Get the factory class of the proxy object:

public class JdkProxyFactory { public static Object getProxy(Object target) { return Proxy.newProxyInstance( Target.getclass ().getClassloader (), // target class loading target.getClass().getinterfaces (), // the interface that the proxy needs to implement, You can specify multiple New DebugInvocationHandler(target) // custom InvocationHandler for the proxy object); }}Copy the code

GetProxy () : Gets the Proxy object of a class, mainly through proxy.newProxyInstance ()

5. Actual Use:

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
Copy the code

After running the above code, the console prints:

before method send
send message:java
after method send
Copy the code

2.2. CGLIB dynamic proxy mechanism

introduce

One of the most deadly problems with JDK dynamic proxies is that they can only proxy classes that implement the interface.

To solve this problem, we can use CGLIB dynamic proxy mechanism to avoid.

CGLIB(Code Generation Library) is an ASM-based bytecode Generation Library that allows you to modify and generate bytecode dynamically at run time. CGLIB implements the proxy through inheritance. Many well-known open source frameworks use CGLIB, such as AOP modules in Spring: JDK dynamic proxies are used by default if the target object implements an interface, CGLIB dynamic proxies are used otherwise.

The MethodInterceptor interface and Enhancer class are the core of the CGLIB dynamic proxy mechanism.

You’ll need to customize the MethodInterceptor and override the Intercept method, which intercepts methods that enhance the propped class.

Public Object Interceptor extends Callback{public Object interceptor extends Callback{ java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable; }Copy the code
  • Obj: Proxied objects (objects that need enhancement)
  • Method: methods that are being intercepted (methods that need enhancement)
  • Args: method entry parameter
  • MethodProxy: Used to call the original method

You can use the Enhancer class to dynamically fetch the promenade class. When the proxy class calls a method, it actually calls the Intercept method in MethodInterceptor.


Steps for using CGLIB dynamic proxy classes

  1. Define a class;

  2. Custom MethodInterceptor overwrites the Intercept method, which intercepts methods that enhance propped classes, similar to the INVOKE method in JDK dynamic proxies.

  3. Create the proxy class through the Enhancer class create();


Code sample

Unlike the JDK, dynamic proxies do not require additional dependencies. CGLIB(Code Generation Library) is actually an open source project and you need to manually add dependencies if you want to use it.

<dependency> <groupId>cglib</ artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>Copy the code

1. Implement a class using Aliyun to send SMS:

package github.javaguide.dynamicProxy.cglibDynamicProxy; public class AliSmsService { public String send(String message) { System.out.println("send message:" + message); return message; }}Copy the code

2. Custom MethodInterceptor:

import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; Public class DebugMethodInterceptor implements MethodInterceptor {/** * @param Propped object (object to be enhanced) * @param Method Method to be intercepted (method to be enhanced) * @param args method input parameter * @param methodProxy used to call the original method */ @override Public Object Intercept (Object O, Method Method, Object[] args, MethodProxy) throws Throwable {// Before calling a Method, We can add our own system.out.println ("before method "+ method.getName()); Object object = methodProxy.invokeSuper(o, args); Println ("after method "+ method.getName())); system.out.println ("after method" + method.getName())); return object; }}Copy the code

3. Get proxy class:

import net.sf.cglib.proxy.Enhancer; public class CglibProxyFactory { public static Object getProxy(Class<? > clazz) {// create dynamic Enhancer Enhancer = new Enhancer(); // Set the class loader, enhancer.setClassLoader(clazz.getClassLoader()); // Set the propped class henhancer.setsuperclass (clazz); SetCallback (new DebugMethodInterceptor()); // Create the proxy class return enhancer.create(); }}Copy the code

4. Actual use:

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");
Copy the code

After running the above code, the console prints:

before method send
send message:java
after method send
Copy the code

2.3. Comparison between JDK dynamic proxy and CGLIB dynamic proxy

  1. JDK dynamic proxies can only proxy classes that implement interfaces, whereas CGLIB can proxy classes that don’t implement any interfaces. In addition, CGLIB dynamic proxies intercept method calls of proxied classes by generating a subclass of the proxied class, so classes and methods declared as final cannot be proxied.

  2. In most cases, the JDK dynamic proxy is superior in terms of both efficiency, and this advantage becomes more apparent as JDK versions are upgraded.

3. Comparison between static proxy and dynamic proxy

  1. Flexibility: Dynamic proxies are more flexible in that they don’t have to implement interfaces, they can implement classes directly, and they don’t need to create a proxy class for each target class. In addition, in static proxies, once the interface adds new methods, both the target object and the proxy object have to be modified, which is very troublesome!

  2. JVM level: Static agents convert interfaces, implementation classes, and proxy classes into actual class files at compile time. Dynamic proxies, on the other hand, generate bytecodes dynamically at run time and load them into the JVM.

5. To summarize

This article mainly introduces two implementations of proxy pattern: static proxy and dynamic proxy. It covers static proxy and dynamic proxy combat, the difference between static proxy and dynamic proxy, JDK dynamic proxy and Cglib dynamic proxy difference and so on.

This part from the elder brother of the Guide JavaGuide project contents snailclimb. Gitee. IO/JavaGuide / #…