Source code analysis Dubbo service consumer startup process

Keywords: Programming Dubbo Attribute Spring jvm

Through the detailed explanation of the previous article, we know that the Dubbo service consumer label dubbo:reference will eventually create a corresponding ReferenceBean instance in the Spring container, and ReferenceBean implements the Spring life cycle interface: InitializingBean. Next, we should look at the implementation of its afterpropertieset method.

1. Source code analysis referencebean × afterpropertieset

ReferenceBean#afterPropertiesSet

if (getConsumer() == null) {
            Map<string, consumerconfig> consumerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
                 ConsumerConfig.class, false, false);
            if (consumerConfigMap != null &amp;&amp; consumerConfigMap.size() &gt; 0) {
                ConsumerConfig consumerConfig = null;
                for (ConsumerConfig config : consumerConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault().booleanValue()) {
                        if (consumerConfig != null) {
                            throw new IllegalStateException("Duplicate consumer configs: " + consumerConfig + " and " + config);
                        }
                        consumerConfig = config;
                    }
                }
                if (consumerConfig != null) {
                    setConsumer(consumerConfig);
                }
            }
        }

Step 1: if the consumer is empty, the dubbo:reference tag does not set the consumer property. If there is a dubbo:consumer tag, the instance will be taken. If there are multiple dubbo:consumer configurations, the consumer must be set. Otherwise, an exception will be thrown: "Duplicate consumer configs".

Step 2: if the application is empty, an attempt is made to query the dubbo:application instance from BeanFactory. If there are multiple dubbo:application configurations, an exception is thrown: "duplicate application configurations".

Step3: if the module of ServiceBean is empty, try to query dubbo:module instance from BeanFactory. If there are multiple Dubbo: modules, throw an exception: "Duplicate module configs:".

Step 4: try to load all registries from BeanFactory. Note that the list < registryconfig & gt; registries property of ServiceBean is a collection of registries.

Step 5: try to load a monitoring center from beanfactory and fill in the MonitorConfig monitor property of ServiceBean. If there are multiple dubbo:monitor configurations, throw "Duplicate monitor configs:".

ReferenceBean#afterPropertiesSet

Boolean b = isInit();
if (b == null &amp;&amp; getConsumer() != null) {
      b = getConsumer().isInit();
}
if (b != null &amp;&amp; b.booleanValue()) {
      getObject();
}

Step 6: determine whether to initialize. If it is initialization, call getObject() method, which is also defined by FactoryBean. ReferenceBean is the instance project of the class (interface) that dubbo:reference actually refers to. getObject sends back the instance of interface, not ReferenceBean instance.

1.1 source code analysis getObject()

public Object getObject() throws Exception {
        return get();
}

Referencebean × getobject() method directly calls the get method of its parent class, and the get method internally calls init() method for initialization

1.2 referenceconfig? Init method for source code analysis

ReferenceConfig#init

if (initialized) {
     return;
 }
initialized = true;
if (interfaceName == null || interfaceName.length() == 0) {
      throw new IllegalStateException("<dubbo:reference interface="\&quot;\&quot;" /> interface not allow null!");
}

Step 1: if it has been initialized, it will return directly. If the interfaceName is empty, an exception will be thrown.

Referenceconfig? Init call referenceconfig? Checkdefault

private void checkDefault() {
        if (consumer == null) {
            consumer = new ConsumerConfig();
        }
        appendProperties(consumer);
    }

Step 2: if the consumer property of dubbo:reference label, that is, ReferenceBean, is empty, call the appendProperties method to fill in the default property. The specific loading order is as follows:

  1. Load the corresponding parameter value from the system property. Parameter key: dubbo.consumer. Property name. The method to get the property value from the system property is System.getProperty(key).
  2. Load the value of the property profile. Property configuration file. You can use the system property: dubbo.properties.file. If the value is not configured, the default value is the dubbo.properties property configuration file. ReferenceConfig#init
 appendProperties(this);

Step3: call the appendProperties method to fill in the properties of ReferenceBean. The source of property value is the same as step2. Of course, only the properties with empty properties in ReferenceBean are filled.

ReferenceConfig#init

if (getGeneric() == null &amp;&amp; getConsumer() != null) {
      setGeneric(getConsumer().getGeneric());
}
if (ProtocolUtils.isGeneric(getGeneric())) {
      interfaceClass = GenericService.class;
} else {
      try {
              interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
      } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
      }
      checkInterfaceAndMethods(interfaceClass, methods);
}

