01 preface

Earlier we looked at the principles of RPC, and there are many rPC-based frameworks on the market, such as Dubbo. Today, we will analyze Dubbo from its SPI mechanism, service registration and discovery source code and network communication process.

02 Dubbo architecture

2.1 an overview of the

Dubbo is an open source high-performance service framework of Alibaba, which enables applications to realize the output and input functions of services through high-performance RPC, and can be seamlessly integrated with the Spring framework.

Dubbo is a high-performance, lightweight, open source Java RPC framework that provides three core capabilities: interface-oriented remote method invocation, intelligent fault tolerance and load balancing, and automatic service registration and discovery.

Call flow:

  1. The service container is responsible for starting, loading, and running the service provider.

  2. At startup, service providers register their services with the registry.

  3. At startup, service consumers subscribe to the registry for the services they need.

  4. The registry returns a list of service provider addresses to the consumer, and if there are changes, the registry pushes the change data to the consumer based on the long connection.

  5. The service consumer, from the provider address list, selects one provider to call based on the soft load balancing algorithm. If the call fails, selects another one to call.

  6. Service consumers and providers accumulate calls and call times in memory and regularly send statistics to the monitoring center every minute.

2.2 Architecture

(1) Source code structure

  • Dubo-common: Common logic module: includes Util classes and common models

  • Dubbo -remoting: An implementation of the DuBBo protocol. This package is not required if RPC uses RMI

  • Dubbo-rpc remote call module: Abstract various protocols, as well as dynamic proxies, including one-to-one calls, not concerned with the principle of clustering.

  • Dubbo-cluster cluster module: masquerading multiple service providers as one provider, including load balancing, fault tolerance, routing, etc. The address list of the cluster can be statically configured or issued by the registry.

  • Dubbo-registry registry module: Clustering based on registry delivery and abstractions to various registries

  • Dubo-monitor monitoring module: statistics service call times, call time, call chain tracking services.

  • Dubo-config configuration module: it is an EXTERNAL API of Dubbo. Users use Dubbo through config to hide all details of Dubbo

  • Dubbo-container: Is a standlone container that starts with a simple main load of Spring. There is no need to use a Web container to load services because services usually do not require web container features such as Tomcat/Jboss.

(2) Overall design

  • In the figure, the interfaces used by the service consumer on the left with a light blue background, the interfaces used by the service provider on the right with a light green background, and the interfaces used by both parties on the central axis.

  • The figure is divided into ten layers from bottom to top, and each layer is unidirectional dependence. Each layer can be stripped of the upper layer and reused. Among them, Service and Config layers are API, and other layers are SPI.

  • In the figure, the green blocks are extension interfaces and the blue blocks are implementation classes. The figure only shows the implementation classes used to associate each layer.

  • In the figure, the dotted blue line is the initialization process, that is, the assembly chain at startup, the solid red line is the method call process, that is, the run-time call chain, the purple triangle arrow is the inheritance, you can regard the subclass as the same node of the parent class, and the text on the line is the method called.

(3) Description of each layer

  • Config configuration layer: external configuration interface, centering on ServiceConfig and ReferenceConfig, can directly initialize configuration classes, or generate configuration classes through Spring configuration parsing

  • Proxy ServiceProxy layer: transparent proxy of service interfaces. The client Stub and server Skeleton of the service are generated. The extension interface is ProxyFactory

  • Registry layer: encapsulates the registration and discovery of service addresses, centering on service URLS and extending interfaces as RegistryFactory, Registry, and RegistryService

  • Cluster routing layer: encapsulates routing and load balancing of multiple providers, Bridges registries, centers on Invoker, and extends interfaces to Cluster, Directory, Router, and LoadBalance

  • Monitor monitoring layer: Monitors the number and time of RPC calls. It centers on Statistics and extends interfaces to MonitorFactory, Monitor, and MonitorService

  • Protocol Remote Invocation layer: Encapsulates RPC Invocation with Protocol, Invoker, and half interface, based on Invocation and Result

  • Exchange information exchange layer: Encapsulate the Request and Response mode, and convert the synchronous mode to the asynchronous mode. Request and Response are used as the center, and the expansion interface is Exchanger channel, ExchangeClient, ExchangeServer

  • Transport Network transport layer: Abstract MINA and Netty as the unified interface, Message as the center, extended interfaces are Channel, Transporter, Client, Server, Codec

  • Serialize data Serialization layer: reusable tools with Serialization, ObjectInput,ObjectOutput, and ThreadPool extensions

(4) Call the process

The overall architecture diagram can be roughly divided into the following steps:

1. Start the service provider, enable the Netty service, create a Zookeeper client, and register the service with the registry.

