Dubbo source code analysis eight: client service call process

Insert picture description here

Introduction

In fact, the client-side service invocation process is very similar to the server-side request processing process. If you understand the Channelhandler through which the request passes, you can understand more than half.

If you haven’t read
Dubbo source code analysis: the service provider receives the request and returns the result

It is recommended to take a look first, so that you can understand the difference between Dubbo and Netty Channelhandler.

Channelhandler in Netty uses the chain of responsibility mode, while Channelhandler in Dubbo uses the decorator mode .

Commonly used Handlers are as follows

ChannelHandler effect
NettyClientHandler Handle Netty client events, such as connection, disconnection, reading, writing, and exceptions
NettyServerHandler Handle Netty server events, such as connection, disconnection, reading, writing, and exceptions
MultiMessageHandler Multi-message batch processing
HeartbeatHandler Heartbeat processing
AllChannelHandler Put all Netty requests into the business thread pool for processing
DecodeHandler Decode the message
HeaderExchangeHandler Package processing Request/Reponse, and telnet request
ExchangeHandlerAdapter Find the service method and call

Among them, NettyClientHandler and NettyServerHandler implement the ChannelHandler interface in Netty, while the others implement the ChannelHandler interface in Dubbo.

Start remote call

We mentioned in the article on the introduction of the service that the proxy object finally returned is obtained by calling the JavassistProxyFactory#getProxy method

// JavassistProxyFactory
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    
    
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

So any method executed by the proxy object will enter InvokerInvocationHandler#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    String methodName = method.getName();
    Class<?>[] parameterTypes = method.getParameterTypes();
    // 拦截定义在 Object 类中的方法(未被子类重写),比如 wait/notify
    if (method.getDeclaringClass() == Object.class) {
    
    
        return method.invoke(invoker, args);
    }
    // 如果 toString、hashCode 和 equals 等方法被子类重写了,这里也直接调用
    if ("toString".equals(methodName) && parameterTypes.length == 0) {
    
    
        return invoker.toString();
    }
    if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
    
    
        return invoker.hashCode();
    }
    if ("equals".equals(methodName) && parameterTypes.length == 1) {
    
    
        return invoker.equals(args[0]);
    }
    // 将 method 和 args 封装到 RpcInvocation 中,并执行后续的调用
    return invoker.invoke(createInvocation(method, args)).recreate();
}

The execution link is as follows
Insert picture description here
: MockClusterInvoker: service degradation
FailoverClusterInvoker: cluster fault tolerance
DubboInvoker: initiate a network call and get the result

MockClusterInvoker

MockClusterInvoker is mainly used for service degradation, that is, it returns a specific value before the request, does not initiate the actual call, or after the call fails, does not throw an exception and returns a specific value

You can refer to the official document:
http://dubbo.apache.org/zh/docs/v2.7/user/examples/service-downgrade/

DubboInvoker