Step 4: if return reference is used, replace the interface value with GenericService full path name. If not, load interface name, and verify whether the method referenced by dubbo:reference sub label dubbo:method exists in the interface specified by the interface.

ReferenceConfig#init

String resolve = System.getProperty(interfaceName);      // @1
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {                       // @2
     resolveFile = System.getProperty("dubbo.resolve.file");    // @3 start
     if (resolveFile == null || resolveFile.length() == 0) {
          File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
          if (userResolveFile.exists()) {
               resolveFile = userResolveFile.getAbsolutePath();
          }
     }    // @3 end
     if (resolveFile != null &amp;&amp; resolveFile.length() &gt; 0) {    // @4
          Properties properties = new Properties();
          FileInputStream fis = null;
          try {
               fis = new FileInputStream(new File(resolveFile));
               properties.load(fis);
           } catch (IOException e) {
               throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
           } finally {
               try {
                    if (null != fis) fis.close();
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
               }
           }
          resolve = properties.getProperty(interfaceName);
      }
 }
 if (resolve != null &amp;&amp; resolve.length() &gt; 0) {  // @5
      url = resolve;
      if (logger.isWarnEnabled()) {
         if (resolveFile != null &amp;&amp; resolveFile.length() &gt; 0) {
             logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
         } else {
            logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
        }
    }
}

Step 5: deal with the settlement mechanism of dubbo service consumer, that is, the message consumer only connects to the service provider and bypasses the registry.

Code @ 1: get the direct service provider of the interface from the system property, if - Dinterface exists= dubbo://127.0.0.1:20880, where interface is dubbo:reference The value of the interface property.

Code @ 2: if the - D attribute is not specified, try to find it from the resolve configuration file. It can be seen that - D has a higher priority.

Code @ 3: first try to get the path of the resolve configuration file, which can be sourced from - Ddubbo.resolve.file =File pathname. If the system parameter is not configured, it defaults to ${user.home}/dubbo-resolve.properties. If the file exists, the value of resolveFile is set. Otherwise, resolveFile is null.

Code @ 4: if the resolveFile is not empty, load the contents in the resolveFile file, and then obtain the configured direct service provider URL through the interface.

Code @ 5: if resolve is not empty, fill in the URL attribute of ReferenceBean as resolve (point-to-point service provider URL), print the log, and the source of point-to-point URL (system attribute, resolve configuration file).

ReferenceConfig#init

checkApplication();
checkStubAndMock(interfaceClass);

Step 6: verify whether the application of ReferenceBean is empty. If it is empty, new an application, and try to fill in its properties from system properties (priority) and resource files. At the same time, verify the compatibility of stub and mock implementation classes with interface. The system properties and resource file properties are configured as follows: Application dubbo.application. Attribute name, for example dubbo.application.name

ReferenceConfig#init

Map<string, string> map = new HashMap<string, string>();
Map<object, object> attributes = new HashMap<object, object>();
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() &gt; 0) {
     map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}

Step 7: build a Map to encapsulate the attributes of the service consumer's reference to the service provider URL. Here, we mainly fill in the side: consumer, dubbo: 2.0.0 (version), timestamp, pid: process ID.

ReferenceConfig#init

if (!isGeneric()) {
    String revision = Version.getVersion(interfaceClass, version);
    if (revision != null &amp;&amp; revision.length() &gt; 0) {
         map.put("revision", revision);
    }
    String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
    if (methods.length == 0) {
           logger.warn("NO method found in service interface " + interfaceClass.getName());
           map.put("methods", Constants.ANY_VALUE);
     } else {
          map.put("methods", StringUtils.join(new HashSet<string>(Arrays.asList(methods)), ","));
     }

}

Step 8: if it is not a generalized reference, add all method names of methods:interface, and separate multiple methods with commas. ReferenceConfig#init

map.put(Constants.INTERFACE_KEY, interfaceName);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);

Step 9: use Map to store the attributes of application configuration, module configuration, default consumer parameter (ConsumerConfig), and service consumer dubbo:reference. ReferenceConfig#init

String prefix = StringUtils.getServiceKey(map);
if (methods != null &amp;&amp; !methods.isEmpty()) {
    for (MethodConfig method : methods) {
         appendParameters(map, method, method.getName());
         String retryKey = method.getName() + ".retry";
         if (map.containsKey(retryKey)) {
              String retryValue = map.remove(retryKey);
              if ("false".equals(retryValue)) {
                  map.put(method.getName() + ".retries", "0");
              }
         }
         appendAttributes(attributes, method, prefix + "." + method.getName());
         checkAndConvertImplicitConfig(method, map, attributes);
   }
}