2. The service consumer starts, obtains the list of service providers from the registry through Zookeeper, and establishes a long-term connection with the service provider through Netty.

3. The service consumer starts to remotely invoke the service through the interface, ProxyFactory initializes the Proxy object, and Proxy creates the dynamic Proxy object.

Using the Invoke method, layer upon layer to generate an Invoker object that contains the proxy object.

5. Invoker selects the most suitable service provider through routing, and generates a new DubboInvoker object by adding various filters to the protocol layer wrapper.

6. The DubboInvoker object is then wrapped as a Reuqest object by swap, which is serialized through The NettyClient to the NettyServer side of the service provider.

7. On the side of the service provider, a DubboExporter can be generated through deserialization, protocol decryption and other operations, and then an Invoker object will be generated at the service provider side through layer upon layer transfer processing.

8. The Invoker object invokes the local service, gets the results and returns them to the service consumer through multiple callbacks. The service consumer gets the results and parses them for the final results.

SPI mechanism in Dubbo

3.1 What is SPI

(1) Overview

In Dubbo, SPI is a very important module. Based on SPI, we can easily extend Dubbo. If you want to learn Dubbo source, SPI mechanism must understand. Next, let’s take a look at the Java SPI and Dubbo SPI usage, and then analyze Dubbo SPI source code.

SPI stands for Service Provider Interface, a Service discovery mechanism. The essence of SPI is that the fully qualified name of the implementation class of the interface is defined in a configuration file that the server reads and loads the implementation class. This allows you to dynamically replace the implementation class for the interface at run time.

  1. I sorted out some information, and friends in need can click to get it directly
  2. Microservice architecture: RPC+Dubbo+SpirngBoot+Alibaba+Docker+K8s
  3. Java Core Knowledge Set +25 topic interview set

3.2 SPI in the JDK

The Java SPI is actually a dynamic loading mechanism implemented by a combination of interface-based programming + policy pattern + configuration files.

Let’s take a look at SPI through an example

Define an interface:

package com.laowang; /** * @author original * @date 2021/3/27 * @since 1.0 **/ public interface User {String showName(); }Copy the code

Define two implementation classes

package com.laowang.impl; import com.laowang.User; /** * @author ** @date ** since 1.0 **/ public class Student implements User {@override public String showName() { System.out.println("my name is laowang"); return null; } }package com.laowang.impl; import com.laowang.User; /** * @author * @date 2021/3/27 * @since 1.0 **/ public class Teacher implements User {@override public String showName() { System.out.println("my name is zhangsan"); return null; }}Copy the code

Create a folder named meta-INF. services in the resources directory and create a file named com.laowang.User in that folder with the same name as the full path of User

Write the full path names of the two implementation classes to the file

Writing test classes:

package com.laowang; import java.util.ServiceLoader; Public class SpiTest {public static void main(String[] args) {public static void main(String[] args) { ServiceLoader<User> serviceLoader = ServiceLoader.load(User.class); serviceLoader.forEach(User::showName); }}Copy the code

Running results:

We found that the SPI mechanism helped us automate two implementation classes.

Check out the ServiceLoader source code:

Instead, we use reflection to create objects by reading the full path class name of the implementation class from the configuration file and putting them into the providers container.

Conclusion:

The calling procedure application calls the Serviceloader.load method, creates a new ServiceLoader, and instantiates the member variables in the class. The application gets the object instance through the iterator interface, The ServiceLoader checks whether the providers object (LinkedHashMap<String,S>) has a cached instance. If there is no caching and class loading is performed, the advantage of using the Java SPI mechanism is decoupling, so that the definition of the interface is separated from the concrete business implementation, rather than coupled together. Application processes can enable or replace specific components based on actual services. Disadvantages cannot be loaded on demand. Although ServiceLoader does lazy loading, it is basically only possible to fetch it all by traversing it, i.e. the implementation classes of the interface are loaded and instantiated once. If you don’t want to use some implementation class, or if instantiation of some class is time consuming, it gets loaded and instantiated, and that’s a waste. The method of obtaining an implementation class is not flexible. The method can only be obtained in the form of Iterator. The corresponding implementation class cannot be obtained according to a parameter. It is not safe for multiple concurrent threads to use instances of the ServiceLoader class. When an implementation class is not loaded, an exception is thrown for no real reason, and the error is difficult to locate.

3.3 SPI in Dubbo

Instead of using Java SPI, Dubbo has re-implemented a more powerful SPI mechanism. The logic for Dubbo SPI is encapsulated in the ExtensionLoader class, through which we can load the specified implementation class.

(1) chestnut

Unlike the Java SPI implementation class configuration, Dubbo SPI is configured through key-value pairs so that we can load the specified implementation class on demand. The configuration files required for DubboSPI must be stored in the meta-INF/Dubbo directory. Unlike the Java SPI implementation class configuration, DubboSPI is configured using key-value pairs.

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
Copy the code

When using Dubbo SPI, you need to annotate the @SPI annotation on the interface.

@SPI
public interface Robot {
void sayHello();
}
Copy the code

With ExtensionLoader, we can load the specified implementation class. Here is a demonstration of Dubbo SPI:

public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); Robot bumblebee = extensionLoader.getExtension("bumblebee"); bumblebee.sayHello(); }}Copy the code

