SOFA source code analysis - service reference process

foreword

In the previous article on SOFA source code analysis - service publishing process , we analyzed SOFA's service publishing process. In addition to publishing services, a complete RPC also needs to reference services. So, let's take a look at how SOFA refers to services today. In fact, the basic logic is similar to the RPC small demo we wrote in Netty before. If you are interested, you can take a look at this demo - implement a simple RPC with Netty .

sample code

ConsumerConfig<HelloService> consumerConfig = new ConsumerConfig<HelloService>()
    .setInterfaceId(HelloService.class.getName()) // 指定接口
    .setProtocol("bolt") // 指定协议
    .setDirectUrl("bolt://127.0.0.1:9696"); // 指定直连地址

HelloService helloService = consumerConfig.refer();

while (true) {
    System.out.println(helloService.sayHello("world"));
    try {
        Thread.sleep(2000);
    } catch (Exception e) {
    }
}

Again, the code examples are from the SOFA-RPC source code at com.alipay.sofa.rpc.quickstart.QuickStartClient.

Very simple, create a consumer configuration class, then use this configuration to refer to a proxy object, and call
the method of the proxy object, which is actually calling the remote service.

Let's use the above simple example to see how SOFA performs service reference. Note: Our purpose today is to take a look at the main process. Some details may be passed temporarily, such as load balancing, error retry, etc. We will analyze it in detail later. In fact, the Client has a relatively Server, which is more complicated. , because it has more cases to consider.

OK, let's get started!

Source code analysis

First look at this ConsumerConfig class, which is similar to the previous ProviderConfig, even the implemented interface and inherited abstract class are the same.

The above example sets some properties, such as interface name, protocol, and direct address.

The key point is the refer method.

This method returns a proxy object, which contains all the information needed in subsequent remote calls, such as filters, load balancing, and so on.

Then, call the method of the dynamic proxy to make a remote call. If it is a dynamic proxy of the JDK, it is a class that implements the InvocationHandler interface. The invoke method of this class will intercept and make remote calls. Naturally, it uses Netty's client to call the server and get data.

Take a look at the refer method first.

Start source code analysis from the refer method

This method class routine is similar to the provider routine, and both use a BootStrap to guide. i.e. single responsibility.

public T refer() {
    if (consumerBootstrap == null) {
        consumerBootstrap = Bootstraps.from(this);
    }
    return consumerBootstrap.refer();
}

ConsumerBootstrap is an abstract class that SOFA extends based on. There are currently 2 extension points, bolt and rest. The default is bolt. The implementation of bolt is BoltConsumerBootstrap. At present, there is no difference between bolt and rest. Both use a parent class DefaultConsumerBootstrap.

So, let's take a look at the refer method of DefaultConsumerBootstrap. I won't post the code because it's too long. Basically, the logic of referencing the service is all here, similar to Spring's refresh method. The logic is as follows:

  1. Create key and appName based on ConsumerConfig. Check parameter validity. Count the calls.
  2. Create a Cluster object, this object is very important, the object manages the core part of the information. The details will be said later, and the parameter to construct the object is BootStrap.
  3. Set up some listeners.
  4. Initialize the Cluster. These include setting up routes, address initialization, connection management, filter chain construction, reconnection threads, and more.
  5. Create a proxyInvoker execution object, that is, the initial call object, which is injected into the interception class of the dynamic proxy, so that the dynamic proxy can start calling from here. The constructor parameter is also BootStrap.
  6. Finally, create a dynamic proxy object. At present, the dynamic proxy has two extension points, namely JDK and javassist. The default is JDK, but it seems that javassist will perform a little better. If it is JDK, the interceptor is the JDKInvocationHandler class, and the constructor requires the proxy class and the proxyInvoker object just created. The role of proxyInvoker is to start chaining calls from here.

Among them, the key object is Cluster. This object needs focus.

Cluster is an abstract class and an extension point that implements Invoker, ProviderInfoListener, Initializable, Destroyable and other interfaces. He currently has 2 specific extension points: FailFastCluster (fast failure), FailoverCluster (failover and retry). The default is the latter. Of course, there is also an abstract parent class, AbstractCluster.

This class has an important method, the init method, which initializes the Cluster. The Cluster can be understood as a client, which encapsulates abstract classes such as cluster mode, long-term connection management, service routing, and load balancing.

The init method code is as follows, not much.

public synchronized void init() {
    if (initialized) { // 已初始化
        return;
    }
    // 构造Router链
    routerChain = RouterChain.buildConsumerChain(consumerBootstrap);
    // 负载均衡策略 考虑是否可动态替换?
    loadBalancer = LoadBalancerFactory.getLoadBalancer(consumerBootstrap);
    // 地址管理器
    addressHolder = AddressHolderFactory.getAddressHolder(consumerBootstrap);
    // 连接管理器
    connectionHolder = ConnectionHolderFactory.getConnectionHolder(consumerBootstrap);
    // 构造Filter链,最底层是调用过滤器
    this.filterChain = FilterChain.buildConsumerChain(this.consumerConfig,
        new ConsumerInvoker(consumerBootstrap));
    // 启动重连线程
    connectionHolder.init();
    // 得到服务端列表
    List<ProviderGroup> all = consumerBootstrap.subscribe();
    if (CommonUtils.isNotEmpty(all)) {
        // 初始化服务端连接(建立长连接)
        updateAllProviders(all);
    }
    // 启动成功
    initialized = true;
    // 如果check=true表示强依赖
    if (consumerConfig.isCheck() && !isAvailable()) {
    }
}