Step10: get the service key value / {group}/interface: version. If the group is empty, it is interface: version. Its value is saved as prifex. Then fill the attribute name of dubbo:method into the map, and the key prefix is dubbo.method.methodname. Attribute name. The attributes of the child tag dubbo:argument of dubbo:method are also appended to the attributes map. The key is prifex + methodname. Attribute name.

ReferenceConfig#init

String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
      hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
      throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + 
           hostToRegistry);
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

Step 11: fill in the register.ip attribute, which is the IP of the message consumer connecting to the registry, not the IP of the registry itself. ReferenceConfig#init

ref = createProxy(map);

Step 12: call the createProxy method to create a message consumer proxy. The details of its implementation are analyzed in detail below. ReferenceConfig#init

ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);

Step 13: cache the message consumer in the application model.

1.2.1 source code analysis referenceconfig ා createproxy method

ReferenceConfig#createProxy

URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
    if (url != null &amp;&amp; url.length() &gt; 0) { // if a url is specified, don't do local reference
          isJvmRefer = false;
    } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
         // by default, reference local service if there is
         isJvmRefer = true;
    } else {
         isJvmRefer = false;
    }
} else {
    isJvmRefer = isInjvm().booleanValue();
}

Step 1: judge whether the consumer refers to the services provided in this JVM. If the injvm of the dubbo:reference tag (expired, replaced by the local attribute) is not empty, take the value directly. If the value is not configured, judge whether the URL attribute of ReferenceConfig is empty. If it is not empty, isjvmreferer = false, indicating that the service consumer will directly connect to the service provider of the URL. If the URL attribute is empty, judge whether the protocol is isInjvm, its Implementation logic: get the scop e attribute of dubbo:reference, and judge according to its value:

  • If it is empty, isJvmRefer is false.
  • If the protocol is injvm, it means local protocol. Since the implementation of local protocol is provided, isJvmRefer does not need to be configured. The label is true, so isJvmRefer = false.
  • If scope=local or injvm=true, isJvmRefer=true.
  • If scope=remote, isJvmRefer is set to false.
  • If it is a generalized reference, isJvmRefer is set to false.
  • By default, isJvmRefer is set to true.

ReferenceConfig#createProxy

if (isJvmRefer) {
   URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
   invoker = refprotocol.refer(interfaceClass, url);
   if (logger.isInfoEnabled()) {
         logger.info("Using injvm service " + interfaceClass.getName());
   }
} 

Step 2: if the consumer references the service in the local JVM, the invoker will be created by using the InjvmProtocol. The invoker in dubbo is mainly responsible for the function of service invocation, which is the core implementation. It will be analyzed in the special chapter later. Here we need to know that the invoker related to the protocol will be created.

ReferenceConfig#createProxy

if (url != null &amp;&amp; url.length() &gt; 0) { // user specified URL, could be peer-to-peer address, or register center's address.
     String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);   // @1
     if (us != null &amp;&amp; us.length &gt; 0) {
           for (String u : us) {
                  URL url = URL.valueOf(u);
                  if (url.getPath() == null || url.getPath().length() == 0) {
                       url = url.setPath(interfaceName);
                  }
                 if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {   // @2
                      urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                  } else {
                      urls.add(ClusterUtils.mergeUrl(url, map));  // @3
                 }
             }
       }
} 

Step3: deal with direct connection and mutually exclusive with step2.

Code @ 1: split the direct URL. Separate multiple direct URLs with semicolons. If the URL does not contain the path attribute, set the path attribute to interfaceName for the URL.

Code @ 2: if the direct connection provider's protocol is registry, add the refer attribute to the url, whose value is all attributes of the message consumer. (indicates service provider discovery from registry)

Code @ 3: if it is another agreement provider, merge the properties of service provider and message consumer, and remove the default properties of service provider. Properties starting with default.

ReferenceConfig#createProxy

List<url> us = loadRegistries(false);   // @1
if (us != null &amp;&amp; !us.isEmpty()) {
     for (URL u : us) {
           URL monitorUrl = loadMonitor(u);   // @2
           if (monitorUrl != null) {
                 map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));   // @3
            }
            urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));  // @4
       }
}
if (urls == null || urls.isEmpty()) {
       throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address="\&quot;...\&quot;" /> to your spring config.");
}

Step 4: ordinary message consumer, subscribing to the service from the registration center.

Code @ 1: get all the registry URL s, where the parameter false indicates the consumer side. You need to exclude the registry with dubbo:registry subscribe=false, and the value false indicates that the subscription is not accepted.

