dubbo Source Resolution Server Processing Request Processing

Keywords: Java Dubbo Session JSON

2.7 Disclosure - Service side processing request process

Target: From the source point of view, analyze a series of operations after the server receives the request, and eventually return the value that the client needs.

Preface

The previous article talked about the process of sending requests by the consumer side, which is about the process of processing requests by the server side.This means a series of processing after the server receives the request packet and how to return the final result.We also know that the consumer has encoded the request, so we also need to decode the protocol header and the protocol body after the server receives the packet.However, this article does not explain how to decode.Interesting ones can flip through my previous articles and talk about the logic of decoding.Next, you will begin to explain the logic of the server after it receives the request.

Processing process

Assuming that the implementation of remote communication is still netty4, after the decoder parses the packet into a Request object, NettyHandler's messageReceived method receives the object immediately, so the first step is channelRead for NettyServerHandler.

(1) channelRead of NettyServerHandler

Can be referred to dubbo Source Parsing (17) Remote Communication - Netty4 (3) NettyServerHandler

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    // See if you hit in the cache, if not, create NettyChannel and cache.
    NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
    try {
        // Accept message
        handler.received(channel, msg);
    } finally {
        // Clear from cache if channel is inactive or disconnected
        NettyChannel.removeChannelIfDisconnected(ctx.channel());
    }
}

NettyServerHandler is a service-side channel processing implementation class based on netty4 implementation, which is used to receive requests, followed by AbstractPeer's receive.

(2) received by AbstractPeer

Can be referred to dubbo Source Parsing (9) Remote Communication - Transport Layer (1) AbstractPeer

public void received(Channel ch, Object msg) throws RemotingException {
    // If the channel is closed, return directly
    if (closed) {
        return;
    }
    handler.received(ch, msg);
}

This method is relatively simple, and the AbstractPeer class has been mentioned before as a decorative role in decoration mode, just maintaining the closing and closing completion states of the channel.Then to receive of MultiMessageHandler

(3) received by MultiMessageHandler

Can be referred to dubbo Source Parsing (9) Remote Communication - Transport Layer (8) MultiMessageHandler

public void received(Channel channel, Object message) throws RemotingException {
    // If the message is of type MultiMessage, that is, multiple message type
    if (message instanceof MultiMessage) {
        // Force conversion to MultiMessage
        MultiMessage list = (MultiMessage) message;
        // Send individual messages
        for (Object obj : list) {
            handler.received(channel, obj);
        }
    } else {
        // Direct Send
        handler.received(channel, message);
    }
}

This method is also simple, that is, for the processing of multiple messages.

(4) received by Heartbeat Handler

Can be referred to dubbo Source Resolution (10) Remote Communication - Exchange Layer (20) Heartbeat Handler.This involves dealing with heart-beat events.If it's not a heartbeat request, then go to receive on AllChannelHandler.

(5) received by AllChannelHandler

Can be referred to dubbo Source Parsing (9) Remote Communication - Transport Layer (11) AllChannelHandler.This class handles connections, disconnections, catching exceptions, and distributing all messages received to the thread pool.So the received method here is to distribute the request to the thread pool and let the thread pool execute the request.

Remember when I mentioned the Dispatcher interface in the previous article, it's a thread dispatcher.There are five implementations:

Dispatcher Implementation Class Corresponding handler purpose
AllDispatcher AllChannelHandler All messages are dispatched to the thread pool, including requests, responses, connection events, disconnection events, and so on
ConnectionOrderedDispatcher ConnectionOrderedChannelHandler On IO threads, connect and disconnect events are queued, executed sequentially one by one, and other messages are dispatched to the thread pool
DirectDispatcher nothing No messages are dispatched to the thread pool, all executed directly on the IO thread
ExecutionDispatcher ExecutionChannelHandler Only request messages are dispatched to the thread pool, with no response.Other messages are executed on the IO thread
MessageOnlyDispatcher MessageOnlyChannelHandler Only request and response messages are dispatched to the thread pool, and all other messages are executed on the IO thread

The implementation classes of these Dispatcher s and the corresponding handlers can be found in dubbo Source Parsing (9) Remote Communication - Transport Layer View related implementations in.dubbo defaults all to a dispatch policy.So I'm talking about receiving by AllChannelHandler here.After sending the message to the thread pool, you can see that a ChannelEventRunnable entity is created first.Then the thread receives and executes the task.

