This debug code uses the Dubbo – Demo of the Dubbo project on Githubdubbo-demo-xmlThe code below. The default Dubbo communication protocol is used as the debug code here. Since this article is the internal implementation of dubbo framework, it is recommended to read a talk on the official website of Dubbo firstDubbo’s design principlesTo help you understand the code, post here firstDubbo websiteThen when we look at the code, compare the architecture diagram with the analysis:
The following is a description of each layer in the architecture diagram:
- 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: It encapsulates the Request and Response mode, turns synchronous to asynchronous, uses Request and Response as the center, and uses exchange channel, ExchangeClient and ExchangeServer as the expansion interface
- 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
XML configuration for consumer in demo:
<beans
// XMLNS :xsi is the xSI tag namespace
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
// XMLNS :dubbo is the namespace of the dubbo tag
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
// The default namespace for the current XML file
xmlns="http://www.springframework.org/schema/beans"
// xsi:schemaLocation configures the configuration specification corresponding to each namespace and is used for format verification
xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-consumer"/>
<dubbo:registry address=Zookeeper: / / "127.0.0.1:2181" timeout="6000"/ > <! <dubbo:protocol name="dubbo"/ > <! <dubbo: Reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService" timeout="6000" />
</beans>
Copy the code
1, will bedubbo:reference
The configuration resolves toBeanDefinition
Dubbo’s consumer configuration is<dubbo:reference>
In theSpring dubbo tag Configuration processYou can see that the configuration is resolved toBeanDefinition
Class, the sequence diagram is as follows:
2. Instantiate by BeanDefinitionReferenceConfig
object
BeanDefinition
The BeanClass attribute oforg.apache.dubbo.config.spring.ReferenceConfig.class
Class, which implementsFactoryBean
Interface (FactoryBean
The interface is designed for this scenario where beans are generated using dynamic proxy techniques, and Spring will inject this object when it is injectedgetObject()
Method to return a real instance.getBean()
For detailed logic, seehereThe debug screenshot is as follows:
3. When dependency injection is triggeredReferenceConfig
thisFactoryBean
thegetObject()
Call, returns the proxy class
Spring also calls it during dependency injectiongetBean(String beanName)
Method to get the bean that has already been instantiated. BeanName gets the bean instantiated in step 2ReferenceConfig
Object, which isBeanFactory
, so it will eventually callgetObject()
Method, the debug screenshot is as follows:
getObject()
Screenshot below:
The code for getObject() looks like this, and I’ve omitted some branch code here:
public Object getObject(a) {
return this.get();
}
public synchronized T get(a) {...this.init(); .return this.ref;
}
public synchronized void init(a) {
// Initializes the Dubbo startup class
if (this.bootstrap == null) {
this.bootstrap = DubboBootstrap.getInstance();
this.bootstrap.init(); }...// Map holds all the configuration information for creating the Consumer proxy class
Map<String, String> map = new HashMap();
map.put("side"."consumer");
// Insert dubbo version number, timestamp, process PID runtime information into mapReferenceConfigBase.appendRuntimeParameters(map); .// Pass in the map to create the proxy class
this.ref = this.createProxy(map); . }private T createProxy(Map<String, String> map) {...// Check the registry configuration and generate the registry URL based on the registry configuration
this.checkRegistry();
List<URL> us = ConfigValidationUtils.loadRegistries(this.false);
if (CollectionUtils.isNotEmpty(us)) {
for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
u = (URL)var3.next();
monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
if(monitorUrl ! =null) {
map.put("monitor", URL.encode(monitorUrl.toFullString())); }}}...// Build the invoker chain
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0)); .// Create an agent using ProxyFactory
return PROXY_FACTORY.getProxy(this.invoker);
}
Copy the code
Below is a sequence diagram combining the main steps of the Dubbo architecture drawing:
3.1 Initializing Dubbo Start the DubboBootstrap class
DubboBootstrap is a dubbo startup class, similar to springBoot’s SpringApplication class, which stores various configuration information for the entire Dubbo process. Responsible for the provider’s service exposure and the consumer’s service subscription, maintaining the dubbo container lifecycle, we will skip here and continue to explore our consumer instantiation process
3.2 Building a registry object
CreateProxy (Map
Map
this.checkRegistry();
List<URL> us = ConfigValidationUtils.loadRegistries(this.false);
if (CollectionUtils.isNotEmpty(us)) {
// Get the URL of the registry and put the metadata information of the current consumer configuration into the Parameter property of the URL
for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) { ... }}Copy the code
<dubbo:registry>
The bean corresponding to the configuration center isRegistryConfig.class
, parse the process and<dubbo:reference>
Again, parsed configurations are placed in a global fileConfigManager
Phi, this is going to go from thisConfigManager
Read the registry configuration in. Common configurations in Dubbo are cached inDubbo Tag Parsing in Spring XML FilesParses dubbo custom tagsDubboNamespaceHandler
, the entity classes corresponding to each configuration are as follows:These classes are inheritedAbstractConfig
Class, this abstract class has a quilt@PostConstruct
annotatedaddIntoConfigManager()
Method, which tells the Spring container to execute the annotated method after instantiating the object, caches the current object into a globalConfigManager
Object:
@PostConstruct
public void addIntoConfigManager(a) {
ApplicationModel.getConfigManager().addConfig(this);
}
Copy the code
Url
It is a very heavy parameter encapsulation class in the main link of Dubbo. In the main link, the encapsulation parameter is passed all the way. Here, it means that the metadata of the registry and the metadata of the consumer are encapsulated in a URL, which is passed to the downstream method. The address of the registry, the interface of the consumer, and the method of the consumer are encapsulated in the following debug screenshots:
private static final Protocol REF_PROTOCOL = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
Copy the code
Here is the SPI extension technology:
Dubbo extends related components through SPI technology, as shown in the code aboveREF_PROTOCOL
Objects are extended through SPI, and the principle is to findMETA-INF/dubbo/internal
The extension class configured below, as shown in my screenshot below:
As you can see from this, there are many extensions to the protocol. class Protocol. Which implementation class is ultimately called to execute the refer method? The key is in getAdaptiveExtension(), which returns an adapter class whose refer method calls getExtension(extName) based on the protol protocol in the URL, For example, a URL starting with Registry :// needs to be executed using the registry’s protocol implementation class. I post the code for this class:
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {...// Get the protocol in the URL, default dubbo
org.apache.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo": url.getProtocol()); .// Get the corresponding plug-in according to the protocol
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
Copy the code
getExtension(extName)
Method returns a wrapper class, as shown in the screenshot below:
You can see that when Dubbo extends through SPI, there are three classes in the configuration file:
- Adapter class: mainly through the URL information to match the appropriate wrapper class
- Wrapper class: This wrapper class can be interpreted as a proxy class with filtering and listening logic, such as the ProtocolListenerWrapper and ProtocolFilterWrapper classes shown in the screenshot
- A real implementation class
The call chain finds the corresponding wrapper class through the adapter class, then forms a call chain through the wrapper class, and finally calls the real implementation class, as shown belowloadClass
Method is responsible for loadingMETA-INF/dubbo/internal
You can see the logic that distinguishes the three types of extension classes. The screenshot is as follows:
Ref_protocol. refer(this.interfaceclass, (URL)this.urls.get(0)), which calls the refer method of the outermost wrapper class ProtocolListenerWrapper:
// ProtocolListenerWrapper class refer method
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return (Invoker)(UrlUtils.isRegistry(url) ? this.protocol.refer(type, url) : new ListenerInvokerWrapper(this.protocol.refer(type, url), Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(InvokerListener.class).getActivateExtension(url, "invoker.listener"))));
}
// ProtocolFilterWrapper class refer method
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return UrlUtils.isRegistry(url) ? this.protocol.refer(type, url) : buildInvokerChain(this.protocol.refer(type, url), "reference.filter"."consumer");
}
Copy the code
Here, as it is the Registry protocol, the refer method of the RegistryProtocol class is directly called. The main code is as follows:
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = this.getRegistryUrl(url);
// Get the registry object
Registry registry = this.registryFactory.getRegistry(url); .return this.doRefer(this.cluster, registry, type, url);
}
Copy the code
The registryFactory here is also extended via SPI, since the registry uses ZooKeeper, so the registryFactory hereregistry
The object isZookeeperRegistry
, the stack screenshot is as follows:
3.3 Building a Service Discovery Object
Each configured Consumer corresponds to a service discovery object. The main function of service discovery is to monitor the node changes under the interface of the registry, including the service provider and route configuration. When the node changes, the registry is notified to update the local configuration information. The trunk code is as follows:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// Build a service discovery object
RegistryDirectory<T> directory = new RegistryDirectory(type, url);
directory.setRegistry(registry);
directory.setProtocol(this.protocol); .// Set the default configured routing chain for service discovery through the SPI extension mechanismdirectory.buildRouterChain(subscribeUrl); . }Copy the code
Build the routing chain with the SPI extension configuration and save it to the service discovery object:
public void buildRouterChain(URL url) {
this.setRouterChain(RouterChain.buildChain(url));
}
private RouterChain(URL url) {
List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension(url, "router");
List<Router> routers = (List)extensionFactories.stream().map((factory) -> {
return factory.getRouter(url);
}).collect(Collectors.toList());
this.initWithRouters(routers);
}
Copy the code
3.4 Publish Consumer information to the registry
Again in the doRefer method, delete the secondary code as follows:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {...if (!"*".equals(url.getServiceInterface()) && url.getParameter("register".true)) {
directory.setRegisteredConsumerUrl(this.getRegisteredConsumerUrl(subscribeUrl, url));
// Publish Consumer information to the registryregistry.register(directory.getRegisteredConsumerUrl()); }... }Copy the code
registry.register(directory.getRegisteredConsumerUrl());
The ZooKeeper client framework is calledCurator
Put consumser information to the zookeeper, in zookeeper/dubbo/org. Apache. Dubbo. Demo. DemoService/write information consumers directory:
3.5 Subscribe to and obtain the service provider, configuration, and routing information in the registry through service discovery objects, update the local configuration of service discovery objects, and build the Invoker chain
Again in the doRefer method, delete the secondary code as follows:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory(type, url);
directory.setRegistry(registry);
directory.setProtocol(this.protocol); .// Subscribe to the node information and update the configuration of the service discovery directory object based on the configuration information under the node in the registry
directory.subscribe(subscribeUrl.addParameter("category"."providers,configurators,routers")); . }public void subscribe(URL url) {
this.setConsumerUrl(url);
CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
this.serviceConfigurationListener = new RegistryDirectory.ReferenceConfigurationListener(this, url);
// Use the current registryDirectory object as a Listener to listen for node changes
this.registry.subscribe(url, this);
}
Copy the code
3.5.1 Subscribe to the registryproviders,configurators,routers
node
This is the core of the service discovery process by zookeeper client framework of Curator to watch on the zookeeper will, configurators, routers nodes information, and to the zookeeper node information changes, will inform to the listener, RegistryDirectory class implements the NotifyListener class, you will be as listeners to monitor the change of the service provider node, when the registry will, configurators, these three node routers changes, The notify(List urls) method of the NotifyListener class calls back to this listener.
3.5.2 Obtaining node configuration, updating the locally cached Provider list, and establishing a long-term connection with the provider
When the service provider changes, the content of zooKeeper’s providers node changes. The notify(List urls) method of ZookeeperRegistry is called to update the privider List. The notify(List) method is automatically triggered, and the main code is as follows:
public synchronized void notify(List<URL> urls) {...// Get service provider information from the urls in the callback
List<URL> providerURLs = (List)urls.getOrDefault("providers", Collections.emptyList());
// Update the list of local service providers
this.refreshOverrideAndInvoker(providerURLs);
}
Copy the code
The URL information contains the metadata information of the service provider. The debug screenshot is as follows: Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – 8 gsawvyr – 1617207225262) (quiver – image – url / 763 a6034b1f510d4bfc30456997d0c74. JPG =1862×631)] there are two main steps:
- The URL information is parsed into a chain of invokers wrapped in layers, the innermost being DubboInvoker.
Each layer of wrapping is designed to add new features, the most important of which is AsyncToSyncInvoker with asynchronous support. The consumer blocks the Invoke method of AsyncToSyncInvoker and waits for the provider to return the result. The Invoker packaging chain formed in this step is as follows: Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – AkF52hWn – 1617207225279) (quiver – image – url / 261433 a85d0d9ca4d4b9408679a5ac82. JPG =1000×262)]
- Create NettyClient, establish a long connection with the provider, and pass
DubboInvoker
The constructor shows the need to encapsulate oneExchangeClient
, this object is responsible for network communication and uses the Netty framework by default.
The constructor code is as follows:
DubboInvoker<T> invoker = new DubboInvoker(serviceType, url, this.getClients(url), this.invokers);
Copy the code
The sequence diagram is as follows:
The complete sequence diagram for the registry callback is as follows:
3.6. Re-wrap the Invoker with the Cluster object to add Cluster features, including troubleshooting, load balancing, and routing logic
Invoker invoker = cluster.join(directory);
Copy the code
Clusters are also extended by SPI, and we can see what this abstraction means by looking at dubbo’s classification of clusters:The name indicates failover and failfastCluster
The idea is to abstract the different mechanisms for dealing with failure. The default is failover, and the final call isFailoverCluster
The debug screenshot is as follows:
Based on the Invoker chain in 3.6, after this layer of packaging, the Picture of the Invoker chain is as follows:
3.7 Wrap the Invoker chain as a proxy class and return it togetObject()
methods
private static final ProxyFactory PROXY_FACTORY = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
PROXY_FACTORY.getProxy(this.invoker);
Copy the code
As can be seen from the code ProxyFactory is extended through SPI, by default, use JavassistProxyFactory JavassistProxyFactory. GetProxy code is as follows:
public <T> T getProxy(Invoker
invoker, Class
[] interfaces)
{
return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
Copy the code
Here will be formed on the packaged into InvokerInvocationHandler invoker chain. The class, let’s look at the generated proxy class by the compiled source code:
public class proxy0
implements ClassGenerator.DC.Destroyable.EchoService.DemoService {
public static Method[] methods;
private InvocationHandler handler;
@Override
public Object $echo(Object object) {
Object[] objectArray = new Object[]{object};
Object object2 = this.handler.invoke(this, methods[0], objectArray);
return object2;
}
public CompletableFuture sayHelloAsync(String string) {
Object[] objectArray = new Object[]{string};
Object object = this.handler.invoke(this, methods[1], objectArray);
return (CompletableFuture)object;
}
public String sayHello(String string) {
Object[] objectArray = new Object[]{string};
Object object = this.handler.invoke(this, methods[2], objectArray);
return (String)object;
}
@Override
public void $destroy() {
Object[] objectArray = new Object[]{};
Object object = this.handler.invoke(this, methods[3], objectArray);
}
public proxy0(a) {}public proxy0(InvocationHandler invocationHandler) {
this.handler = invocationHandler; }}Copy the code
The invoke method of InvokerInvocationHandler is invoked when demoservice.sayHello (String String) is called.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this.invoker, args);
} else{ String methodName = method.getName(); Class<? >[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 0) {
if ("toString".equals(methodName)) {
return this.invoker.toString();
}
if ("$destroy".equals(methodName)) {
this.invoker.destroy();
return null;
}
if ("hashCode".equals(methodName)) {
return this.invoker.hashCode(); }}else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
return this.invoker.equals(args[0]);
}
// struct the RpcInvocation message, including the interface, method, parameters, and other metadata
RpcInvocation rpcInvocation = new RpcInvocation(method, this.invoker.getInterface().getName(), args);
rpcInvocation.setTargetServiceUniqueName(this.invoker.getUrl().getServiceKey());
// Invoke the invoker chain
return this.invoker.invoke(rpcInvocation).recreate(); }}Copy the code
The main logic is to encapsulate the metadata of the invocation class, method, and parameter into the RpcInvocation object and invoke the invocation method from the invocation chain generated earlier. At this point, the proxy class has been created, and what is returned to the getObject() method is a proxy class.