References:

  • Odin — Dubbo’s service exposure process
  • Dubbo exposure service process parsing

Note: source version is 2.6.5

conclusion

Main:

From a business code perspective: Encapsulate URL -> local exposure -> Remote exposure

From the object construction and migration perspective: concrete implementation class -> Invoker -> Exporter

General process:

1. When the Spring container refresh is complete, the ContextRefreshEvent event is published and the ServiceBean executes the doExport method to start the service exposure.

2. Obtain the URL of the registry and register all protocols in the registry

3. Encapsulate the URL object of the service interface based on various parameters

4. If Remote is not configured for scope, local exposure is performed. Change the protocol to InjVM, then call the proxy factory to generate the Invoker, which encapsulates the invocation logic of the actual implementation class, and finally export the Exporter through the specific protocol

5. If Local is not configured for the scope, remote exposure is performed. It is also through the proxy factory that the concrete implementation class is encapsulated, the Invoker is generated, and then the export is maintained. However, there are two types of export here.

5.1 The first case: Export of Registry type; This refers to registering the service to a registry. In the case of ZK, a ZK connection is created, and then a node is created under the specified directory. The node information includes the IP address, port, service interface, and so on of the service provider

5.2 The second case: export of non-Registry type; For example, the Dubbo protocol. The process is to create a Netty server, open it and listen on the port.

The URL that

Before we start analyzing the dubbo service exposure process, let’s talk briefly about URLS, because Dubbo uses URLS as convention parameter types, that is, passing data through URL objects.

URL refers to the unified resource locator, and the standard format is as follows:

protocol://username:password@host:port/path? key=value&key=valueCopy the code
  • Protocol: Refers to the various protocols used in Dubbo, such as Dubbo, HTTP, and Registry
  • Path: fully qualified name of the interface
  • Parameter: key-value pair

Configure the parsing

When we configure dubbo using an XML file, we register various tag parsers with the Spring container through the DubboNamespaceHandler, which is used to read tag contents and save configuration information.

<dubbo:service interface="cn.com.service.RegionService" ref="regionService" />
Copy the code
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init(a) {
        registerBeanDefinitionParser("application".new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module".new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry".new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor".new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider".new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer".new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol".new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service".new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference".new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation".newAnnotationBeanDefinitionParser()); }}Copy the code

As you can see from the code above, the parser for the tag dubbo: Service is ServiceBean

Service exposure process

Exposure time

After the Spring container refresh is complete, the ContextRefreshedEvent event is published, and ServiceBean executes the onApplicationEvent method, which calls the export method of the ServiceConfig parent class to begin exposing the service.

doExportUrls

The doExport method is a long one that goes through various configuration checks and populates the properties. Next, the doExportUrls method is called. This method first calls loadRegistries to get the urls of all registries, then iterates through the protocols, registering each with the registry. For example, if both the Dubbo and Hessian protocols are supported, the service needs to be exposed to the registry using both protocols

private void doExportUrls(a) {
    List<URL> registryURLs = loadRegistries(true);
    for(ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); }}Copy the code

loadRegistries

Get the URL for the registry, as shown below

Registry: / / 127.0.0.1:2181 / com. Alibaba. Dubbo. Registry. RegistryService? Application = dubbo - provider&application. Version = 1.0 & dubbo = 2.5.3 & environment = product&organization = china&owner = cheng. Xi&pid =2939&registry=zookeeper&timestamp=1488898049284Copy the code

doExportUrlsFor1Protocol

This method basically does three things:

  1. First, concatenate the service URL object
  2. Local exposure service
  3. Remote exposure service
Concatenation service URL

Because the code is too long, I will not post it. It is to specify the protocol, service interface and method of the service, and finally splice the service URL object, as shown below

URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
Copy the code

dubbo://10.0.0.83:20886/cn.com.bluemoon.asset.dubbo.IDubboCommonService?anyhost=true&application=bm-officeAuto-asset&com plexity=0&dubbo=2.8.4.BM-SNAPSHOT&generic=false&interface=cn.com.bluemoon.asset.dubbo.IDubboCommonService&methods=assetP ushSMS&pid=516&retries=0&sendmsg=false&side=provider&timeout=60000&timestamp=1608039139999Copy the code
Local exposure

After the service URL stitching is completed, the exposed service is followed, which is mainly divided into local exposure and remote exposure. In both cases, the Invoker is generated using a proxy factory that encapsulates the invocation logic of the implementation class. Then, according to different agreements, export the corresponding Exporter and provide it to the consumer.

// This comment is copied directly from the reference...
private void exportLocal(URL url) {
	// If the protocol is injvm, no further execution is required
    if(! Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {/ / this time into a local exposure of url:injvm://127.0.0.1/dubbo.com mon. Hello. Service. HelloService? Anyhost = true &
        / / application = dubbo - provider&application. Version = 1.0 & dubbo = 2.5.3 & environment = product &
        //interface=dubbo.common.hello.service.HelloService&methods=sayHello&
        //organization=china&owner=cheng.xi&pid=720&side=provider&timestamp=1489716708276
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(NetUtils.LOCALHOST)
                .setPort(0);
        
        // Get Invoker first
        // Export to Exporter and cache
        // The proxyFactory is actually JavassistProxyFactory
        // Details about obtaining Invoke and EXPORTER are resolved in the process below, which is not explained when the process is exposed locally.Exporter<? > exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry"); }}Copy the code

For local exposure, the protocol is changed to InjVM, IP is 127.0.0.1, and port is 0.

Remote exposed

Create the Invoker

In the above code, we can see that Invoker is created, followed by the export by contract.

Invoker<? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);Copy the code

Currently, there are two ways to getInvoker: JavassistProxyFactory#getInvoker and JdkProxyFactory#getInvoker. JavassistProxyFactory is used by default.

public class JavassistProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker
       
         invoker, Class
        [] interfaces)
        {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // The TODO Wrapper class does not handle class names with $correctly
        // If the class starts with $, use the interface type
        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 {
                
                // Wrapper. invokeMethod calls the implementation method of the implementation class
                returnwrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); }}; }}Copy the code