(6) run of ChannelEventRunnable

ChannelEventRunnable implements the Runnable interface, which is mainly used to receive message events and perform different operations according to the type of events.Take a look at its run method:

public void run() {
    // If it is a received message
    if (state == ChannelState.RECEIVED) {
        try {
            // Call next receive directly
            handler.received(channel, message);
        } catch (Exception e) {
            logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
                    + ", message is " + message, e);
        }
    } else {
        switch (state) {
            //If connection event request
        case CONNECTED:
            try {
                // Execute Connection
                handler.connected(channel);
            } catch (Exception e) {
                logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
            }
            break;
            // If disconnected event request
        case DISCONNECTED:
            try {
                // Perform disconnection
                handler.disconnected(channel);
            } catch (Exception e) {
                logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
            }
            break;
            // If sending a message
        case SENT:
            try {
                // Execute Send Message
                handler.sent(channel, message);
            } catch (Exception e) {
                logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
                        + ", message is " + message, e);
            }
            break;
            // If it is an exception
        case CAUGHT:
            try {
                // Execute exception capture
                handler.caught(channel, exception);
            } catch (Exception e) {
                logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
                        + ", message is: " + message + ", exception is " + exception, e);
            }
            break;
        default:
            logger.warn("unknown state: " + state + ", message is " + message);
        }
    }

}

You can see that messages are grouped into several categories because request and response messages appear significantly more frequently than other types of messages, which are RECEIVED. Processing them separately first will execute different logic depending on the type of message. Here we mainly see that if the state is RECEIVED, then the next receive method will be executed.

(7) received by DecodeHandler

Can be referred to dubbo Source Parsing (9) Remote Communication - Transport Layer (7) DecodeHandler.You can see that received methods decode differently depending on the type of message.The purpose of DecodeHandler is to ensure that the request or response object can be decoded in the thread pool and, when decoded, distributed to received of HeaderExchangeHandler.

(8) received by HeaderExchangeHandler

public void received(Channel channel, Object message) throws RemotingException {
    // Set timestamp of received message
    channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
    // Get Channel
    final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
    try {
        // If the message is of Request type
        if (message instanceof Request) {
            // handle request.
            // Force to Request
            Request request = (Request) message;
            // If the request is an event heartbeat or read-only event
            if (request.isEvent()) {
                // Execution Event
                handlerEvent(channel, request);
            } else {
                // If this is a normal call request and requires a response
                if (request.isTwoWay()) {
                    // Processing Request
                    handleRequest(exchangeChannel, request);
                } else {
                    // If no response is required, proceed to the next step
                    handler.received(exchangeChannel, request.getData());
                }
            }
        } else if (message instanceof Response) {
            // Processing Responses
            handleResponse(channel, (Response) message);
        } else if (message instanceof String) {
            // If telnet related request
            if (isClientSide(channel)) {
                // If it is on the client side, throw the exception directly because telnet is not supported on the client side
                Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
                logger.error(e.getMessage(), e);
            } else {
                // If on the server side, execute the telnet command
                String echo = handler.telnet(channel, (String) message);
                if (echo != null && echo.length() > 0) {
                    channel.send(echo);
                }
            }
        } else {
            // If not, proceed to the next step
            handler.received(exchangeChannel, message);
        }
    } finally {
        // Remove closed or inactive channels
        HeaderExchangeChannel.removeChannelIfDisconnected(channel);
    }
}

In this method, messages are subdivided into event requests, normal call requests, responses, telnet command requests, and different logical calls are made for different message types.We're mainly looking at normal call requests here.See next step.

(9) handleRequest of HeaderExchangeHandler

