The last section talks about the configuration and parameter subscription of service introduction. After parameter subscription, the consumer obtains the provider url, the configurer url, and the routing URL under the corresponding interface classification. Then it must update its own provider directory.
And create a client that connects to the provider.
In addition to refreshing the list of providers when service introduction starts, dubbo also refreshes the list of providers when registration and cancellation events occur in the registry.
public void com.alibaba.dubbo.registry.redis.RedisRegistry.Notifier#run() { while (running) { try { //Core number: 10, maximum number: 0-10 random plus 10, more than the maximum number, and from the core number of 10, so reciprocating if (!isSkip()) { try { //Cyclic no-desk redis service connection pool for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) { JedisPool jedisPool = entry.getValue(); try { jedis = jedisPool.getResource(); try { //Check whether the service name ends with * if (service.endsWith(Constants.ANY_VALUE)) { //Is it the first time? if (!first) { //Yes, that's the first time I asked for it. first = false; //Get the key that matches the current service name Set<String> keys = jedis.keys(service); if (keys != null && !keys.isEmpty()) { for (String s : keys) { //Notification refresh directory, com.alibaba.dubbo.registry.redis.RedisRegistry#doNotify method logic analyzed in the previous section doNotify(jedis, s); } } //Reset connection count resetSkip(); } //Subscription (blocking) jedis.psubscribe(new NotifySub(jedisPool), service); // blocking } else { //Is it the first time? if (!first) { first = false; //Notification refresh directory, com.alibaba.dubbo.registry.redis.RedisRegistry#doNotify method logic analyzed in the previous section doNotify(jedis, service); //Reset connection count resetSkip(); } //Subscription (blocking) jedis.psubscribe(new NotifySub(jedisPool), service + Constants.PATH_SEPARATOR + Constants.ANY_VALUE); // blocking } break; } finally { jedis.close(); } } catch (Throwable t) { // Retry another server logger.warn("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t); // If you only have a single redis, you need to take a rest to avoid overtaking a lot of CPU resources sleep(reconnectPeriod); } } } catch (Throwable t) { logger.error(t.getMessage(), t); sleep(reconnectPeriod); } } } catch (Throwable t) { logger.error(t.getMessage(), t); } } }
NotifySub is notified of the subscription, and we are concerned about its onMessage method
public void NotifySub#onMessage(String key, String msg) { if (logger.isInfoEnabled()) { logger.info("redis event: " + key + " = " + msg); } //Interested in provider registration and cancellation if (msg.equals(Constants.REGISTER) || msg.equals(Constants.UNREGISTER)) { try { Jedis jedis = jedisPool.getResource(); try { //Notification, the logic of the com.alibaba.dubbo.registry.redis.RedisRegistry#doNotify method was analyzed in the previous section doNotify(jedis, key); } finally { jedis.close(); } } catch (Throwable t) { // TODO Notification failure does not restore mechanism guarantee logger.error(t.getMessage(), t); } } }
Okay, let's get a general idea of how the notifier works. Now let's see how to refresh the provider directory.
public synchronized void com.alibaba.dubbo.registry.integration.RegistryDirectory#notify(List<URL> urls) { //Provider url List<URL> invokerUrls = new ArrayList<URL>(); //Routing url List<URL> routerUrls = new ArrayList<URL>(); //Configurator url List<URL> configuratorUrls = new ArrayList<URL>(); for (URL url : urls) { //Acquisition protocol String protocol = url.getProtocol(); //Get the category, the default category is providers String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); //Is it a routing url? if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) { routerUrls.add(url); //Is it the configurator url? } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) { configuratorUrls.add(url); //Is it the provider url? } else if (Constants.PROVIDERS_CATEGORY.equals(category)) { invokerUrls.add(url); } else { //Prompts that the current url classification is not supported logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()); } } // configurators //The URL is used as the configuration URL to build the configurator. It is mainly used to modify the original url. Some default parameters can be added. if (configuratorUrls != null && !configuratorUrls.isEmpty()) { this.configurators = toConfigurators(configuratorUrls); } // routers //Create routing rules and construct specific routing rules types through router parameters in url //For example, ConditionRouter Factory matches providers according to conditions //197.0.0.1 = > 192.0.0.2 = > when condition on the left and then condition on the right. Interested in reading the corresponding source code can be customized. if (routerUrls != null && !routerUrls.isEmpty()) { List<Router> routers = toRouters(routerUrls); if (routers != null) { // null - do nothing setRouters(routers); } } //Get the configurator that you just parsed List<Configurator> localConfigurators = this.configurators; // local reference // merge override parameters //This is the registry address. this.overrideDirectoryUrl = directoryUrl; //Call the configurer to configure the registry address if (localConfigurators != null && !localConfigurators.isEmpty()) { for (Configurator configurator : localConfigurators) { this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); } } // providers //Refresh Provider refreshInvoker(invokerUrls); }
The above code mainly classifies URLs into provider url, configurator url, routing url. If there is a configuration url, the configurator of the corresponding protocol will be built to configure our registry address, and finally start refreshing the provider directory.
private void com.alibaba.dubbo.registry.integration.RegistryDirectory#refreshInvoker(List<URL> invokerUrls) { //If there is only one provider, and the provider's protocol is empty, that is, no provider is obtained. if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { //Marked as No Access this.forbidden = true; // Forbid to access //No caller, methodName - > List < invoker > this.methodInvokerMap = null; // Set the method invoker map to null //Close (close) all connected invokers and empty the urlInvokerMap (url-) invoker collection destroyAllInvokers(); // Close all invokers } else { //allow access to this.forbidden = false; // Allow to access //Recording old data Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) { //If invokerUrls have no url, that is, no provider address, then retrieve it from the cache invokerUrls.addAll(this.cachedInvokerUrls); } else { this.cachedInvokerUrls = new HashSet<URL>(); //cache this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison } if (invokerUrls.isEmpty()) { return; } //Build an Invoker for each url //URL - > invoker (example of Invoker Delegate) Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map //Convert to Method Name - > List < Invoker > Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map // state change // If the calculation is wrong, it is not processed. if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString())); return; } //invoker merging multiple groups this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; this.urlInvokerMap = newUrlInvokerMap; try { //Consumption invalid provider url, loop oldUrlInvokerMap collection, destroy invoker that does not exist with newUrlInvokerMap collection destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker } catch (Exception e) { logger.warn("destroyUnusedInvokers error. ", e); } } }
The above code does a few things.
- Build the provider url into Invoker Delegate, which connects the provider when building Invoker Delegate
- Get the method of interface from url parameter and construct methodName-"Listmap with method name key and invoker as value
- Check if it's multi-group, and if so, group it, and merge it through cluster (if it's multi-group, the final object of this instance is Mergeable Cluster, of course, for the existence of decorative classes, which are actually MockCluster Wrapper)
- Eliminate invalid providers
Let's look at the process of building invoker
private Map<String, Invoker<T>> com.alibaba.dubbo.registry.integration.RegistryDirectory#toInvokers(List<URL> urls) { Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>(); if (urls == null || urls.isEmpty()) { return newUrlInvokerMap; } Set<String> keys = new HashSet<String>(); //Does the consumer specify an agreement? String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY); for (URL providerUrl : urls) { // If protocol is configured at the reference side, only the matching protocol is selected //Screening URLs for service specification protocols if (queryProtocols != null && queryProtocols.length() > 0) { boolean accept = false; String[] acceptProtocols = queryProtocols.split(","); for (String acceptProtocol : acceptProtocols) { if (providerUrl.getProtocol().equals(acceptProtocol)) { accept = true; break; } } if (!accept) { continue; } } if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) { continue; } //Check whether the corresponding protocol exists or not. if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) { logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost() + ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions())); continue; } //Merge parameters, merge some parameters of the provider url, and do not merge the number of threads of the provider, etc. //For the same parameters, consumers have the highest priority in their own configuration. URL url = mergeUrl(providerUrl); String key = url.toFullString(); // The parameter urls are sorted //Duplicate url, skip if (keys.contains(key)) { // Repeated url continue; } keys.add(key); // Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key); if (invoker == null) { // Not in the cache, refer again try { boolean enabled = true; if (url.hasParameter(Constants.DISABLED_KEY)) { enabled = !url.getParameter(Constants.DISABLED_KEY, false); } else { enabled = url.getParameter(Constants.ENABLED_KEY, true); } if (enabled) { //Build invoker only if it is available invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl); } } catch (Throwable t) { logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t); } if (invoker != null) { // Put new invoker in cache newUrlInvokerMap.put(key, invoker); } } else { newUrlInvokerMap.put(key, invoker); } } keys.clear(); return newUrlInvokerMap; }
Grouping invoker s with method names
private Map<String, List<Invoker<T>>> com.alibaba.dubbo.registry.integration.RegistryDirectory#toMethodInvokers(Map<String, Invoker<T>> invokersMap) { Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>(); // According to the methods classification declared by the provider URL, the methods is compatible with the registry to execute the filtered methods List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>(); if (invokersMap != null && invokersMap.size() > 0) { for (Invoker<T> invoker : invokersMap.values()) { //Get all the public method names of the interface from the url parameters, and the method names are separated by commas String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY); if (parameter != null && parameter.length() > 0) { //Comma Segmentation Method String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter); if (methods != null && methods.length > 0) { for (String method : methods) { if (method != null && method.length() > 0 && !Constants.ANY_VALUE.equals(method)) { //Grouping by method name List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null) { methodInvokers = new ArrayList<Invoker<T>>(); newMethodInvokerMap.put(method, methodInvokers); } methodInvokers.add(invoker); } } } } invokersList.add(invoker); } } //The invoker is filtered according to the routing, assuming that conditional routing is ConditionRouter, and the rule rule value is host=192.0.0.1=> host=192.0.0.2. //It means that consumers with ip 192.0.0.1 can only use the services of providers with ip 192.0.0.2, so when the current consumer's ip 192.0.0.1, it only screens out invoker s with ip 192.0.0.2. List<Invoker<T>> newInvokersList = route(invokersList, null); //* -> List<Invoker> newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList); //All public method names of service reference interfaces if (serviceMethods != null && serviceMethods.length > 0) { for (String method : serviceMethods) { List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null || methodInvokers.isEmpty()) { methodInvokers = newInvokersList; } //Further screening according to the method newMethodInvokerMap.put(method, route(methodInvokers, method)); } } // sort and unmodifiable for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) { //Sort List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); Collections.sort(methodInvokers, InvokerComparator.getComparator()); newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers)); } return Collections.unmodifiableMap(newMethodInvokerMap); }
Grouping and merging
private Map<String, List<Invoker<T>>> com.alibaba.dubbo.registry.integration.RegistryDirectory#toMergeMethodInvokerMap(Map<String, List<Invoker<T>>> methodMap) { Map<String, List<Invoker<T>>> result = new HashMap<String, List<Invoker<T>>>(); for (Map.Entry<String, List<Invoker<T>>> entry : methodMap.entrySet()) { //Method name String method = entry.getKey(); List<Invoker<T>> invokers = entry.getValue(); //group -> List<Invoker<T>> Map<String, List<Invoker<T>>> groupMap = new HashMap<String, List<Invoker<T>>>(); for (Invoker<T> invoker : invokers) { //Get group String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, ""); //Grouping List<Invoker<T>> groupInvokers = groupMap.get(group); if (groupInvokers == null) { groupInvokers = new ArrayList<Invoker<T>>(); groupMap.put(group, groupInvokers); } groupInvokers.add(invoker); } if (groupMap.size() == 1) { //If there's only one, there's no need to merge. result.put(method, groupMap.values().iterator().next()); } else if (groupMap.size() > 1) { //If there are more than one group, merge each group into one List<Invoker<T>> groupInvokers = new ArrayList<Invoker<T>>(); for (List<Invoker<T>> groupList : groupMap.values()) { groupInvokers.add(cluster.join(new StaticDirectory<T>(groupList))); } result.put(method, groupInvokers); } else { result.put(method, invokers); } } return result; }
Next, I'll continue to analyze the client connection provider's logic, which was built when Invoker Delegate was built.
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
This protocol is a class generated with javassist, which eventually calls DubboProtocol's refer method. DubboProtocol is decorated with two wrapper classes that we analyzed when we analyzed the export of the service.
//type: Interface Class Object, url: Provider Address public <T> Invoker<T> com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper#refer(Class<T> type, URL url) throws RpcException { if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } //Build the interceptor chain. This time, unlike the service export, the filter grouping required is consumers, and the logic is no longer exhaustive. return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER); } | V public <T> Invoker<T> com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper#refer(Class<T> type, URL url) throws RpcException { if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } //Obtaining listeners is the same logic as exporting services, no more details. return new ListenerInvokerWrapper<T>(protocol.refer(type, url), Collections.unmodifiableList( ExtensionLoader.getExtensionLoader(InvokerListener.class) .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY))); } | | V public <T> Invoker<T> com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#refer(Class<T> serviceType, URL url) throws RpcException { //url takes the optimizer parameter, which specifies the optimized serialized class name optimizeSerialization(url); // create rpc invoker. //Create rpc invoker DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers); invokers.add(invoker); return invoker; }
Initialize client
private ExchangeClient[] com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#getClients(URL url) { // whether to share connection boolean service_share_connect = false; //Connection number int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0); // if not configured, connection is shared, otherwise, one connection for one service //If no connection is set, then the shared client is set if (connections == 0) { service_share_connect = true; connections = 1; } ExchangeClient[] clients = new ExchangeClient[connections]; for (int i = 0; i < clients.length; i++) { if (service_share_connect) { //Usually the shared client is created, and the initClient method is also called internally. clients[i] = getSharedClient(url); } else { clients[i] = initClient(url); } } return clients; }
Getting Shared Client
private ExchangeClient com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#getSharedClient(URL url) { //Get address String key = url.getAddress(); //Get from the cache ReferenceCountExchangeClient client = referenceClientMap.get(key); if (client != null) { if (!client.isClosed()) { //count client.incrementAndGetCount(); return client; } else { //If the connection is closed, remove it referenceClientMap.remove(key); } } locks.putIfAbsent(key, new Object()); synchronized (locks.get(key)) { //Double lock-in test if (referenceClientMap.containsKey(key)) { return referenceClientMap.get(key); } //Initialize client ExchangeClient exchangeClient = initClient(url); //Create a reference computing client, because it is a shared client, so this class can record how many service introductions have been created. client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap); //Record to Cache referenceClientMap.put(key, client); ghostClientMap.remove(key); locks.remove(key); return client; } }
Initialize client
private ExchangeClient com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#initClient(URL url) { // client type setting. //Get the client parameter value from the url to determine which client to use and specify netty or mima String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT)); //Add encoding parameters, default is codec - > Dubbo url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME); // enable heartbeat by default //Add heartbeat cycle, default is 60s url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); // BIO is not allowed since it has severe performance issue. //Check whether the specified client exists 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 //If lazy loading is required, create a lazy loading client if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) { client = new LazyConnectExchangeClient(url, requestHandler); } else { //Create a client to connect to the provider client = Exchangers.connect(url, requestHandler); } } catch (RemotingException e) { throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e); } return client; }
dubbo creates the NettyClient client, and the logic to connect the provider is in the constructor of this class.
protected void com.alibaba.dubbo.remoting.transport.netty4.NettyClient#doOpen() throws Throwable { final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this); bootstrap = new Bootstrap(); bootstrap.group(nioEventLoopGroup) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout()) .channel(NioSocketChannel.class); if (getConnectTimeout() < 3000) { bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); } else { bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout()); } bootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this); ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug .addLast("decoder", adapter.getDecoder()) .addLast("encoder", adapter.getEncoder()) .addLast("handler", nettyClientHandler); } }); }
As you can see, dubbo's creation of clients is similar to that of servers, and there's not much to say here. Finally, summarize what classes invoker is packaged into
MockClusterInvoker invoker -> FailoverClusterInvoker(If the consumer sets it up group,So this will become MergeableClusterInvoker) directory -> RegistryDirectory(With the function of dynamically updating the provider directory) urlInvokerMap -> This is a Map,key Provider url,The value is InvokeDelegate InvokeDelegate invoker -> ProtocolFilterWrapper invoker -> ListenerInvokerWrapper invoker -> DubboInvoker clients -> This is an array whose number of elements depends on how many connections are allowed when the configuration service is introduced. ReferenceCountExchangeClient ReferenceCountExchangeClient client -> HeaderExchangeClient client -> NettyClient channel -> NioSocketChannel handler -> MultiMessageHandler handler -> HeartbeatHandler handler -> AllChannelHandler handler -> DecodeHandler handler -> HeaderExchangeChannel handler -> DubboProtocol#RequHandler (internal class)
From the ChannelHandler packaging level, the channel processor involved in message encoding and decoding is the same as the channel processing created by the server, so the processing logic of the channel can be seen in the article of requestor response data processing, which is not elaborated here.
From the packaging level of Invoker, except that ProtocolFilter Wrapper and Listener Invoker Wrapper are the same as the server (code logic is similar, but the grouping of listeners and filters is different), the other is completely different, so we need to study its differences. We will go to the next section. To analyze.