Dubbo source code analysis (46) the process of sending the request by the consumer

Time:2019-11-26

2.7 Disclosure – the process of the consumer sending the request

Objective: from the source point of view to analyze how a service method call experience after the ordeal to reach the server.

Preface

The previous article talked about the process of reference service, which is nothing more than creating a proxy. Methods for consumers to invoke the service. This section will start with the call method to explain the entire internal call chain. Let’s take the example of Dubbo.

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
DemoService demoService = context.getBean("demoService", DemoService.class);
String hello = demoService.sayHello("world");
System.out.println("result: " + hello);

This is the instance code in Dubbo demo XML consumer. Let’s start to see what Dubbo does when calling the demoservice.sayhello method.

Execution process

(I) invoke of invokerinvocationhandler

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //Get method name
    String methodName = method.getName();
    //Get method parameter type
    Class<?>[] parameterTypes = method.getParameterTypes();
    //If the class of the method is of object type, invoke is called directly.
    if (method.getDeclaringClass() == Object.class) {
        return method.invoke(invoker, args);
    }
    //If this method is toString, call invoker. Tostring() directly
    if ("toString".equals(methodName) && parameterTypes.length == 0) {
        return invoker.toString();
    }
    //If this method is hashcode, call invoker. Hashcode ()
    if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
        return invoker.hashCode();
    }
    //If this method is equals, call invoker. Equals (args [0]) directly
    if ("equals".equals(methodName) && parameterTypes.length == 1) {
        return invoker.equals(args[0]);
    }

    //Call invoke
    return invoker.invoke(new RpcInvocation(method, args)).recreate();
}

You can see the source code above. First, the methods of object are processed. If the called methods are not these methods, rpcinvocation will be created first, and then invoke.

Construction method of rpcinvocation

public RpcInvocation(Method method, Object[] arguments) {
    this(method.getName(), method.getParameterTypes(), arguments, null, null);
}
public RpcInvocation(String methodName, Class<?>[] parameterTypes, Object[] arguments, Map<String, String> attachments, Invoker<?> invoker) {
    //Set method name
    this.methodName = methodName;
    //Set parameter type
    this.parameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
    //Set parameters
    this.arguments = arguments == null ? new Object[0] : arguments;
    //Set additional value
    this.attachments = attachments == null ? new HashMap<String, String>() : attachments;
    //Set invoker entity
    this.invoker = invoker;
}

After rpcinvocation is created, invoke is called. The first entry is the listener invokerwrapper’s invoke.

(II) invoke of mockclusterinvoker

You can refer to (2) mockclusterinvoker in Dubbo source code analysis (41) cluster – mock. The implementation of the degraded return policy determines whether to not downgrade or force the service to downgrade or fail the service to downgrade according to the configuration.

(III) invoke of abstractclusterinvoker

You can refer to (1) abstractclusterinvoker in Dubbo source code analysis (35) cluster. This class is an abstract class, which encapsulates some common methods, and abstractclusterinvoker’s invoke only does some common operations. The main logic is in doinvoke.

(IV) doinvoke of failoverclusterinvoker

You can refer to (12) failoverclusterinvoker in Dubbo source code analysis (35) cluster. This class implements the fault tolerance strategy of failure retry.

(V) invokewrapper’s invoke

You can refer to (5) invokerwrapper in Dubbo source code parsing (22) remote call protocol. This class uses a decoration pattern, but does not implement the actual enhancements.

(VI) the internal class of protocolfilterwrapper, callbackregistrationinvoker, invoker

public Result invoke(Invocation invocation) throws RpcException {
    //Invoke the interceptor chain
    Result asyncResult = filterInvoker.invoke(invocation);

    //Add asynchronous returned results to context
    asyncResult.thenApplyWithContext(r -> {
        //Cycle each filter
        for (int i = filters.size() - 1; i >= 0; i--) {
            Filter filter = filters.get(i);
            // onResponse callback
            //If the filter is of type listenablefilter
            if (filter instanceof ListenableFilter) {
                //Forced type conversion
                Filter.Listener listener = ((ListenableFilter) filter).listener();
                if (listener != null) {
                    //If the internal class listener is not empty, call the callback method onresponse
                    listener.onResponse(r, filterInvoker, invocation);
                }
            } else {
                //Otherwise, call the onresponse of the filter directly for compatibility.
                filter.onResponse(r, filterInvoker, invocation);
            }
        }
        //Return asynchronous results
        return r;
    });

    //Return asynchronous results
    return asyncResult;
}

The first thing we see here is to call the invoke method of the interceptor chain. The following logic is to put the results returned asynchronously into the context. I will talk about the specific listenablefilter and the design of internal classes, and the theapplywithcontext and other methods in the asynchronous implementation.

