Last time we talked about Dubbo's service exposure, this time we'll look at how Dubbo calls services
This article will be interpreted based on the architecture diagram of dubbo
Catalog
Client Start Process
As we all know, when a client invokes a service, only the interface has no corresponding implementation class, so we need to generate a proxy and execute the service through the proxy.
You can see that we get the Bean of DemoService from ioc, but there is no DemoService bean in the client itself. At this time, we can see that such a configuration in the configuration file uses the reference tag. The service configured by the reference tag in dubbo will be proxied by another Bean, that is, the ReferenceBean. It will put the generated proxy object into IOC through the getObject() method, so point in
<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>
The proxy object of the interface is checked here, and if it is not, a proxy object is initialized
public synchronized T get() { checkAndUpdateSubConfigs(); if (destroyed) { throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!"); } // Detect if ref is empty, or create it by init method if (ref == null) { // The init method is primarily used to handle configurations and to call createProxy to generate proxy classes init(); } return ref; }
In the click-in init() method, you can see that the core method is to create proxy objects (you can see some information about our own configuration, interface paths, etc.)
Then there is a judgment about the number of providers, whether they are multiple or single, then generate invokers, and finally return the generated proxies based on invokers, focusing on how to generate invokers, where the protocols are adaptive, so the corresponding implementation classes are selected based on the information in the url, you can see that the registry is used in the protocol, and you come to RegistryProtocol
// Individual registry or service provider (direct service connection, same below) if (urls.size() == 1) { // Call RegistryProtocol's refer to build an Invoker instance invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); // registry://192.168.200.129:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&pid=14276&qos.port=33333&refer=application%3Ddemo-consumer%26check%3Dfalse%26dubbo%3D2.0.2%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D14276%26qos.port%3D33333%26register.ip%3D192.168.200.10%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1622603489641®istry=zookeeper×tamp=1622603494381 } else { // Multiple registries, multiple service providers, or a mix of both List<Invoker<?>> invokers = new ArrayList<Invoker<?>>(); URL registryURL = null; // Get all Invoker s into invokers for (URL url : urls) { // By ref_protocol calls refer to build Invoker, refprotocol will run // Loads the specified Protocol instance according to the url protocol header and calls the refer method of the instance invokers.add(REF_PROTOCOL.refer(interfaceClass, url)); if (REGISTRY_PROTOCOL.equals(url.getProtocol())) { registryURL = url; // use last registry url } } if (registryURL != null) { // registry url is available // use RegistryAwareCluster only when register's CLUSTER is available // If the registry link is not empty, AvailableCluster will be used URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME); // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker // Create a StaticDirectory instance and merge multiple Invoker s with Cluster Cluster Extension Points FailoverCluster is the default invoker = CLUSTER.join(new StaticDirectory(u, invokers)); } else { // not a registry url, must be direct invoke. invoker = CLUSTER.join(new StaticDirectory(invokers)); } } // invoker availability check if (shouldCheck() && !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()); } if (logger.isInfoEnabled()) { logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl()); } /** * @since 2.7.0 * ServiceData Store */ MetadataReportService metadataReportService = null; if ((metadataReportService = getMetadataReportService()) != null) { URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map); metadataReportService.publishConsumer(consumerURL); } // create service proxy really generates proxy PROXY_based on Invoker FACTORY=ProxyFactory$ Adaptive@xxxx return (T) PROXY_FACTORY.getProxy(invoker);
Go to refer() in RegistryProtocol s, where you get the registry based on the configuration information and connect to zk, then pass in a doRefer() containing cluster fault tolerance and load balancing clusters, registries, interface types, and URLs
In doRefer, you register yourself with the registry, then subscribe to the service (focus) to generate an invoker for each provider, and wrap multiple invokers into one
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { url = URLBuilder.from(url) .setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY)) .removeParameter(REGISTRY_KEY) .build(); //Get registry instance ZookeeperRegistry url= zookeeper://192.168.200.129:2181/org.apache.dubbo.registry.RegistryService?application=demo -consumer&dubbo=2.0.2&interface=org. Apache. Dubbo. Registry. RegistryService&pid=14276&qos. Port=33333×tamp=1622603494381 Registry registry = registryFactory.getRegistry(url);// Get an instance and use curator to connect to zk if (RegistryService.class.equals(type)) { return proxyFactory.getInvoker((T) registry, type, url); } // group="a,b" or group="*" Converts url query string to Map Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY)); String group = qs.get(GROUP_KEY); // //Get group configuration if (group != null && group.length() > 0) { if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) { // Load MergeableCluster instance through SPI and call doRefer to continue executing service reference logic return doRefer(getMergeableCluster(), registry, type, url); } } return doRefer(cluster, registry, type, url); } private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) { // Create RegistryDirectory Instance RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url); // Set up a registry and protocol directory.setRegistry(registry); directory.setProtocol(protocol); // all attributes of REFER_KEY Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters()); // Generate service consumer links consumer://192.168.200.10/org.apache.dubbo.demo.DemoService?application=demo -consumer&check=false&dubbo=2.0.2&interface=org. Apache. Dubbo. Demo. DemoService&lazy=false&methods=sayHello&pid=12468&qos. Port=33333&side=consumer&sticky=false×tamp=1622604057385 URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters); // Register service consumers and create new nodes in the consumers directory if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) { directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url)); registry.register(directory.getRegisteredConsumerUrl()); } directory.buildRouterChain(subscribeUrl); /** * Subscribe to node data such as providers, configurators, routers * Once you get the provider information under providers, you create a client (nettyclient) and connect to the server, focusing on the protocolBindingRefer in DubboProtocol s * ********So focus on this method call**** */ directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY)); Invoker invoker = cluster.join(directory); // There may be multiple service providers under a registry, one invoker for each provider, where multiple invokers are merged into one ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory); return invoker; }
Entering the subscription section, the subscription itself is an extension point, and since we use zk, we end up with doSubscribe in ZookeeperRegistry, which, after a series of ZK operations, notifies all client-pulled provider nodes to encapsulate them as invoker s
public synchronized void notify(List<URL> urls) { Map<String, List<URL>> categoryUrls = urls.stream() .filter(Objects::nonNull) .filter(this::isValidCategory) .filter(this::isNotCompatibleFor26x) .collect(Collectors.groupingBy(url -> { if (UrlUtils.isConfigurator(url)) { return CONFIGURATORS_CATEGORY; } else if (UrlUtils.isRoute(url)) { return ROUTERS_CATEGORY; } else if (UrlUtils.isProvider(url)) { return PROVIDERS_CATEGORY; } return ""; })); List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList()); this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators); List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList()); toRouters(routerURLs).ifPresent(this::addRouters); // Providers get the URL for providers List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList()); /** * Encapsulate each provider url under/providers as invoker ****** Key ***** */ refreshOverrideAndInvoker(providerURLs); }
This encapsulates and connects the provider as invoker
This is created from the url, and the protocol itself is an extension point. By default, DubboProtocols are used to birth invoker. This invoker has an InvokerDelegate layer on top of it. We focus on the content in DubboProtocols.
When creating a DubboInvoker, you can see that there is also a client in the constructor, which first checks to see if the provider has a local connection, or if it is used directly, it needs to be created if it is not
@Override public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException { optimizeSerialization(url); /** * create rpc invoker. Create DubboInvoker * Focus on getClients (urls) getting clients from provider url s. * * url=dubbo://192.168.200.10:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&bean.name=org.apache.dubbo.demo.DemoService&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=20192&qos.port=33333®ister=true®ister.ip=192.168.200.10&release=&remote.application=demo-provider&side=consumer&sticky=false×tamp=1622964228064 */ DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers); invokers.add(invoker); return invoker; } private ExchangeClient[] getClients(URL url) { // whether to share connection shares connection or not boolean useShareConnect = false; int connections = url.getParameter(CONNECTIONS_KEY, 0);// Gets the number of connections, defaulting to 0, indicating no configuration List<ReferenceCountExchangeClient> shareClients = null; // if not configured, connection is shared, otherwise, one connection for one service if (connections == 0) { useShareConnect = true; /** * The xml configuration should have a higher priority than properties. xml Configuration takes precedence over properties * Gets the value of the shareconnections property in <dubbo:consumer/> to indicate the number of shared connections */ String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null); connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY, DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr); shareClients = getSharedClient(url, connections); // Get a specified number of shared connections (first acquisition will not be created yet) } // Connections represent the number of connections at this time, no longer distinguishing between shared or newly created connections ExchangeClient[] clients = new ExchangeClient[connections]; for (int i = 0; i < clients.length; i++) { // If shared, take directly from shareClients if (useShareConnect) { // Get Shared Clients clients[i] = shareClients.get(i); } else { // If not shared, new connections initiate new clients where you can focus your attention clients[i] = initClient(url); } } return clients; }
Just take a look to see if there is a configuration for lazy loading first, or if it is not, create a normal one. You may remember that Exchanger has a friend who also has an Exchanger when exposed on the server side. This is an extension in itself, and HeaderExchanger is used by default.
private ExchangeClient initClient(URL url) { // client type setting. Get the client type, netty by default String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT)); // Add codec and url url = url.addParameter(CODEC_KEY, DubboCodec.NAME); // dubbo codec, important // enable heartbeat by default adds a heartbeat parameter to the url url = url.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT)); // BIO is not allowed since it has severe performance issue. if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) { throw new RpcException("Unsupported client type: " + str + "," + " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " ")); } ExchangeClient client; try { // connection should be lazy Gets the lazy configuration and determines the type of client to create based on the configuration value if (url.getParameter(LAZY_CONNECT_KEY, false)) { // Create lazy-loading ExchangeClient instance client = new LazyConnectExchangeClient(url, requestHandler); } else { /** * Create a normal exchangeClient instance Analysis This block creates an exchangeClient that binds a Netty Client * * ExchangeHandler requestHandler = new ExchangeHandlerAdapter Internal implementation in DubboProtocol */ client = Exchangers.connect(url, requestHandler); } } catch (RemotingException e) { throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e); } return client; }
Connections are made in HeaderExchanger, and netty is used by default. As mentioned earlier, netty is no longer repeated.
public class HeaderExchanger implements Exchanger { public static final String NAME = "header"; @Override public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException { /** * There are several calls, as follows: * 1. Create HeaderExchangeHandler object parameter handler= DubboProtocol. RequHandler is important and involves processing when sending a request * 2. Create DecodeHandler Object * 3. Building Client Instances with Transporters * 4. Create HeaderExchangeClient object * * Focus on: Transporters.connect */ return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true); } @Override public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { // Create a HeaderExchangeServer instance, which contains multiple logic, as follows: // 1. new HeaderExchangeHandler(handler) load handling request writeback results // 2. New DecodeHandler (new HeaderExchangeHandler)) decodes request data and response results and how to submit them to subsequent processes for further processing // 3. Transporters. Bind (url, new DecodeHandler) (new HeaderExchangeHandler) (handler)) focuses on this bind method. return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); } }
So here we get the wrapped invoker and go back to where the proxy is generated from invoker (above, invoker, below), proxy_factory itself is an extension point, using JavassistProxyFactory by default
Notable here is that new InvokerInvocationHandler, which implements jdk's InvocationHandler, injects invoker in and intercepts when executing the corresponding method
The connection to the server and the registration are all done here. The next step is the call to the service.
Dubbo Service Call
The client proxy generation was just described above, so when the next generated proxy method is called, it's not Dubbo's call chain. Let's look directly at the code
You can see that demoService is the InvokerInvocationHandler just generated, and when the method is executed, the invoke method is called
First we exclude methods from the Object class, then we dispatch toString methods, and then we encapsulate our methods and parameters into RpcInvocation
MockClusterInvoker determines whether it is a mock call or not, and if it is not, it is passed directly to AbstractClusterInvoker. There are three main operations here: first, routing from the list of Invokers in the registry, then initializing the load balancing policy, and then passing in the FailoverClusterInvoker subclass
There is a fault tolerance mechanism in FailoverClusterInvoker, which has a default number of retries (default 2) +1, and then loops, selecting the appropriate Invoker through load balancing and calling it
Next comes DubboInvoker, where it is worth noting that a Future is created to subscribe to the results of the call
The client's netty is then called to send in the HeaderExchangeChannel, which is a familiar name to see because HeaderExchange Channel exists on both the server and the client
The message is then sent in dubbo encapsulated NettyChannel, but before the message is actually sent, it has to be encoded twice and once, after which the request is transmitted to the server via channel
Next, let's look at the code on the server side. The first thing the server receives the request is decoding, so we directly see the decoding in NettyServer. As for the design of a Dubbo protocol before decoding, the protocol header will have 16 bytes of location, so the information stored will not be explained one by one.
You can see that in the form of a do while, 16 bytes of the protocol header are read first, and if not 16, a NEED_is set MORE_ The state of the INPUT and throws it, waiting for the next read
After reading the protocol header, it will be parsed and judged, such as getting the request id to determine if it is a heartbeat
Finally, the request is judged and returned to the business processor
In business processing you can see that some of the information in our request has been parsed into objects, such as our method name
The call passes through HeartbeatHandler to determine if it is a heartbeat
Next comes the point, dubbo can set request dispatch, what does it mean, that is, when processing a task, it can be executed in the io thread of netty or in the pool of worker threads, depending on the type of task. I do not configure it here, so it executes in the worker thread by default, so it comes to AllChannelHandler
You can see how events are handled in an asynchronous thread
You can see that the received method decodes again because we didn't decode the incoming parameters before, so we have to decode them here.
Get the request id here first, after all, one-to-one correspondence, call will get a future, when the future has a result, it will write the response results back to the client through the channel
The specific calling process first obtains the invoker from the information and then invokes it through invoker. Since invoker is a proxy, the implementation class of the server side is executed during the call, and the result is returned
This is an introduction to the calling process of dubbo.