In addition to its support for loading interface implementation classes on demand, Dubbo SPI adds features such as IOC and AOP, which are described in the following source code analysis sections.

(2) Source code analysis

The getExtensionLoader method of ExtensionLoader gets an instance of ExtensionLoader, which then gets the extensionClass object through the getExtension method of ExtensionLoader. Let’s start with the getExtension method of ExtensionLoader as an entry point for a detailed analysis of the acquisition process of extended class objects.

public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name  == null"); } if ("true".equals(name)) {return getDefaultExtension(); } // new Holder<Object> Holder = getOrCreateHolder(name); Object instance = holder.get(); If (instance == null) {synchronized (holder) {instance = holder.get(); If (instance == null) {// Create an extension instance instance = createExtension(name); // Set the instance to holder.set(instance); } } } return (T) instance; }Copy the code

The logic of the above code is relatively simple: first check the cache and create the extended object if the cache is not hit. Let’s look at what the process of creating an extension object looks like.

Private T createExtension(String name) {private T createExtension(String name) {private T createExtension(String name) { > clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); T instance = (T) EXTENSION_INSTANCES. Get (clazz); PutIfAbsent (clazz, clazz.newinstance ()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // Inject dependencies into instances injectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses; If (CollectionUtils isNotEmpty (wrapperClasses)) {/ / cycle create instances for Wrapper (Class <? > wrapperClass: wrapperClasses) {// Pass the current instance as an argument to the Wrapper constructor and create the Wrapper instance through reflection. // Then inject the dependencies into the Wrapper instance, At last, the Wrapper example again assigned to the instance variable instance = injectExtension ((T) wrapperClass. GetConstructor (type). NewInstance (instance));  }}Copy the code

The logic of the createExtension method is a little more complicated and includes the following steps:

  1. Get all extended classes through getExtensionClasses

  2. Create extension objects through reflection

  3. Inject dependencies into extended objects

  4. Wrap the extension object in the corresponding Wrapper object

Of the above steps, the first step is the key to loading the extension class, and the third and fourth steps are the concrete implementation of Dubbo IOC and AOP. Because such design source code is more, here is a simple summary of the entire execution logic of ExtensionLoader:

GetExtensionClasses getExtensionClasses getExtensionClasses getExtensionClasses getExtensionClasses getExtensionClasses getExtensionClasses - > loadExtensionClasses # load expanding class - > cacheDefaultExtensionName analytical @ # SPI annotations - > loadDirectory # method the specified folder configuration file - > loadResource Load the class and cache the class using the loadClass methodCopy the code

3.4 How does Dubbo’s SPI implement IOC and AOP

(1) Dubbo IoC

Dubbo IOC is about injecting dependencies through setter methods. Dubbo first retrieves all of the instance’s methods by reflection, then iterates through the list of methods to check if the method name has setter method characteristics. If so, the dependency object is obtained through ObjectFactory, and finally setter methods are called through reflection to set the dependency into the target object. The corresponding code for the whole process is as follows:

private T injectExtension(T instance) { try { if (objectFactory ! = null) {// Get all methods of the instance for (Method Method: Instance.getclass ().getmethods ()) {// What isSetter does is check if a method starts with set and has only one argument, The method access level is public if (isSetter(method)) {/** * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) ! = null) { continue; } Class<? > pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { String property = getSetterProperty(method); / / get dependent objects Object Object = objectFactory. GetExtension (pt, the property); if (object ! // invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }Copy the code

(2) Dubbo Aop

Before we get to that, we need to know the decorator pattern

Decorator pattern: Extends an object dynamically by dynamically attaching responsibility to the object without changing the original class file or using inheritance. It wraps the real object by creating a wrapper object, a decoration.

When working with Spring, we often use AOP capabilities. Insert additional logic before and after the methods of the target class. For example, Spring AOP is often used to implement logging, monitoring, and authentication functions. Does Dubbo’s extension mechanism support similar functionality? The answer is yes. In Dubbo, there is a special class called the Wrapper class. The original extension point instance is wrapped with a wrapper class through the decorator pattern. Implement AOP functionality by inserting additional logic before and after the original extension point implementation.

ConcreteComponent: Defines concrete objects (decorators) : ConcreteComponent: defines concrete objects (decorators) : Abstract decorator, inherited from Component, extends ConcreteComponent from an external class. For ConcreteComponent, there is no need to know about the existence of a Decorator, which is an interface or abstract class ConcreteDecorator: ConcreteDecorator used to extend ConcreteComponent

// Get all classes that need to be wrapped Set<Class<? >> wrapperClasses = cachedWrapperClasses;Copy the code

What are cachedWrapperClasses?

private Set<Class<? >> cachedWrapperClasses;Copy the code

Is a set set, so when does the set add elements?

/** * cache wrapper class * <p> * like: ProtocolFilterWrapper, ProtocolListenerWrapper */ private void cacheWrapperClass(Class<? > clazz) { if (cachedWrapperClasses == null) { cachedWrapperClasses = new ConcurrentHashSet<>(); } cachedWrapperClasses.add(clazz); }Copy the code

This is added by this method, and let’s see who called this private method:

/**
     * test if clazz is a wrapper class
     * <p>
     * which has Constructor with given class type as its only argument
     */
    private boolean isWrapperClass(Class<?> clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }
Copy the code

Dubbo calls the constructor of the decorator class and returns the instance object. If the constructor of the decorator class holds the object,dubbo calls the constructor and returns the instance object

Then replace the loaded class by instantiating the wrapper class. The method that does this is the method that wraps the class.

Dynamic compilation in 04 Dubbo

We know that in Dubbo many extensions are loaded through the SPI mechanism, such as Protocol, Cluster, LoadBalance, ProxyFactory, etc. Sometimes, extensions do not want to be loaded during the framework startup phase, but rather want to be loaded according to runtime parameters when the extension method is called, that is, the implementation class is dynamically loaded according to the parameters.

This dynamic decision to use specific extensions at run time, based on method parameters, is called an extension point adaptive instance in Dubbo. Is actually an extension point proxy, deferring the selection of extensions from Dubbo startup to RPC call. Each extension point in Dubbo has an adaptive class, and if it is not explicitly provided, Dubbo will automatically create one for us, using Javaassist by default.

The implementation logic for an adaptive extension mechanism goes like this

  1. Dubbo generates proxy code for the extended interface.

  2. Compile this code with Javassist or JDK to get the Class Class;

  3. Create proxy classes through reflection;

  4. In the proxy class, the parameters of the URL object are used to determine which implementation class is invoked.

4.1 the javassist

Javassist is an open source library for analyzing, editing, and creating Java bytecode. It was created by Shigeru Chiba of the Department of Mathematics and computer Science at Tokyo Institute of Technology. It has joined the open source JBoss application Server project to implement a dynamic AOP framework for JBoss by using Javassist for bytecode manipulation. Javassist is a subproject of JBoss, and its main advantages are simplicity and speed. You can dynamically change the structure of a class, or generate a class on the fly, using Java encoded form directly without needing to understand virtual machine instructions.

/** * Javassist is an open source library for analyzing, editing, and creating Java bytecodes Public class CompilerByJavassist {public static void main(String[] args) throws Exception {// ClassPool: ClassPool pool = ClassPool. GetDefault (); CtClass = pool.makeclass (" com.itheima.domain.user "); -- private String username CtField enameField = new CtField(pool.getctClass (" java.lang.string "), "username", ctClass); enameField.setModifiers(Modifier.PRIVATE); ctClass.addField(enameField); -- private int age CtField enoField = new CtField(pool.getctClass ("int"), "age", ctClass); enoField.setModifiers(Modifier.PRIVATE); ctClass.addField(enoField); // addMethod ctclass.addmethod (ctnewmethod.getter ("getUsername", enameField)); ctClass.addMethod(CtNewMethod.setter("setUsername", enameField)); ctClass.addMethod(CtNewMethod.getter("getAge", enoField)); ctClass.addMethod(CtNewMethod.setter("setAge", enoField)); // CtConstructor constructor = new CtConstructor(null, ctClass); constructor.setBody("{}"); ctClass.addConstructor(constructor); // ctClass.addconstructor (new ctClass [] {}, ctClass); CtConstructor ctConstructor = new CtConstructor(new CtClass[] {pool.get(String.class.getName()),CtClass.intType}, ctClass); ctConstructor.setBody("{\n this.username=$1; \n this.age=$2; \n}"); ctClass.addConstructor(ctConstructor); VoidType, "printUser",new CtClass[] {}, CtClass); // Add custom method CtMethod CtMethod = new CtMethod(ctclass. voidType, "printUser",new CtClass[] {}, CtClass); // Set the ctmethod.setmodifiers (Modifier.PUBLIC) for custom methods; // Set the function body for the custom method StringBuffer buffer2 = new StringBuffer(); Buffer2.append ("{\nSystem.out.println(\" user info as follows \"); System. \ n "), append (" out. The println (\ "username = \" + username); \n").append(" system.out.println (\" age =\"+age); \n").append("}"); ctMethod.setBody(buffer2.toString()); ctClass.addMethod(ctMethod); // Generate a class <? > clazz = ctClass.toClass(); Constructor cons2 = clazz.getDeclaredConstructor(String.class,Integer.TYPE); Object obj = cons2.newInstance("itheima",20); GetMethod ("printUser", new Class[] {}).invoke(obj, new Object[] {}); Byte [] byteArr = ctClass.tobytecode (); FileOutputStream fos = new FileOutputStream(new File("D://User.class")); fos.write(byteArr); fos.close(); }}Copy the code

4.2 Source Code Analysis

The Adaptive annotations

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
   String[] value() default {};
}
Copy the code

Adaptive can be annotated on classes or methods. Annotation on class: Dubbo does not generate a proxy class for this class. Annotation on the method: Dubbo generates proxy logic for the method, indicating that the current method needs to be implemented by invoking the corresponding extension point based on the parameter URL.

Each extension point in Dubbo has an adaptive class, and if it is not explicitly provided, Dubbo will automatically create one for us, using Javaassist by default. Let’s take a look at the code to create an adaptive extension class

/ / 1, look at the obtain method of the extensionLoader extensionLoader < Robot > extensionLoader = extensionLoader. GetExtensionLoader (Robot. Class); Private ExtensionLoader(Class<? > type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); Public T getAdaptiveExtension() {// getAdaptiveExtension() {public T getAdaptiveExtension() cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); If (instance == null) {try {// here we create an adaptive extension class instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; CreateAdaptiveExtension () private T createAdaptiveExtension() {try {return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); }} / / again into the getAdaptiveExtensionClass () private Class <? > getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass ! = null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); } / / continue to chase in createAdaptiveExtensionClass () private Class <? > createAdaptiveExtensionClass() { String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); Compiler @spi ("javassist") public interface compiler {/** * Compile Java source code. ** @param code Java source  code * @param classLoader classloader * @return Compiled class */ Class<? > compile(String code, ClassLoader classLoader); } // This is done by generating a string of classes and using JavAssist to generate an objectCopy the code

CreateAdaptiveExtensionClassCode () method is used in a StringBuilder to build adaptive Java source code of a class. Method implementation is long, so I won’t post the code here. This is also an interesting way to generate bytecode, which is Java source code, then compiled and loaded into the JVM. In this way, you have more control over the generated Java classes. And it doesn’t have to care about the apis of various bytecode generation frameworks, etc. Because xxx. Java files are generic to Java, they are the ones we are most familiar with. It’s just that the code isn’t very readable, and you need to build xx.java bit by bit.

05 Service exposure and discovery

5.1 Service Exposure

(1)

In Dubbo’s core domain model:

  • Invoker is the entity domain that is the core model of Dubbo, to which all other models rely or turn. It represents an executable to which invoke calls can be made. It may be a local implementation, a remote implementation, or a cluster implementation. On the service provider side, Invoker is used to invoke the service provider class. On the service consumer side, Invoker is used to make remote calls.

  • Protocol is the service domain, which is the main functional entry for Invoker exposure and references, and is responsible for the lifecycle management of Invoker. Export: exposes the remote service. Refer: references the remote service

  • GetInvoker: For the server side, wrap the service object, such as DemoServiceImpl, into an Invoker object getProxy: For the client, create a proxy object for the interface, such as the interface of DemoService.

  • The Invocation is the session domain, which holds variables during Invocation, such as method names, parameters, etc

(2) Overall process

Before going into the details of service exposure, let’s look at the principle of service exposure for duubo as a whole

On the whole, the Dubbo framework does service exposure in two parts, The first step is to convert a held service instance to An Invoker through a broker, and the second step is to convert the Invoker to an Exporter through a specific protocol, such as Dubbo. This abstraction is also greatly facilitated by the framework.

The service provider exposes the blue initialization chain of the service, and the sequence diagram is as follows:

(3) Source code analysis

The entry method for the service export is onApplicationEvent of ServiceBean. OnApplicationEvent is an event-response method that performs a service export upon receiving a Spring context refresh event. The method code is as follows:

@Override public void onApplicationEvent(ContextRefreshedEvent event) { if (! isExported() && ! isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } export(); }}Copy the code

Finally find doExportUrls() method by export

Private void doExportUrls() {// Load all registries in the URL URL file and package them as a List of URL objects inside dubbo. List<URL> registryURLs = loadRegistries(true); For (ProtocolConfig ProtocolConfig) ProtocolConfig: protocols) { String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version); ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass); ApplicationModel.initProviderModel(pathKey, providerModel); // Url exporturlsFor1Protocol (protocolConfig, registryURLs); }}Copy the code

DoExportUrlsFor1Protocol () method code is much older, we only deal with the core

. if (! Scope_none.equalsignorecase (scope)) {// Local exposure, logging service data to local JVM if (! SCOPE_REMOTE.equalsIgnoreCase(scope)) { exportLocal(url); } // Remote exposure, sending data to the registry if (! SCOPE_LOCAL.equalsIgnoreCase(scope)) { if (! isOnlyInJvm() && logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " +  url); } if (CollectionUtils.isNotEmpty(registryURLs)) { for (URL registryURL : registryURLs) { //if protocol is only injvm ,not register if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { continue; } url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); URL monitorUrl = loadMonitor(registryURL); if (monitorUrl ! = null) { url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString()); } if (logger.isInfoEnabled()) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); } // For providers, this is used to enable custom proxy to generate invoker String proxy = url.getParameter(PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { registryURL = registryURL.addParameter(PROXY_KEY, proxy); } // Generate Invoker Invoker<? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); / / DelegateProviderMetaDataInvoker used to hold the Invoker and ServiceConfig DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // Export the service and generate an Exporter<? > exporter = protocol.export(wrapperInvoker); exporters.add(exporter); }} else {// There is no registry, only export service.... } /** * @since 2.7.0 * ServiceData Store */ MetadataReportService MetadataReportService = null; if ((metadataReportService = getMetadataReportService()) ! = null) { metadataReportService.publishProvider(url); } } } this.urls.add(url);Copy the code

The following code determines how to export the service according to the scope parameter in the URL: scope = none, does not export the service scope! = remote, export to local scope! = local, export to remote

Whether it’s exported locally or remotely. An important step is to create an Invoker before exporting a service. So let’s look at the Invoker creation process. Invoker is created by ProxyFactory. Dubbo’s default ProxyFactory implementation class is JavassistProxyFactory. Let’s go to the JavassistProxyFactory code and explore the Invoker creation process. As follows:

@Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL URL) {// Create warpper final Wrapper for the target class Wrapper = wrapper.getwrapper (proxy.getClass().getName().indexof ('$') < 0? proxy.getClass() : type); Doinvoke return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<? >[] parameterTypes, Object[] arguments) throws Throwable {// Call the Wrapper invokeMethod method InvokeMethod will eventually call the target method return wrapper. InvokeMethod (proxy, methodName, parameterTypes, arguments); }}; }Copy the code

With the Invoke created successfully, let’s look at the local export

/** * always export injvm */ private void exportLocal(URL url) { URL local = URLBuilder.from(url) .setprotocol (LOCAL_PROTOCOL) // Set the protocol header to injvm.sethost (LOCALHOST_VALUE)// Local IP: 127.0.0.1.setport (0).build(); // Create Invoker and export the service, where the protocol calls InjvmProtocol's export <? > exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local); }Copy the code

The exportLocal method is relatively simple. First, it determines whether to export the service according to the URL protocol header. To export, create a new URL and set the protocol header, host name, and port to the new values. Then create Invoker and call InjvmProtocol’s Export method to export the service. Let’s take a look at what InjvmProtocol’s export method does.

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }
Copy the code

As above, InjvmProtocol’s export method creates only an InjvmExporter, with no other logic. To export the service to the local analysis is finished.

Look at exporting services to remote

Next, let’s look at the process of exporting services to remote. Exporting a service to a remote location involves two processes: service export and service registration. Start by analyzing the service export logic. Let’s move on to the Export method of RegistryProtocol.

@Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {// Obtain the registry URL URL registryUrl = getRegistryUrl(originInvoker); URL providerUrl = getProviderUrl(originInvoker); final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); / / export service final ExporterChangeableWrapper < T > exporter = doLocalExport (originInvoker providerUrl); // Load the Registry implementation class according to the URL, such as ZookeeperRegistry final Registry = getRegistry(originInvoker); // get the registered service providerUrl, final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl); ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); //to judge if we need to delay publish boolean register = registeredProviderUrl.getParameter("register", true); If (register) {// Register the service with the registry (registryUrl, registeredProviderUrl); providerInvokerWrapper.setReg(true); } / / to subscribe to override the data to the registry registry. The subscribe (overrideSubscribeUrl overrideSubscribeListener); exporter.setRegisterUrl(registeredProviderUrl); exporter.setSubscribeUrl(overrideSubscribeUrl); // Create and return DestroyableExporter return DestroyableExporter<>(exporter); }Copy the code

The above code looks quite complicated, and it mainly does the following:

  1. Call doLocalExport to export the service

  2. Register services with the registry

  3. Subscribe to override data to the registry

  4. Create and return DestroyableExporter

What did doLocalExport do

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); //protocol is related to the configured protocol (dubbo: DubboProtocol) return new ExporterChangeableWrapper < > (Exporter < T >) protocol. The export (invokerDelegate), originInvoker); }); }Copy the code

Next, let’s focus on the Protocol’s export method. Assuming the runtime protocol is Dubbo, the protocol variable here loads DubboProtocol at run time and calls the Export method of DubboProtocol.

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { URL url = invoker.getUrl(); // export service. Get the service identifier. It consists of the service group name, service name, service version number, and port. Such as: demoGroup/com. Alibaba. Dubbo. Demo. DemoService: 1.0.1:20880 String key = the serviceKey (url); DubboExporter<T> exporter = new DubboExporter<T>(url, url, export map); exporterMap.put(key, exporter); / / the key: Interface (DemoService) //export an Stub service for Dispatching Event Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT); Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false); if (isStubSupportEvent && ! isCallbackservice) { String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY); if (stubServiceMethods == null || stubServiceMethods.length() == 0) { if (logger.isWarnEnabled()) { logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) + "], has set stubproxy support event ,but no stub methods founded.")); } } else { stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); }} // Start the service openServer(url); // optimizeSerialization(URL); return exporter; }Copy the code

As mentioned above, we focus on the creation of DubboExporter and the openServer method. It is ok to not understand the other logic, and it does not affect the understanding of the service export process. Let’s examine the openServer method.

private void openServer(URL url) { // find server. String key = url.getAddress(); //client can export a service which's only for server to invoke boolean isServer = url.getParameter(IS_SERVER_KEY, true); If (isServer) {// Access cache ExchangeServer Server = servermap.get (key); if (server == null) { synchronized (this) { server = serverMap.get(key); If (server == null) {// Create a server instance servermap. put(key, createServer(URL)); } } } else { // server supports reset, use together with override server.reset(url); }}}Copy the code

Next, examine the server instance creation process. The following

private ExchangeServer createServer(URL url) { url = URLBuilder.from(url) // send readonly event when server closes, it's enabled by default .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString()) // enable heartbeat by default .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT)) .addParameter(CODEC_KEY, DubboCodec.NAME) .build(); String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER); // SPI checks whether the Transporter extension represented by the server parameter exists, and raises an exception if (STR! = null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) { throw new RpcException("Unsupported server type: " + str + ", url: " + url); } ExchangeServer server; Try {// Create ExchangeServer server = exchangers.bind (url, requestHandler); } catch (RemotingException e) { throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e); Mina STR = url.getparameter (CLIENT_KEY); mina STR = url.getparameter (CLIENT_KEY); if (str ! = null && str.length() > 0) {// Get all Transporter implementation class names, SupportedTypes = [netty, mina] Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(); If (! Transporter) {// If (! Transporter) {// If (! Transporter) { supportedTypes.contains(str)) { throw new RpcException("Unsupported client type: " + str); } } return server; }Copy the code

As mentioned above, createServer contains three core logic.

The first is to check whether the Transporter extension represented by the server parameter exists, and throw an exception if it does not.

The second is to create the server instance.

The third check is whether the Transporter extension represented by the client parameter is supported. If the Transporter extension does not exist, an exception is thrown. The code corresponding to the two detection operations is straightforward, needless to say. However, the operation of creating the server is not very clear at present, so let’s move on.

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { if (url == null) { throw new IllegalArgumentException("url == null"); } if (handler == null) { throw new IllegalArgumentException("handler == null"); } url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange"); // Recovery, which is headerRecovery by default. Return getExchanger(URL). Bind (url, handler); // The headerrecovery is used to create an ExchangeServer instance. }Copy the code

The above code is relatively simple, so I won’t go into details. The headerrecovery bind method is used.

Public ExchangeServer Bind (URL URL, ExchangeHandler handler) throws RemotingException {// Create HeaderExchangeServer instance, This method contains multiple pieces of logic, as follows:  // 1. new HeaderExchangeHandler(handler) // 2. new DecodeHandler(new HeaderExchangeHandler(handler)) // 3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))) return new HeaderExchangeServer(Transporters.bind(url, new ChannelHandler[]{new DecodeHandler(new HeaderExchangeHandler(handler))})); }Copy the code

The bind method (Headersano11003) contains a lot of logic, but currently we only need to worry about the Transporters bind method. The code for this method is as follows:

public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException { if (url == null) { throw new IllegalArgumentException("url == null"); } else if (handlers ! = null && handlers.length ! = 0) { Object handler; if (handlers.length == 1) { handler = handlers[0]; Handlers = new ChannelHandlerDispatcher(Handlers);} else {handlers = new ChannelHandlerDispatcher(Handlers); } // Get the adaptive Transporter instance and call the instance method return getTransporter().bind(url, (ChannelHandler)handler); } else { throw new IllegalArgumentException("handlers == null"); }}Copy the code

As shown above, the Transporter obtained by the getTransporter() method is dynamically created at runtime. The class is called TransporterAdaptive, which is an adaptive extension class. TransporterAdaptive determines which type of Transporter to load at run time based on the URL parameter passed in. The default is NettyTransporter. Call nettytransporter. bind(URL,ChannelHandler). Create a NettyServer instance. By calling the nettyServer.doopen () method, the server is opened and the service is exposed.

(4) Service registration

This section analyzes the Zookeeper registry. You can analyze other types of registries by yourself. Starting with the entry method of service registration, let’s look again at the export method of RegistryProtocol. As follows:

Go to the register() method

public void register(URL registryUrl, URL registeredProviderUrl) {/ / Registry using Registry Registry. = registryFactory getRegistry (registryUrl); Register (registeredProviderUrl); }Copy the code

Take a look at the getRegistry() method

@Override
    public Registry getRegistry(URL url) {
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
        String key = url.toServiceStringWithoutResolving();
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            //create registry by spi/ioc
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }
Copy the code

Enter the createRegistry() method

@Override public Registry createRegistry(URL url) { return new ZookeeperRegistry(url, zookeeperTransporter); }public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { super(url); if (url.isAnyHost()) { throw new IllegalStateException("registry address == null"); } //// Get the group name, default is dubbo String group = url.getparameter (GROUP_KEY, DEFAULT_ROOT); if (! group.startsWith(PATH_SEPARATOR)) { group = PATH_SEPARATOR + group; } this.root = group; / / create a Zookeeper client, the default is CuratorZookeeperTransporter zkClient = zookeeperTransporter. Connect (url); / / add state listener zkClient. AddStateListener (state - > {if state = = StateListener. The RECONNECTED () {try {recover (); } catch (Exception e) { logger.error(e.getMessage(), e); }}}); }Copy the code

In the above code, we focus on the Connect method call of ZookeeperTransporter, which is used to create the Zookeeper client. Once the Zookeeper client is created, the registry creation process is complete.

Now that we understand the nature of service registration, we are ready to read the code for service registration.

Public void doRegister(URL URL) {try {// Create a node using the Zookeeper client. The node path is generated by the toUrlPath method in the following format: / / / ${group} / ${serviceInterface} / will / ${url} / / for example Dubbo/org. Apache. Dubbo. DemoService/will/dubbo % % 2 f % 2 f127. 3 a 0.0.1... zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }@Override public void create(String path, boolean ephemeral) { if (! Ephemeral) {// If the type of node to be created is not ephemeral, then check whether the node exists if (checkExists(path)) {return; } } int i = path.lastIndexOf('/'); If (I > 0) {// Recursively create the upper path create(path.substring(0, I), false); } // Create a temporary or permanent node based on the ephemeral value if (ephemeral) {createEphemeral(path); } else { createPersistent(path); }}Copy the code

Well, this is the end of the analysis of the service registration process. The whole process can be summarized as: create a registry instance first, and then register the service through the registry instance.

conclusion

  1. Providers address registered in the registry, need, ServiceConfig parsing the URL format for: registry: / / registry – host/org. Apache. Dubbo. Registry. RegistryService? Export = URL encode (” dubbo: / / service – the host / {service name} / {version} “)

  2. Based on the adaptive mechanism of Dubbo SPI, the RegistryProtocol#export() method is called through the URL Registry :// protocol header recognition

  3. Wrap a concrete service class name, such as DubboServiceRegistryImpl, into an Invoker instance via ProxyFactory

  4. Call the doLocalExport method, use the DubboProtocol to turn the Invoker into an Exporter instance, and open the Netty server to listen for client requests

  5. Create a Registry instance, connect to Zookeeper, and register the service by writing the provider’S URL address under the service node

  6. Subscribe to the Override data to the registry and return an Exporter instance

  7. Call the DubboProtocol#export() method to develop the service port according to the dubbo://service-host/{service name}/{version number} protocol header dubbo://

  8. RegistryProtocol#export() The returned export instance is stored in the Listexporters of ServiceConfig