// DubboInvoker
protected Result doInvoke(final Invocation invocation) throws Throwable {
    
    
    // 设置附加属性
    RpcInvocation inv = (RpcInvocation) invocation;
    final String methodName = RpcUtils.getMethodName(invocation);
    // 设置路径,版本
    inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
    inv.setAttachment(Constants.VERSION_KEY, version);

    // 获取客户端,发起实际调用
    // 构造DubboInvoker的时候已经初始化好了
    ExchangeClient currentClient;
    if (clients.length == 1) {
    
    
        currentClient = clients[0];
    } else {
    
    
        currentClient = clients[index.getAndIncrement() % clients.length];
    }
    try {
    
    
        // 是否为异步
        boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
        // 是否为future方式异步
        boolean isAsyncFuture = RpcUtils.isReturnTypeFuture(inv);
        // isOneway 为 true,表示“单向”通信
        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
        // 超时等待时间
        int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        // 不需要响应的请求
        if (isOneway) {
    
    
            boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
            // 发起网络调用
            currentClient.send(inv, isSent);
            RpcContext.getContext().setFuture(null);
            return new RpcResult();
        } else if (isAsync) {
    
    
            // 异步有返回值
            // 同步:框架调用ResponseFuture#get()
            // 异步:用户调用ResponseFuture#get()
            ResponseFuture future = currentClient.request(inv, timeout);
            // For compatibility
            // FutureAdapter 是将ResponseFuture与jdk中的Future进行适配
            // 当用户调用Future的get方法时,经过FutureAdapter的适配,最终会调用DefaultFuture的get方法
            FutureAdapter<Object> futureAdapter = new FutureAdapter<>(future);
            RpcContext.getContext().setFuture(futureAdapter);

            Result result;
            // 返回值是否是 CompletableFuture
            if (isAsyncFuture) {
    
    
                // register resultCallback, sometimes we need the async result being processed by the filter chain.
                result = new AsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
            } else {
    
    
                result = new SimpleAsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
            }
            return result;
        } else {
    
    
            // 同步有返回值
            RpcContext.getContext().setFuture(null);
            // 发送请求,得到一个 ResponseFuture 实例,并调用该实例的 get 方法进行等待
            // 本质上还是通过异步的代码来实现同步调用
            return (Result) currentClient.request(inv, timeout).get();
        }
    } catch (TimeoutException e) {
    
    
        throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
    } catch (RemotingException e) {
    
    
        throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

DubboInvoker#doInvoke This method is the method that actually initiates the call.
There are two situations when calling.

  1. Need return value
  2. No need to return value
    2.1 Synchronous request, the framework calls the get method of DefaultFuture
    2.2 Asynchronous request, the user calls the get method of DefaultFuture

The process of synchronous call and asynchronous call will be analyzed in a separate article later.

Send network request

In fact, the process of the client sending a request and receiving a response is similar to that of the server. Both initialize the handler in the constructor and call the doOpen method to open the connection.

Analyze according to the old routine, and see which handlers NettyClient sends and receives requests through.
Insert picture description here
You can see that except for NettyClientHandler and NettyClient, the rest of the Handler and the server are the same

// 
protected void doOpen() throws Throwable {
    
    
    // 执行业务逻辑的handler
    final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
    bootstrap = new Bootstrap();
    bootstrap.group(nioEventLoopGroup)
            .option(ChannelOption.SO_KEEPALIVE, true)
            .option(ChannelOption.TCP_NODELAY, true)
            .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
            //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
            .channel(NioSocketChannel.class);

    if (getConnectTimeout() < 3000) {
    
    
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
    } else {
    
    
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
    }

    bootstrap.handler(new ChannelInitializer() {
    
    

        @Override
        protected void initChannel(Channel ch) throws Exception {
    
    
            int heartbeatInterval = UrlUtils.getHeartbeat(getUrl());
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
            ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                    .addLast("decoder", adapter.getDecoder())
                    .addLast("encoder", adapter.getEncoder())
                    .addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
                    .addLast("handler", nettyClientHandler);
        }
    });
}

To sum up, the process of initiating a request is as follows

  1. NettyClientHandler#write
  2. NettyClient(AbstractPeer#sent)
  3. MultiMessageHandler(AbstractChannelHandlerDelegate#sent)
  4. HeartbeatHandler#sent
  5. AllChannelHandler(WrappedChannelHandler#sent) (here the default is AllChannelHandler, the thread model and thread pool strategy can be determined through SPI)
  6. DecodeHandler(AbstractChannelHandlerDelegate#sent)
  7. HeaderExchangeHandler#sent
  8. ExchangeHandlerAdapter#sent (an anonymous inner class in DubboProtocol, which is an empty implementation)

Receive response

The handler through which the client received the message

  1. NettyClientHandler#channelRead
  2. NettyClient(AbstractPeer#received)
  3. MultiMessageHandler#received
  4. HeartbeatHandler#received
  5. AllChannelHandle#received (the default is AllChannelHandler here)
  6. DecodeHandler#received
  7. HeaderExchangeHandler#received (terminated here)
  8. DubboProtocol中的ExchangeHandlerAdapter#received

The detailed process will be in "Dubbo source code analysis: How does Dubbo support synchronous and asynchronous calls? 》Analysis

Reference blog

Timeout exception
[1]https://juejin.im/post/6844903857416323079

Guess you like

Origin blog.csdn.net/zzti_erlie/article/details/109473813