Code @ 2: build the monitoring center URL according to the registration center URL.

Code @ 3: if the monitoring center is not empty, add the property monitor after the registry URL.

Code @ 4: in the registry URL, append the attribute refer, whose value is the URL composed of all configurations of the consumer.

ReferenceConfig#createProxy

if (urls.size() == 1) {
    invoker = refprotocol.refer(interfaceClass, urls.get(0));     // @1
} else {
    List<invoker<?>&gt; invokers = new ArrayList<invoker<?>&gt;();    // @2. Multiple service provider URL s, cluster mode
    URL registryURL = null;
    for (URL url : urls) {
         invokers.add(refprotocol.refer(interfaceClass, url));    // @2
         if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
               registryURL = url; // use last registry url
          }
     }
     if (registryURL != null) { // registry url is available
          // use AvailableCluster only when register's cluster is available
          URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
          invoker = cluster.join(new StaticDirectory(u, invokers));    // @3
     } else { // not a registry url
          invoker = cluster.join(new StaticDirectory(invokers));
     }
 }

Step5: get the Invoker of the corresponding protocol according to the URL. Code @ 1: if there is only one service provider URL, the Invoker is built directly according to the protocol, as follows:

Code @ 2: if there are multiple service providers, many service providers form a cluster. First, build the service Invoker according to the protocol. By default, Dubbo registers in discovery based on the service. The URL attribute will not be specified in the service consumer, and the service provider list will be obtained from the registry. At this time, the URL: At the beginning of registry: / /, the url will contain the register attribute, whose value is the type of the registry. For example, zookeeper will use RedisProtocol to build the Invoker. This method will automatically discover the service providers registered in the registry. In the following article, zookeeper Registry will be used as an example to analyze its implementation principle in detail. Code @ 3: returns the Invoker implemented in the cluster mode. The inheritance system of the Invoker class in Dubbo is as follows:

The Invoker in cluster mode implements the Invoker interface just like the single protocol Invoker, and then uses Directory to ensure a protocol Invoker in cluster Invoker, which is very ingenious. In the following chapters, we will focus on the implementation principle of Dubbo Invoker, including the cluster implementation mechanism.

ReferenceConfig#createProxy

Boolean c = check;
if (c == null &amp;&amp; consumer != null) {
      c = consumer.isCheck();
}
if (c == null) {
      c = true; // default true
}
if (c &amp;&amp; !invoker.isAvailable()) {
       throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group   
              == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + 
             NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}

Code @ 4: if the check of Dubbo: referencece is true or empty by default, you need to determine whether the service provider exists.

ReferenceConfig#createProxy

return (T) proxyFactory.getProxy(invoker);
AbstractProxyFactory#getProxy
public <t> T getProxy(Invoker<t> invoker) throws RpcException {
        Class<!--?-->[] interfaces = null;
        String config = invoker.getUrl().getParameter("interfaces");     // @1
        if (config != null &amp;&amp; config.length() &gt; 0) {
            String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
            if (types != null &amp;&amp; types.length &gt; 0) {
                interfaces = new Class<!--?-->[types.length + 2];
                interfaces[0] = invoker.getInterface();
                interfaces[1] = EchoService.class;        // @2
                for (int i = 0; i &lt; types.length; i++) {
                    interfaces[i + 1] = ReflectUtils.forName(types[i]);
                }
            }
        }
        if (interfaces == null) {
            interfaces = new Class<!--?-->[]{invoker.getInterface(), EchoService.class};
        }
        return getProxy(invoker, interfaces);    // @3
    }

Obtain the proxy class according to the invoker. The implementation logic is as follows:

Code @ 1: get the value of interfaces from the consumer URL, and separate the single service application interface with.

Code @ 2: add the default interface, EchoService interface.

Code @ 3: use jdk or Javassist to create a proxy class according to the interface to be implemented. Finally, the starting sequence diagram of message consumers is given

This section is about the startup process of Dubbo service consumers (service callers). The next chapter will focus on Invoker (implementation details related to service invocation).

Author introduction: Ding Wei, author of RocketMQ technology insider, community preacher of RocketMQ, public No.: Middleware interest circle At present, maintainers have successively published source code analysis Java collection, Java Concurrent contract (JUC), Netty, Mycat, Dubbo, RocketMQ, Mybatis and other source code columns. You can click on the link: Middleware knowledge planet , discuss high concurrent and distributed service architecture and exchange source code together.

</t></t></invoker<?></invoker<?></url></string></object,></object,></string,></string,></string,>

Posted by Fari on Sun, 22 Dec 2019 02:06:53 -0800