(7) the invoke method of the invoker instance in the buildinvokerchain method of the protocolfilterwrapper.

public Result invoke(Invocation invocation) throws RpcException {
    Result asyncResult;
    try {
        //Call each filter in turn to get the final return result
        asyncResult = filter.invoke(next, invocation);
    } catch (Exception e) {
        // onError callback
        //Catch exceptions if the filter is of type listenablefilter
        if (filter instanceof ListenableFilter) {
            //Get inner class listener
            Filter.Listener listener = ((ListenableFilter) filter).listener();
            if (listener != null) {
                //Call onerror to call back error information
                listener.onError(e, invoker, invocation);
            }
        }
        //Throw exception
        throw e;
    }
    //Return results
    return asyncResult;
}

In this method, the exception is caught and the error information is recalled by calling the onerror of the internal class listener. Next let’s see what interceptors it passes through.

(VIII) invoke of consumercontextfilter

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    //Get context, set invoker, session domain, local address and original address
    RpcContext.getContext()
            .setInvoker(invoker)
            .setInvocation(invocation)
            .setLocalAddress(NetUtils.getLocalHost(), 0)
            .setRemoteAddress(invoker.getUrl().getHost(),
                    invoker.getUrl().getPort());
    //Set invoker if session domain is rpcinvocation
    if (invocation instanceof RpcInvocation) {
        ((RpcInvocation) invocation).setInvoker(invoker);
    }
    try {
        //Remove the context of the server
        RpcContext.removeServerContext();
        //Call next filter
        return invoker.invoke(invocation);
    } finally {
        //Clear context
        RpcContext.removeContext();
    }
}
static class ConsumerContextListener implements Listener {
    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        //Put the added value in the result into the context
        RpcContext.getServerContext().setAttachments(appResponse.getAttachments());
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
        //Do nothing
    }
}

You can refer to Dubbo source code analysis (20) remote call filter, but the source code above is the latest, and the source code in the link is 2.6. X. although some changes have been made, such as the design of internal classes, the subsequent filters have the same implementation, but the function of consumercontextfilter has not changed, it still records the local call in the current rpccontext Primary status information for. After the filter is executed, it will return to the

Result result = filter.invoke(next, invocation);

Then continue to call the next filter, futurefilter.

(IX) invoke of futurefilter

public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
    //This method is the execution of the real calling method
    fireInvokeCallback(invoker, invocation);
    // need to configure if there's return value before the invocation in order to help invoker to judge if it's
    // necessary to return future.
    return invoker.invoke(invocation);
}
class FutureListener implements Listener {
    @Override
    public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
        if (result.hasException()) {
            //Handling abnormal results
            fireThrowCallback(invoker, invocation, result.getException());
        } else {
            //Handle normal results
            fireReturnCallback(invoker, invocation, result.getValue());
        }
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

    }
}

You can refer to (14) futurefilter in Dubbo source code analysis (24) remote call – Dubbo protocol, some of which have different structures, just like consumercontextfilter, because in the subsequent version, the filter interface has been newly designed, the onresponse method has been added, and the returned execution logic has been put into onresponse. Other logic has not changed much. After the implementation of the filter, return to the invoke of the protocolfilterwrapper and continue to call the next filter, monitorfilter.

(x) invoke of monitorfilter

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    //If monitoring is turned on
    if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
        //Set monitoring start time
        invocation.setAttachment(MONITOR_FILTER_START_TIME, String.valueOf(System.currentTimeMillis()));
        //Get the current number of calls and increase
        getConcurrent(invoker, invocation).incrementAndGet(); // count up
    }
    return invoker.invoke(invocation); // proceed invocation chain
}
class MonitorListener implements Listener {

    @Override
    public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
        //If monitoring is turned on
        if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
            //Monitor and collect data
            collect(invoker, invocation, result, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), false);
            // reduce the number of calls.
            getConcurrent(invoker, invocation).decrementAndGet(); // count down
        }
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
        //If monitoring is turned on
        if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
            //Monitor and collect data
            collect(invoker, invocation, null, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), true);
            // reduce the number of calls.
            getConcurrent(invoker, invocation).decrementAndGet(); // count down
        }
    }

You can see that the filter is actually used for monitoring, monitoring the number of service calls, etc. Among them, the logic of monitoring is not the focus of this paper, so it is not detailed. The next call is the listener invoker wrapper’s invoke.

(XI) listenerinvokerwrapper’s invoke

public Result invoke(Invocation invocation) throws RpcException {
    return invoker.invoke(invocation);
}

You can refer to Dubbo source code analysis (21) remote call listener, where the decorator mode is used and the invoker is called directly. In this class, a service startup listener is made. Let’s focus directly on the next invoke.

(XII) asynctosyncinvoker’s invoke