void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
    // Create a Response instance
    Response res = new Response(req.getId(), req.getVersion());
    // If the request is destroyed
    if (req.isBroken()) {
        // Get the requested packet
        Object data = req.getData();

        String msg;
        // If the data is empty
        if (data == null) {
            //Message set to null
            msg = null;
            // If an exception has occurred before then, that is, the data is of type Throwable
        } else if (data instanceof Throwable) {
            // Response message returns exception information
            msg = StringUtils.toString((Throwable) data);
        } else {
            // Return Request Data
            msg = data.toString();
        }
        res.setErrorMessage("Fail to decode request due to: " + msg);
        // Set status code for error request
        res.setStatus(Response.BAD_REQUEST);

        // Send this message
        channel.send(res);
        return;
    }
    // find handler by message class.
    // Get the request data, also known as the RpcInvocation object
    Object msg = req.getData();
    try {
        // Continue the call down to return a future
        CompletionStage<Object> future = handler.reply(channel, msg);
        future.whenComplete((appResult, t) -> {
            try {
                if (t == null) {
                    //Set call result status to successful
                    res.setStatus(Response.OK);
                    // Put results in response
                    res.setResult(appResult);
                } else {
                    // Set the result status code to a service error if there is an exception to the service call
                    res.setStatus(Response.SERVICE_ERROR);
                    // Put error message in response
                    res.setErrorMessage(StringUtils.toString(t));
                }
                // Send this response
                channel.send(res);
            } catch (RemotingException e) {
                logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
            } finally {
                // HeaderExchangeChannel.removeChannelIfDisconnected(channel);
            }
        });
    } catch (Throwable e) {
        // If an exception is thrown during execution, it is also a service exception
        res.setStatus(Response.SERVICE_ERROR);
        res.setErrorMessage(StringUtils.toString(e));
        channel.send(res);
    }
}

This method handles normal call requests, mainly in the case of normal and abnormal calls, and adds a status code, then sends the results of execution to the client.Next step.

(10) reply of requestHandler instance of DubboProtocol

Here I use the dubbo protocol by default, so the reply method of the requestHandler of DubboProtocol is executed.Can be referred to dubbo Source Resolution (24) Remote Call - dubbo Protocol (3) DubboProtocol

public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {

    // Throw an exception if the request message does not belong to the session domain
    if (!(message instanceof Invocation)) {
        throw new RemotingException(channel, "Unsupported request: "
                + (message == null ? null : (message.getClass().getName() + ": " + message))
                + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
    }

    //Forced Type Conversion
    Invocation inv = (Invocation) message;
    // Get exposed service invoker
    Invoker<?> invoker = getInvoker(channel, inv);
    // need to consider backward-compatibility if it's a callback
    // If it is a callback service
    if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
        // Get Method Definition
        String methodsStr = invoker.getUrl().getParameters().get("methods");
        boolean hasMethod = false;
        // If there is only one method definition
        if (methodsStr == null || !methodsStr.contains(",")) {
            // Set whether there is a consistent method definition flag in the session domain
            hasMethod = inv.getMethodName().equals(methodsStr);
        } else {
            // Separate different methods
            String[] methods = methodsStr.split(",");
            // If more than one method is used, then the query is split and traversed and found, then set to true
            for (String method : methods) {
                if (inv.getMethodName().equals(method)) {
                    hasMethod = true;
                    break;
                }
            }
        }
        // If not, print the alert log
        if (!hasMethod) {
            logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
                    + " not found in callback service interface ,invoke will be ignored."
                    + " please update the api interface. url is:"
                    + invoker.getUrl()) + " ,invocation is :" + inv);
            return null;
        }
    }
    // Set Remote Address
    RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
    // Call Next Call Chain
    Result result = invoker.invoke(inv);
    //  Return CompletableFuture<Result>
    return result.completionFuture().thenApply(Function.identity());
}

Some of the changes to the above code are the latest, adding CompletableFuture, which I'll cover in a subsequent asynchronous transformation.The main concern here is that invoke call chains are starting to execute as clients request.

(11) CallbackRegistrationInvoker invoke of ProtocolFilterWrapper

Can be referred to directly "dubbo Source Resolution (46) Request Sending Process at Consumer Side" (6) invoke of the internal class CallbackRegistrationInvoker of ProtocolFilterWrapper.

(12) invoke method of invoker instance in buildInvokerChain method of ProtocolFilterWrapper.

Can be referred to directly "dubbo Source Resolution (46) Request Sending Process at Consumer Side" (7) invoke method of invoker instance in buildInvokerChain method of ProtocolFilterWrapper.

(13) invoke of EchoFilter

Can be referred to dubbo Source Resolution (20) Remote Call - Filter (8) EchoFilter

public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
    // If the method invoked is the echo test method, the result is returned directly, otherwise the next call chain is invoked
    if (inv.getMethodName().equals($ECHO) && inv.getArguments() != null && inv.getArguments().length == 1) {
        // Create a default syncRpcResult return
        return AsyncRpcResult.newDefaultAsyncResult(inv.getArguments()[0], inv);
    }
    return invoker.invoke(inv);
}

