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.