public Result invoke(Invocation invocation) throws RpcException {
    Result asyncResult = invoker.invoke(invocation);

    try {
        //If it's a synchronous call
        if (InvokeMode.SYNC == ((RpcInvocation)invocation).getInvokeMode()) {
            //Get results from asynchronous results
            asyncResult.get();
        }
    } catch (InterruptedException e) {
        throw new RpcException("Interrupted unexpectedly while waiting for remoting result to return!  method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
    } catch (ExecutionException e) {
        Throwable t = e.getCause();
        if (t instanceof TimeoutException) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } else if (t instanceof RemotingException) {
            throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    } catch (Throwable e) {
        throw new RpcException(e.getMessage(), e);
    }
    //Return asynchronous results
    return asyncResult;
}

The asynctosyncinvoker class is well understood by its name. Its function is to convert asynchronous results into synchronous results. In the new change, as long as each call is not oneway, it will start with an asynchronous call, and then if it is a synchronous call according to the configuration, the asynchronous result will be transferred to synchronization in this class. Of course, the first step is to execute the invoke, and then to enter the next abstractinvoker’s invoke.

(XIII) abstractinvoker’s invoke

public Result invoke(Invocation inv) throws RpcException {
    // if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
    //If the service reference is destroyed, the alarm log will be printed, but through the
    if (destroyed.get()) {
        logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
                + ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
    }
    RpcInvocation invocation = (RpcInvocation) inv;
    //Join the call chain in the session domain
    invocation.setInvoker(this);
    //Put the added value in the session domain
    if (CollectionUtils.isNotEmptyMap(attachment)) {
        invocation.addAttachmentsIfAbsent(attachment);
    }
    //Add context value to session domain
    Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
    if (CollectionUtils.isNotEmptyMap(contextAttachments)) {
        /**
         * invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
         * because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
         * by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
         * a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
         */
        invocation.addAttachments(contextAttachments);
    }

    //Get the mode call from the configuration, including function, async and sync
    invocation.setInvokeMode(RpcUtils.getInvokeMode(url, invocation));
    //Add number
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

    try {
        //Execute call chain
        return doInvoke(invocation);
    } catch (InvocationTargetException e) { // biz exception
        //Get exception
        Throwable te = e.getTargetException();
        if (te == null) {
            //Create default exception asynchronous result
            return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
        } else {
            if (te instanceof RpcException) {
                //Set exception code
                ((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
            }
            //Create default exception asynchronous result
            return AsyncRpcResult.newDefaultAsyncResult(null, te, invocation);
        }
    } catch (RpcException e) {
        if (e.isBiz()) {
            return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
        } else {
            throw e;
        }
    } catch (Throwable e) {
        return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
    }
}

You can refer to (3) abstractinvoker in Dubbo source code parsing (22) remote call protocol. This method does some common operations, such as the detection of service reference destruction, adding added value, adding call chain entity domain to session domain. Then the doinvoke abstract method is executed. Each agreement will be implemented by itself. And then it goes to the doinvoke method. The protocol used is different, and the logic of doinvoke is different. The example I give here is using the Dubbo protocol, so I will introduce the doinvoke of dubboinvoker, and check the specific implementation by myself. This asynchronous transformation adds invokemode, which I will introduce later.

(XIV) doinvoke of dubboinvoker

protected Result doInvoke(final Invocation invocation) throws Throwable {
    //RPC session domain
    RpcInvocation inv = (RpcInvocation) invocation;
    //Get method name
    final String methodName = RpcUtils.getMethodName(invocation);
    //Put path into the added value
    inv.setAttachment(PATH_KEY, getUrl().getPath());
    //Put version number into added value
    inv.setAttachment(VERSION_KEY, version);

    //Current client
    ExchangeClient currentClient;
    //If there is only one client in the array, take it out directly
    if (clients.length == 1) {
        currentClient = clients[0];
    } else {
        //Modulo polling is taken from the array. When the last one is taken, start from the beginning
        currentClient = clients[index.getAndIncrement() % clients.length];
    }
    try {
        //Is it a one-way delivery
        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
        //Get timeout
        int timeout = getUrl().getMethodParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);
        //In case of one-way transmission
        if (isOneway) {
            //Whether to wait for the message to be sent. By default, it does not wait for the message to be sent. Put the message into the IO queue and return immediately.
            boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
            //One way sending is only responsible for sending messages and does not wait for the server to respond, so there is no return value
            currentClient.send(inv, isSent);
            //Set future to null because there is no return value for one-way send
            RpcContext.getContext().setFuture(null);
            //Create a default asyncrpcresult
            return AsyncRpcResult.newDefaultAsyncResult(invocation);
        } else {
            //Otherwise, create asyncrpcresult directly
            AsyncRpcResult asyncRpcResult = new AsyncRpcResult(inv);
            //Asynchronous call, return the future of type completabilefuture
            CompletableFuture<Object> responseFuture = currentClient.request(inv, timeout);
            //When the result of the call is complete
            responseFuture.whenComplete((obj, t) -> {
                //If there is any abnormality
                if (t != null) {
                    //Throw an exception
                    asyncRpcResult.completeExceptionally(t);
                } else {
                    //Call complete
                    asyncRpcResult.complete((AppResponse) obj);
                }
            });
            //The asynchronous return result is wrapped with completable future, and the future is placed in the context,
            RpcContext.getContext().setFuture(new FutureAdapter(asyncRpcResult));
            //Return results
            return asyncRpcResult;
        }
    } 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);
    }
}