The filter intercepts calls to echo tests, and the only difference between the above source and the one in the connection is to change to AsyncRpcResult.

(14) invoke of ClassLoaderFilter

Can be referred to dubbo Source Resolution (20) Remote Call - Filter (3) ClassLoaderFilter, which is used to switch class loaders.

(15) invoke of GenericFilter

Can be referred to dubbo Source Resolution (20) Remote Call - Filter GenericFilter.

public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
    // If it is a generalization call
    if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
            && inv.getArguments() != null
            && inv.getArguments().length == 3
            && !GenericService.class.isAssignableFrom(invoker.getInterface())) {
        // Get the requested name
        String name = ((String) inv.getArguments()[0]).trim();
        // Get Request Parameter Type
        String[] types = (String[]) inv.getArguments()[1];
        // Get Request Parameters
        Object[] args = (Object[]) inv.getArguments()[2];
        try {
            // Getting Method
            Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
            // Obtain the parameter type of this method
            Class<?>[] params = method.getParameterTypes();
            if (args == null) {
                args = new Object[params.length];
            }
            // Get added value
            String generic = inv.getAttachment(GENERIC_KEY);

            if (StringUtils.isBlank(generic)) {
                generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
            }

            // If the add-on is empty, the add-on carried in the context of use
            if (StringUtils.isEmpty(generic)
                    || ProtocolUtils.isDefaultGenericSerialization(generic)) {
                // Direct type conversion
                args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
            } else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                for (int i = 0; i < args.length; i++) {
                    if (byte[].class == args[i].getClass()) {
                        try (UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i])) {
                            // Deserialize using nativejava
                            args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
                                    .getExtension(GENERIC_SERIALIZATION_NATIVE_JAVA)
                                    .deserialize(null, is).readObject();
                        } catch (Exception e) {
                            throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e);
                        }
                    } else {
                        throw new RpcException(
                                "Generic serialization [" +
                                        GENERIC_SERIALIZATION_NATIVE_JAVA +
                                        "] only support message type " +
                                        byte[].class +
                                        " and your message type is " +
                                        args[i].getClass());
                    }
                }
            } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof JavaBeanDescriptor) {
                        // Deserialize using JavaBean
                        args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
                    } else {
                        throw new RpcException(
                                "Generic serialization [" +
                                        GENERIC_SERIALIZATION_BEAN +
                                        "] only support message type " +
                                        JavaBeanDescriptor.class.getName() +
                                        " and your message type is " +
                                        args[i].getClass().getName());
                    }
                }
            } else if (ProtocolUtils.isProtobufGenericSerialization(generic)) {
                // as proto3 only accept one protobuf parameter
                if (args.length == 1 && args[0] instanceof String) {
                    try (UnsafeByteArrayInputStream is =
                                 new UnsafeByteArrayInputStream(((String) args[0]).getBytes())) {
                        // Deserialization with protobuf-json
                        args[0] = ExtensionLoader.getExtensionLoader(Serialization.class)
                                .getExtension("" + GENERIC_SERIALIZATION_PROTOBUF)
                                .deserialize(null, is).readObject(method.getParameterTypes()[0]);
                    } catch (Exception e) {
                        throw new RpcException("Deserialize argument failed.", e);
                    }
                } else {
                    throw new RpcException(
                            "Generic serialization [" +
                                    GENERIC_SERIALIZATION_PROTOBUF +
                                    "] only support one" + String.class.getName() +
                                    " argument and your message size is " +
                                    args.length + " and type is" +
                                    args[0].getClass().getName());
                }
            }
            return invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
        } catch (NoSuchMethodException e) {
            throw new RpcException(e.getMessage(), e);
        } catch (ClassNotFoundException e) {
            throw new RpcException(e.getMessage(), e);
        }
    }
    return invoker.invoke(inv);
}