public class JdkProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker
       
         invoker, Class
        [] interfaces)
        {
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                                          interfaces, new InvokerInvocationHandler(invoker));
    }

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, Class
       [] parameterTypes, Object[] arguments) throws Throwable {
                Method method = proxy.getClass().getMethod(methodName, parameterTypes);
                returnmethod.invoke(proxy, arguments); }}; }}Copy the code

As you can see, in fact, the two implementation methods are much the same, the difference is that JavassistProxyFactory will make a wrapper on the concrete implementation class, and finally call the target method of the concrete implementation class. Therefore, the Invoker role is to call the implementation methods of the concrete implementation class.

Export the Exporter

An Invoker export to a friend can be classified into two types: a Registry type Invoker and a non-Registry type Invoker.

Method description:

Rpccontext.getcontext ().setremoteAddress (); rpcContext.getContext (). <br> * 2.export () must be idempotent, that is, exposing the same URL Invoker twice is no different from exposing it once. <br> * 3. export() the Invoker passed in is implemented by the framework and passed in without the protocol being concerned. <br> * *@param<T> Type of service *@paramAn executable of the Invoker service@returnA reference to the exporter exposure service to cancel exposure *@throwsRpcException is thrown when exposing a service error, such as a port occupied */
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

Copy the code

Method entry:

Exporter<? > exporter = protocol.export(invoker);Copy the code
Export of type Registry

com.alibaba.dubbo.registry.integration.RegistryProtocol#export

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // export invoker
    	// doLocalExport: Here is the exposure of implementing other protocols, such as Dubbo
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    	
    	//registry provider
    	// Get the Registry instance object according to the Invoker URL
        final Registry registry = getRegistry(originInvoker);
    	// Get the URL to register with the registry, the service interface URL
    	final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
        // Call the remote registry's register method to register the service
    	registry.register(registedProviderUrl);
    
        // Subscribe to override data
        Subscribed by FIXME providers can affect scenarios in which the same JVM, i.e., services are exposed and referenced, because subscribed information is overwritten by using cached keys with service names.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
         // The provider subscribes to the registry for coverage configuration for all registration services
    	// When the registry is registered with an override configuration for this service, push a message to the provider to re-expose the service, which is done by the administration page.
    	registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        
    	// Ensure that each export returns a new export instance
    	// Return the exposed Exporter to the upper ServiceConfig for caching, so that it can be unexposed later.
        return new Exporter<T>() {
            public Invoker<T> getInvoker(a) {
                return exporter.getInvoker();
            }
            public void unexport(a) {
            	try {
            		exporter.unexport();
            	} catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
                try {
                	registry.unregister(registedProviderUrl);
                } catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
                try {
                	overrideListeners.remove(overrideSubscribeUrl);
                	registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
                } catch(Throwable t) { logger.warn(t.getMessage(), t); }}}; }Copy the code

