Dubbo source code analysis - the whole process of request processing by the Provider

Posted by Janus13 on Wed, 01 Dec 2021 15:03:55 +0100

preface:

Followed by the previous article. The above analyzes the whole process of the consumer sending the request, and finally sends the request to the Provider through NettyChannel. Of course, a series of processing has been carried out in the middle.

From the perspective of Provider, this paper discusses how the service Provider handles the request after receiving the request.

1. Code example

The Provider example is shown above

public class ProviderApplication {
    public static void main(String[] args) throws Exception {
        ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());
        service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
        service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        service.export();

        System.out.println("dubbo service started");
        new CountDownLatch(1).await();
    }
}

2. The whole process of service provider processing request

2.1 DecodeHandler

The service provider exposes a Netty server, and all requests will be processed by its Handler. Therefore, the service provider first parses the received messages through the DecodeHandler

public class DecodeHandler extends AbstractChannelHandlerDelegate {
 
    public void received(Channel channel, Object message) throws RemotingException {
        if (message instanceof Decodeable) {
            decode(message);
        }

        // Parse the Request message into a Request object
        if (message instanceof Request) {
            decode(((Request) message).getData());
        }

        if (message instanceof Response) {
            decode(((Response) message).getResult());
        }

        // Continue to be handled by HeaderExchangeHandler
        handler.received(channel, message);
    }
}

The encapsulation relationship between DecodeHandler and HeaderExchangeHandler has been determined in HeaderExchanger.

public class HeaderExchanger implements Exchanger {
    public static final String NAME = "header";
    @Override
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }
    @Override
    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }
}

2.2 HeaderExchangeHandler

public class HeaderExchangeHandler implements ChannelHandlerDelegate {
	public void received(Channel channel, Object message) throws RemotingException {
        final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
        if (message instanceof Request) {
            // handle request.
            Request request = (Request) message;
            if (request.isEvent()) {
                handlerEvent(channel, request);
            } else {
                if (request.isTwoWay()) {
                    // Continue processing
                    handleRequest(exchangeChannel, request);
                } else {
                    handler.received(exchangeChannel, request.getData());
                }
            }
        } ...
    }
    
    void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
        Response res = new Response(req.getId(), req.getVersion());
        ...
        Object msg = req.getData();
        try {
            // What is this Handler? Refer to 2.2.1 for details
            CompletionStage<Object> future = handler.reply(channel, msg);
            // After execution, add the result to the Response
            future.whenComplete((appResult, t) -> {
                try {
                    if (t == null) {
                        res.setStatus(Response.OK);
                        res.setResult(appResult);
                    } else {
                        res.setStatus(Response.SERVICE_ERROR);
                        res.setErrorMessage(StringUtils.toString(t));
                    }
                    channel.send(res);
                } catch (RemotingException e) {
                    logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
                }
            });
        } catch (Throwable e) {
            res.setStatus(Response.SERVICE_ERROR);
            res.setErrorMessage(StringUtils.toString(e));
            channel.send(res);
        }
    }
}

2.2.1 adding headerexchangehandler.handler property

There is nothing unexplained in the world of code. All attributes are added somewhere. So what is the HeaderExchangeHandler.handler property?

In the DubboProtocol.createServer() method:

public class DubboProtocol extends AbstractProtocol {
    private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {...}
	private ProtocolServer createServer(URL url) {
        ...

        ExchangeServer server;
        try {
            // Bind the current requestHandler to the url
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }

        str = url.getParameter(CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }

        return new DubboProtocolServer(server);
    }
}

requestHandler is subsequently bound to HeaderExchanger.bind() or connect() methods.

Therefore, DubboProtocol.requestHandler is used to process the request body

2.3 DubboProtocol.requestHandler.reply()

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

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

        ...
        Invocation inv = (Invocation) message;
        // The key point here is to obtain the specific Invoker in DubboExporter through the request parameters in Invocation
        // See 2.3.1 for details 
        Invoker<?> invoker = getInvoker(channel, inv);
        ...
        RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
        Result result = invoker.invoke(inv);
        return result.thenApply(Function.identity());
    }
}