static class GenericListener implements Listener {

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation inv) {
        // If it is a generalization call
        if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
                && inv.getArguments() != null
                && inv.getArguments().length == 3
                && !GenericService.class.isAssignableFrom(invoker.getInterface())) {

            // Get the serialization method
            String generic = inv.getAttachment(GENERIC_KEY);
            // If empty, get the configuration in the session domain by default
            if (StringUtils.isBlank(generic)) {
                generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
            }

            // If the callback has an exception, set the exception directly
            if (appResponse.hasException() && !(appResponse.getException() instanceof GenericException)) {
                appResponse.setException(new GenericException(appResponse.getException()));
            }
            // If serialized as native java
            if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                try {
                    UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
                    // Serialization using native java form
                    ExtensionLoader.getExtensionLoader(Serialization.class).getExtension(GENERIC_SERIALIZATION_NATIVE_JAVA).serialize(null, os).writeObject(appResponse.getValue());
                    // Join Result
                    appResponse.setValue(os.toByteArray());
                } catch (IOException e) {
                    throw new RpcException(
                            "Generic serialization [" +
                                    GENERIC_SERIALIZATION_NATIVE_JAVA +
                                    "] serialize result failed.", e);
                }
            } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                // Serialization in JavaBean
                appResponse.setValue(JavaBeanSerializeUtil.serialize(appResponse.getValue(), JavaBeanAccessor.METHOD));
            } else if (ProtocolUtils.isProtobufGenericSerialization(generic)) {
                try {
                    UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
                    // Serialization with protobuf-json
                    ExtensionLoader.getExtensionLoader(Serialization.class)
                            .getExtension(GENERIC_SERIALIZATION_PROTOBUF)
                            .serialize(null, os).writeObject(appResponse.getValue());
                    appResponse.setValue(os.toString());
                } catch (IOException e) {
                    throw new RpcException("Generic serialization [" +
                            GENERIC_SERIALIZATION_PROTOBUF +
                            "] serialize result failed.", e);
                }
            } else {
                // Convert types directly and set values
                appResponse.setValue(PojoUtils.generalize(appResponse.getValue()));
            }
        }
    }

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

    }
}

In contrast to the code within the connection, the first is a change in the filter design, which I will mention in the asynchronous transformation, and the second is a new way to generalize serialization with protobuf-json.

(16) invoke of ContextFilter

Can be referred to dubbo Source Resolution (20) Remote Call - Filter (6) ContextFilter, the latest code is almost the same, in addition to the modifications to the design of the Filter, there is a new tag routing related logic, tag related section I will explain in subsequent articles, this class is mainly made to initialize rpc context.

(17) invoke of TraceFilter

Can be referred to dubbo Source Resolution (24) Remote Call - dubbo Protocol (13) TraceFilter, which is an enhanced function of channel tracking, will put the maximum number of calls and the number of calls now in the channel.Easy to use telnet to track service calls, etc.

(18) invoke of TimeoutFilter

Can be referred to dubbo Source Resolution (20) Remote Call - Filter (13) TimeoutFilter, which records alert logs when service calls time out.