This involves a lot of methods, specific analysis can see the resources.

  • getRegistry(originInvoker)

    • The specific Registry instance is obtained according to the Dubbo SPI mechanism. If it is the ZooKeeper protocol, ZookeeperRegistry is returned. This object holds the zkClient connected to ZooKeeper

    • /** * Get the registry instance * based on the invoker address@param originInvoker
          * @return* /
         private Registry getRegistry(finalInvoker<? > originInvoker){
             URL registryUrl = originInvoker.getUrl();
             if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
                 String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
                 registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
             }
             return registryFactory.getRegistry(registryUrl);
         }
      Copy the code
  • getRegistedProviderUrl(originInvoker)

    • Gets the URL to register with the registry, the provider’s service interface URL

    • final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
      // The resulting URL is:
      / / dubbo://192.168.1.100:20880/dubbo.com mon. Hello. Service. HelloService?
      / / anyhost = true&application = dubbo - provider&application. Version = 1.0 & dubbo = 2.5.3 & environment = product &
      //interface=dubbo.common.hello.service.HelloService&methods=sayHello&
      //organization=china&owner=cheng.xi&pid=9457&side=provider&timestamp=1489807681627
      Copy the code
  • registry.register(registedProviderUrl)

    • Call the register method of the remote registry to register the service

    • This method invokes doRegistry and, if it is a ZK registry, creates a temporary node by default

    • com.alibaba.dubbo.registry.support.FailbackRegistry#register

    • com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister

    • protected void doRegister(URL url) {
          try {
              // zkClient is the ZkClientZookeeperClient generated when we call the construct above
              // Start registration, that is, creating a node in Zookeeper
              // toUrlPath gets the following path:
              / / / dubbo/dubbo.com mon. Hello. Service. HelloService/will/dubbo % % 2 f % 2 f192. 3 a 168.1.100%3 a20880%2 f
              //dubbo.common.hello.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26
              / / application. Version % 3 d1. 0% 26 dubbo % 3 d2. The 5.3% % 26 environment 3 dproduct % 26 interface % 3 d
              //dubbo.common.hello.service.HelloService%26methods%3DsayHello%26
              //organization%3Dchina%26owner%3Dcheng.xi%26pid%3D8920%26side%3Dprovider%26timestamp%3D1489828029449
              // The default created node is a temporary node
              zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
          } catch (Throwable e) { }
      }
      Copy the code
    • Node information on the ZK after successful registration

    • /dubbo dubbo.common.hello.service.HelloService providers /dubbo/dubbo.common.hello.service.HelloService/providers/ Dubbo%3A%2F%2F192.168.1.100%3A20880%2Fdubbo.com mon. Hello. Service. HelloService % 3 f Anyhost % 3 dtrue % 26 application % 3 ddubbo - provider % 26 application. The version % 3 d1. 0% 26 dubbo % 3 d2. The 5.3% % 26 environment 3 dproduct % 26 interface%3Ddubbo.common.hello.service.HelloService%26methods%3DsayHello%26 organization%3Dchina%26owner%3Dcheng.xi%26pid%3D13239%26side%3D provider%26timestamp%3D1489829293525Copy the code
Export of a non-Registry type

The main step is to open the local port and listen.

  • doLocalExport(invoker)

    • private <T> ExporterChangeableWrapper<T>  doLocalExport(final Invoker<T> originInvoker){
          // The original invoker url:
          / / registry: / / 127.0.0.1:2181 / com. Alibaba. Dubbo. Registry. RegistryService?
          / / application = dubbo - provider&application. Version = 1.0 & dubbo = 2.5.3
          / / & environment = product&export = dubbo % % 2 f % 2 f10. 3 a 42.0.1%3 a20880%2 f
          //dubbo.common.hello.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26
          / / application. Version % 3 d1. 0% 26 dubbo % 3 d2. The 5.3% % 26 environment 3 dproduct % 26
          //interface%3Ddubbo.common.hello.service.HelloService%26methods%3DsayHello%26
          //organization%3Dchina%26owner%3Dcheng.xi%26pid%3D7876%26side%3Dprovider%26timestamp%3D1489057305001&
          //organization=china&owner=cheng.xi&pid=7876&registry=zookeeper&timestamp=1489057304900
          
          // Get the key from the original invoker: (read the export parameter in the original URL)
          / / dubbo://10.42.0.1:20880/dubbo.com mon. Hello. Service. HelloService? Anyhost = true&application = dubbo - provider &
          / / application.version=1.0&dubbo=2.5.3&environment=product&interface=dubbo.com mon. Hello. Service. HelloService &
          //methods=sayHello&organization=china&owner=cheng.xi&pid=7876&side=provider&timestamp=1489057305001
          String key = getCacheKey(originInvoker);
          ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
          if (exporter == null) {
              synchronized (bounds) {
                  exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
                  if (exporter == null) {
                      // Get an Invoker proxy that contains the original Invoker
                      finalInvoker<? > invokerDelegete =new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                      // Call the export method in the code, which selects the specific implementation class according to the protocol name
                      // Here we need to call the DubboProtocol export method
                      // The invoker that exports using a specific protocol is a proxy invoker
                      / / export finished, return a new ExporterChangeableWrapper instance
                      exporter = newExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker); bounds.put(key, exporter); }}}return (ExporterChangeableWrapper<T>) exporter;
      }
      Copy the code
  • Export method of DubboProtocol

    • The resulting exporter is placed in the cache with a key consisting of a service name, port, and IP address. The client sends a request, finds the Exporter based on key, and then finds invoker to invoke.

    • public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
          / / dubbo://10.42.0.1:20880/dubbo.com mon. Hello. Service. HelloService?
          //anyhost=true&application=dubbo-provider&
          / / application. Version = 1.0 & dubbo = 2.5.3 & environment = product &
          //interface=dubbo.common.hello.service.HelloService&
          //methods=sayHello&organization=china&owner=cheng.xi&
          //pid=7876&side=provider&timestamp=1489057305001
          URL url = invoker.getUrl();
      
          // export service.
          // The key consists of serviceName, port, version, and group
          // When a NIO client makes a remote call, the NIO server uses this key to decide which Exporter to invoke, which is the invoked Invoker.
          //dubbo.common.hello.service.HelloService:20880
          String key = serviceKey(url);
          
          // Convert Invoker to Exporter
          // Create a new instance
          // Do nothing, just do some assignment
          // My friend here includes invoker
          DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
          
          // Cache the service to expose, the key is generated above
          exporterMap.put(key, exporter);
      
          //export an stub service for dispaching event
          // Whether local stubs are supported
          // After remote service, the client usually only has the interface left, and the implementation is all on the server side,
          // However, the provider sometimes wants to perform some of the logic on the client side, such as doing ThreadLocal caching,
          // To validate parameters in advance, forge fault-tolerant data after a failed call, etc.
          // The client generates the Proxy and passes it to the Stub via the constructor.
          // The Stub then exposes the group to the user and decides whether to call the Proxy or not.
          Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
          Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
          if(isStubSupportEvent && ! isCallbackservice){ String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
              } else{ stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); }}// Build the NIO framework Server based on the URL binding IP and port
          openServer(url);
      
          return exporter;
      }
      Copy the code
  • OpenServer (url)

    • Create a NIO server and listen on the specified port. (There is also a lot of code involved here, see Resources for details)

    • private void openServer(URL url) {
          // find server.
          / / key is IP: PORT
          / / 192.168.110.197:20880
          String key = url.getAddress();
          // The client can also expose a service that only the server can call.
          boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);
          if (isServer) {
              ExchangeServer server = serverMap.get(key);
              // In the same JVM, services of the same protocol share the same Server,
              // Create the server when the first service is exposed,
              // All services of the same protocol will use the same server
              if (server == null) {
                  serverMap.put(key, createServer(url));
              } else {
                  // Services with the same protocol that later expose the service use the same Server that was created the first time
                  // The server supports reset and can be used with the Override function
                  // Changes in accept, idleTimeout, Threads, heartbeat parameters will cause changes in Server properties
                  // You need to reset the Serverserver.reset(url); }}}Copy the code

At this point, Dubbo has basically gone through the process of service exposure, which is very complicated and can only be digested slowly.