2.3.1 obtain specific DubboInvoker processing information through the request body

public class DubboProtocol extends AbstractProtocol {
	Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {
        boolean isCallBackServiceInvoke = false;
        boolean isStubServiceInvoke = false;
        int port = channel.getLocalAddress().getPort();
        String path = (String) inv.getObjectAttachments().get(PATH_KEY);

        // if it's callback service on client side
        isStubServiceInvoke = Boolean.TRUE.toString().equals(inv.getObjectAttachments().get(STUB_EVENT_KEY));
        if (isStubServiceInvoke) {
            port = channel.getRemoteAddress().getPort();
        }

        //callback
        isCallBackServiceInvoke = isClientSide(channel) && !isStubServiceInvoke;
        if (isCallBackServiceInvoke) {
            path += "." + inv.getObjectAttachments().get(CALLBACK_SERVICE_KEY);
            inv.getObjectAttachments().put(IS_CALLBACK_SERVICE_INVOKE, Boolean.TRUE.toString());
        }

        String serviceKey = serviceKey(
                port,
                path,
                (String) inv.getObjectAttachments().get(VERSION_KEY),
                (String) inv.getObjectAttachments().get(GROUP_KEY)
        );
        DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);

        if (exporter == null) {
            throw new RemotingException(channel, "Not found exported service: " + serviceKey + " in " + exporterMap.keySet() + ", may be version or group mismatch " +
                    ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress() + ", message:" + getInvocationWithoutData(inv));
        }

        return exporter.getInvoker();
    }
}

So far, we have returned to the main battlefield of the provider. The processing of requests starts from Filter, then proxy (AbstractProxyInvoker), and finally to the specific implementation class

2.4 various Filter processing

EchoFilter, ClassLoaderFilter, GenericFilter, etc. are not the focus of this article. The details of Filter processing will be introduced later

2.5 AbstractProxyInvoker.doInvoke()

It is an abstract class. When the Provider is started, we will create an Invoker corresponding to the implementation class, as shown below

public class JavassistProxyFactory extends AbstractProxyFactory {
	@Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // Wrapper is also a dynamically generated implementation class,
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }
}

After the request enters, the obtained Invoker is the current AbstractProxyInvoker, which is used to process the client request.

Therefore, we finally enter the Wrapper.invokeMethod() method, and the Wrapper is a dynamically generated proxy class. We can recall the specific contents of the Wrapper before

2.6 Wrapper0.invokeMethod() process request

public class Wrapper0
  extends Wrapper
  implements ClassGenerator.DC
{
public Object invokeMethod(Object paramObject, String paramString, Class[] paramArrayOfClass, Object[] paramArrayOfObject)
    throws InvocationTargetException
  {
    DemoServiceImpl localDemoServiceImpl;
    try
    {
      localDemoServiceImpl = (DemoServiceImpl)paramObject;
    }
    catch (Throwable localThrowable1)
    {
      throw new IllegalArgumentException(localThrowable1);
    }
    try
    {
    // Here, we call localDemoServiceImpl, which is the class referenced by setRef() method when creating ServiceConfig. In this case, it is new DemoServiceImpl()
      if ((!"sayHello".equals(paramString)) || (paramArrayOfClass.length == 1)) {
        return localDemoServiceImpl.sayHello((String)paramArrayOfObject[0]);
      }
      if ((!"sayHelloAsync".equals(paramString)) || (paramArrayOfClass.length == 1)) {
        return localDemoServiceImpl.sayHelloAsync((String)paramArrayOfObject[0]);
      }
    }
    catch (Throwable localThrowable2)
    {
      throw new InvocationTargetException(localThrowable2);
    }
    throw new NoSuchMethodException("Not found method \"" + paramString + "\" in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
  }
}

Therefore, it is clear that the final call to the specific implementation class through the Wrapper

Similarly, the sequence diagram is used to show the whole process

 

Topics: Java Database MySQL