(19) invoke of MonitorFilter

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // If monitoring is turned on
    if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
        // Set start monitoring time
        invocation.setAttachment(MONITOR_FILTER_START_TIME, String.valueOf(System.currentTimeMillis()));
        // Add 1 to simultaneous online quantity
        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)) {
            // Collect monitoring data and update monitoring data
            collect(invoker, invocation, result, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), false);
            // Simultaneous online monitoring minus 1
            getConcurrent(invoker, invocation).decrementAndGet(); // count down
        }
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
        if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
            // Collect monitoring data and update monitoring data
            collect(invoker, invocation, null, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), true);
            // Simultaneous online monitoring minus 1
            getConcurrent(invoker, invocation).decrementAndGet(); // count down
        }
    }

    private void collect(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
        try {
            // Get monitored URLs
            URL monitorUrl = invoker.getUrl().getUrlParameter(MONITOR_KEY);
            // Obtain Monitor instances from this url
            Monitor monitor = monitorFactory.getMonitor(monitorUrl);
            if (monitor == null) {
                return;
            }
            // Create a url for statistics
            URL statisticsURL = createStatisticsUrl(invoker, invocation, result, remoteHost, start, error);
            // Update and send information collected
            monitor.collect(statisticsURL);
        } catch (Throwable t) {
            logger.warn("Failed to monitor count service " + invoker.getUrl() + ", cause: " + t.getMessage(), t);
        }
    }

    private URL createStatisticsUrl(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
        // ---- service statistics ----
        // Time spent invoking services
        long elapsed = System.currentTimeMillis() - start; // invocation cost
        // Get simultaneous monitored quantities
        int concurrent = getConcurrent(invoker, invocation).get(); // current concurrent count
        String application = invoker.getUrl().getParameter(APPLICATION_KEY);
        // Get service name
        String service = invoker.getInterface().getName(); // service name
        // Get the invoked method name
        String method = RpcUtils.getMethodName(invocation); // method name
        // Get Groups
        String group = invoker.getUrl().getParameter(GROUP_KEY);
        // Get Version Number
        String version = invoker.getUrl().getParameter(VERSION_KEY);

        int localPort;
        String remoteKey, remoteValue;
        // If it's consumer-side monitoring
        if (CONSUMER_SIDE.equals(invoker.getUrl().getParameter(SIDE_KEY))) {
            // ---- for service consumer ----
            // Local port 0
            localPort = 0;
            // key is provider
            remoteKey = MonitorService.PROVIDER;
            // value is service ip
            remoteValue = invoker.getUrl().getAddress();
        } else {
            // ---- for service provider ----
            // Port is service port
            localPort = invoker.getUrl().getPort();
            // key is consumer
            remoteKey = MonitorService.CONSUMER;
            // value is a remote address
            remoteValue = remoteHost;
        }
        String input = "", output = "";
        if (invocation.getAttachment(INPUT_KEY) != null) {
            input = invocation.getAttachment(INPUT_KEY);
        }
        if (result != null && result.getAttachment(OUTPUT_KEY) != null) {
            output = result.getAttachment(OUTPUT_KEY);
        }

        // Return a url
        return new URL(COUNT_PROTOCOL, NetUtils.getLocalHost(), localPort, service + PATH_SEPARATOR + method, MonitorService.APPLICATION, application, MonitorService.INTERFACE, service, MonitorService.METHOD, method, remoteKey, remoteValue, error ? MonitorService.FAILURE : MonitorService.SUCCESS, "1", MonitorService.ELAPSED, String.valueOf(elapsed), MonitorService.CONCURRENT, String.valueOf(concurrent), INPUT_KEY, input, OUTPUT_KEY, output, GROUP_KEY, group, VERSION_KEY, version);
    }

}

In invoke, only the time when the monitoring started was recorded and the number of simultaneous monitoring was added 1. When the result is callback, the result data is collected and calculated, and the latest information is recorded and sent through the monitoring service.

(20) invoke of ExceptionFilter

Can be referred to dubbo Source Resolution (20) Remote Call - Filter (9) ExceptionFilter for exception handling.

(21) InvokerWrapper's invoke

Can be referred to dubbo Source Parsing (22) Remote Call - Protocol (5) InvokerWrapper.This class uses decoration mode, but does not achieve the actual functional enhancements.

(22) invoke of DelegateProviderMetaDataInvoker

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

This class also uses decoration mode, but it is an adapter class for invoker and configuration center, and there are no actual enhancements.

(23) invoke of AbstractProxyInvoker

Can be referred to dubbo Source Parsing (23) Remote Call - Proxy (2) AbstractProxyInvoker.However, the code has been updated. The latest code is posted below.

public Result invoke(Invocation invocation) throws RpcException {
    try {
        // Perform Next Step
        Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
        // Wrap the returned results in CompletableFuture
        CompletableFuture<Object> future = wrapWithFuture(value, invocation);
        // Create an AsyncRpcResult instance
        AsyncRpcResult asyncRpcResult = new AsyncRpcResult(invocation);
        future.whenComplete((obj, t) -> {
            AppResponse result = new AppResponse();
            // If an exception is thrown
            if (t != null) {
                // Is a CompletionException exception
                if (t instanceof CompletionException) {
                    // Set exception information
                    result.setException(t.getCause());
                } else {
                    // Set exception directly
                    result.setException(t);
                }
            } else {
                // If there are no exceptions, put the results into the asynchronous results
                result.setValue(obj);
            }
            // complete
            asyncRpcResult.complete(result);
        });
        return asyncRpcResult;
    } catch (InvocationTargetException e) {
        if (RpcContext.getContext().isAsyncStarted() && !RpcContext.getContext().stopAsync()) {
            logger.error("Provider async started, but got an exception from the original method, cannot write the exception back to consumer because an async result may have returned the new thread.", e);
        }
        return AsyncRpcResult.newDefaultAsyncResult(null, e.getTargetException(), invocation);
    } catch (Throwable e) {
        throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

The main reason for this is the code changes that occur with asynchronous modifications, which I'll cover in Asynchronous modifications.Now the main focus is on the next step.

(24) doInvoke of anonymous class in getInvoker method of JavassistProxyFactory

The default proxy implementation here is Javassist.Can be referred to dubbo Source Parsing (23) Remote Call - Proxy (6) JavassistProxyFactory.Wrapper is an abstract class, where invokeMethod is an abstract method.dubbo generates implementation classes for Wrapper at runtime through the Javassist framework and implements the invokeMethod method, which ultimately calls specific services based on the call information.In the case of DemoServiceImpl, Javassist generates the following proxy classes for it.

/** Wrapper0 Is generated at runtime and can be decompiled using Arthas */
public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;

    // Omit other methods

    public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
        DemoService demoService;
        try {
            // Type Conversion
            demoService = (DemoService)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        try {
            // Invokes the specified method based on the method name
            if ("sayHello".equals(string) && arrclass.length == 1) {
                return demoService.sayHello((String)arrobject[0]);
            }
        }
        catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString());
    }
}