As you can see, there is a lot of work to do. Each step is worth a lot of time to see, but after reading all the tasks that are not our task today, we will pay attention to the call today. In the above code, there is a piece of code that builds FilterChain that is worthy of our attention today.

A ConsumerInvoker object is created as the last filter call. We have studied the design of the filter before, so I won't repeat it. Details SOFA Source Code Analysis - Filter Design .

We mainly look at the ConsumerInvoker class, which is the closest filter to Netty. In fact, he also has a BootStrap, but, note that having a BootStrap is equivalent to coercing the emperor to make the princes, and he has everything. In his invoke method, he will directly obtain the Boostrap Cluster and send data to Netty.

code show as below:

return consumerBootstrap.getCluster().sendMsg(providerInfo, sofaRequest);

Great.

So, how does the Cluster sendMsg? If it is a Bolt-type Cluster, it is called directly using bolt's RpcClient, and RpcClient uses the writeAndFlush method of Netty's Channel to send data. If it is a synchronous call, it blocks waiting for data.

The general process is like this, and the specific details are left for later analysis.

Let's see how to call after getting the dynamic proxy object.

How to call dynamic proxy?

When we call it, it will definitely be intercepted by JDKInvocationHandler. The intercept method is the invoke method. The method is very simple, mainly using the invoke method of the proxyInvoker we injected earlier. As we said before, the role of proxyInvoker is actually the head of a linked list. And he mainly represents the real protagonist Cluster, so you can imagine that his invoke method must call the cluster's invoke method.

Cluster is the real protagonist (note: the default Cluster is FailoverCluster), then his calls must be a series of filters. There are currently two filters by default: ConsumerExceptionFilter, RpcReferenceContextFilter. The last filter is what we said before, the filter closest to Netty - ConsumerInvoker.

The ConsumerInvoker will call the sendMsg method of the Cluster. The Cluster contains a ClientTransport. This ClientTransport is a glue class that integrates the bolt's RpcClient. So, you can think that when ConsumerInvoker calls the sendMsg method, it will finally call the invokeXXX method of RpcClient, which may be asynchronous or synchronous. Bolt supports multiple calling methods.

And RpcClient finally calls the writeAndFlush method of Netty's Channel to send data to the service provider.

Take a piece of code that is executed synchronously (default) in RpcClietn:

protected RemotingCommand invokeSync(final Connection conn, final RemotingCommand request,
                                     final int timeoutMillis) throws RemotingException,
                                                             InterruptedException {
    final InvokeFuture future = createInvokeFuture(request, request.getInvokeContext());
    conn.addInvokeFuture(future);
    conn.getChannel().writeAndFlush(request).addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture f) throws Exception {
            if (!f.isSuccess()) {
                conn.removeInvokeFuture(request.getId());
                future.putResponse(commandFactory.createSendFailedResponse(
                    conn.getRemoteAddress(), f.cause()));
            }
        }
    });
    // 阻塞等待
    RemotingCommand response = future.waitResponse(timeoutMillis);
    return response;
}

Send data through the writeAndFlush method of Netty's Channel, and add a listener. If it fails, inject a failed object into the future.

After asynchronous execution, the main thread starts to wait and internally uses countDownLatch to block. The initialization parameter of countDownLatch is 1. When does countDownLatch wake up?

In the putResponse method, countDownLatch will be woken up.

The putResponse method is used in many places. For example, it will be called in the doProcess method of the bolt's RpcResponseProcessor. And this method is called indirectly in the channelRead method of RpcHandler.

Therefore, if writeAndFlush fails, it will putResponse. If there is no failure, it will be executed normally, and putResponse will be called after the channelRead method.

To sum up the logic of the call, the landlord drew a picture, you can take a look, the drawing is not good, please forgive me.

image.png

The red line is the call chain, starting from JDKInvocationHandler and ending with Netty. The green part is the core of the Cluster and Client. The big red parts are bolt and Netty.

Well, we have almost finished introducing the main process of SOFA's service reference today. Of course, there are many essences that have not been studied. We will study it slowly in the future.

Summarize

After reading SOFA's service release and service reference, compared to SpringCloud, I really feel it is very lightweight. The above picture can basically show most of the calling process, which is unimaginable in the framework of Spring and Tomcat. The isolation of bolts also gives the RPC framework more choices. Through different extension points, you can use different network communication frameworks. At this time, it is necessary to have an official SOFA picture:

image.png

As can be seen from the above figure, the places we are more familiar with today, such as Cluster, include filters, load balancing, routing, and then call the remote module of remoting, that is, bolt. Via the sendMsg method.

As for the external modules of Cluster, we didn't take a closer look today, and this is definitely reserved for the future. Such as address management, connection management and so on.

Alright, that's it for today. If there is anything wrong, please correct me.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325125438&siteId=291194637