You can refer to Dubbo invoker in Dubbo source code analysis (24) remote call – Dubbo protocol (1). However, the source code of the article in the link is version 2.6. X, and the above source code is the latest version, among which there are asynchronous changes, such as adding asynchronous return results, in addition to one-way call, all of them are processed into asyncrpcresult first. The specific asyncrpcresult and the completable future used in it will be introduced below.

Executing currentclient.request or currentclient.send in the above source code means putting the request into the channel and handing it to the channel to handle the request. Finally, let’s look at a currentclient. Request, because it involves the construction of future.

(15) request of referencecounteexchangeclient

public CompletableFuture<Object> request(Object request, int timeout) throws RemotingException {
    return client.request(request, timeout);
}

Referencecounteexchangeclient is a class that records the number of requests. It uses adapter mode to enhance the function of exchangeclient.

You can refer to (8) referencecounteexchangeclient of Dubbo source code parsing (24) remote call – Dubbo protocol.

(XVI) request of headerexchangeclient

public CompletableFuture<Object> request(Object request, int timeout) throws RemotingException {
    return channel.request(request, timeout);
}

This class also uses adapter mode. The main function of this class is to add heartbeat function. Please refer to (IV) header exchange client in Dubbo source code analysis (x) remote communication exchange layer. Then enter the request of headerexchangechannel.

(XVII) header exchange channel request

You can refer to (2) headerexchangechannel in Dubbo source code analysis (10) remote communication – exchange layer. You can see it in this request method

//To create a defaultfuture object, you can actively obtain the response information corresponding to the request from the future
    DefaultFuture future = new DefaultFuture(channel, req, timeout);

Generated the desired future. The result of the asynchronous request is obtained from this future. For defaultfuture, please refer to (7) defaultfuture in Dubbo source code analysis (10) remote communication exchange layer.

The latter channel.send method is related to remote communication. For example, if you use netty as the communication implementation, you will use the client implemented by netty to communicate.

(XVIII) send of abstractpeer

You can refer to (1) abstractpeer in Dubbo source code analysis (9) remote communication – transport layer, where the send method is relatively simple, and send messages according to the send configuration item. Let’s look at the send of abstractclient

(XIX) send of abstractclient

You can refer to (4) abstractclient in Dubbo source code analysis (9) telecommunication – transport layer.

public void send(Object message, boolean sent) throws RemotingException {
    //If you need to reconnect or do not have a link, connect
    if (needReconnect && !isConnected()) {
        connect();
    }
    //Access
    Channel channel = getChannel();
    //TODO Can the value returned by getChannel() be null? need improvement.
    if (channel == null || !channel.isConnected()) {
        throw new RemotingException(this, "message can not send, because channel is closed . url:" + getUrl());
    }
    //Send message through channel
    channel.send(message, sent);
}

In this method, the logic of reconnection is done, and then the message is sent through the channel. Dubbo has several communication implementations. I’ll explain in accordance with the default implementation of netty4 here, so the next step is to go to the send of nettychannel.

(XX) send of nettychannel

You can refer to (1) nettychannel in Dubbo source code analysis (17) telecommunication – netty4. Here, the send of the parent class abstractchannel is executed first, check whether the channel is closed, and then follow the logic below. When the writeandflush method is executed, the message is sent.

The Dubbo data package can be viewed in (25) exchangecodec of Dubbo source code analysis (10) remote communication exchange layer. I will not introduce the coding operation of netty sending messages and netty outbound data before sending. It is mainly related to netty knowledge points, but Dubbo has made some coding and integrated various sequences Ways of transformation.

Epilogue

This article explains all the steps of Dubbo calling the service method, until the calling message is sent to the server, it is the latest code parsing. The next article will explain how to handle and return the call result after the server receives the request of method call.

Recommended Today

Python final review resources

Tip: blue is the required part, yellow is the prompt and unnecessary part. 1. Source code file extension * py 2. Python uses indentation as the syntax boundary, and generally uses four grid indentation. 3. Python variables and functions are defined without specifying the type.  Python variables do not need to be declared in advance […]