Then the corresponding method is called directly.At this point, the method execution is complete.

Result Return

You can see that (8) the received of the HeaderExchangeHandler and (9) the handleRequest of the HeaderExchangeHandler have several calls to the channel.send method, which actively sends the execution result to the client when the result returns.Of course, the Result Response object will still be encoded when it is sent, and the encoding logic will not be explained here.

When the client receives this returned message, it decodes it, identifies it as a Response object, and dispatches it to the thread pool. This process is the same as the logic in which the server receives the call request. You can refer to the above analysis, except (8) when the Recived method of HeaderExchangeHandler executes the handleResponse method.

(9) handleResponse of HeaderExchangeHandler

static void handleResponse(Channel channel, Response response) throws RemotingException {
    // If the response is not null and is not a response to a heartbeat event, call received
    if (response != null && !response.isHeartbeat()) {
        DefaultFuture.received(channel, response);
    }
}

(10) received by DefaultFuture

Can be referred to dubbo Source Resolution (10) Remote Communication - Exchange Layer (7) DefaultFuture, but this class inherits CompletableFuture because of changes to asynchronization.

public static void received(Channel channel, Response response) {
    received(channel, response, false);
}

public static void received(Channel channel, Response response, boolean timeout) {
    try {
        // Remove the future of the request from the future collection, (response id corresponds to request id one to one)
        DefaultFuture future = FUTURES.remove(response.getId());
        if (future != null) {
            //Get Timeout
            Timeout t = future.timeoutCheckTask;
            // Cancel timeoutCheckTask if no timeout occurs
            if (!timeout) {
                // decrease Time
                t.cancel();
            }
            // Receive Response Results
            future.doReceived(response);
        } else {
            logger.warn("The timeout response finally returned at "
                    + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
                    + ", response " + response
                    + (channel == null ? "" : ", channel: " + channel.getLocalAddress()
                    + " -> " + channel.getRemoteAddress()));
        }
    } finally {
        // The collection of channels removes the channel corresponding to the request and represents the end of the request
        CHANNELS.remove(response.getId());
    }
}

This method mainly deals with timeouts and matches the corresponding requests and responses, that is, returns the future of the corresponding id s.

(11) doReceived of DefaultFuture

Can be referred to dubbo Source Resolution (10) Remote Communication - Exchange Layer (7) DefaultFuture, but because CompletableFuture was used, the method was completely rewritten.This part of the transformation will also be described in the asynchronous transformation.

private void doReceived(Response res) {
    // If the result is empty, throw an exception
    if (res == null) {
        throw new IllegalStateException("response cannot be null");
    }
    // If the status code of the result is ok
    if (res.getStatus() == Response.OK) {
        // Then the future call is complete
        this.complete(res.getResult());
    } else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
        // If timeout occurs, a timeout exception is returned
        this.completeExceptionally(new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()));
    } else {
        // Otherwise, return a RemotingException
        this.completeExceptionally(new RemotingException(channel, res.getErrorMessage()));
    }
}

The user thread then gets the result from the DefaultFuture instance.

Postnote

This article explains all the steps that the dubbo mediation server takes after receiving the request and how the server sends the result to the client. It is the latest code analysis.Because there are many processes involved, you can debug yourself to see the whole process step by step.You can see that there are many asynchronous transformations involved in this article, which is why I want to explain these processes before explaining them.Understanding these processes will lead to a better understanding of the implications for asynchronous transformation.The next article will explain the asynchronous transformation.

Posted by dstantdog3 on Sat, 08 Jun 2019 09:20:31 -0700