The previous section analyses the logic of provider directory refresh and connection between consumers and providers introduced by services, and analyses the following major packaging levels, which I divide into three layers
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)
MockCluster Invoker instance belongs to the outermost layer of invoker. This MockCluster Invoker is passed into ProxyFactory.getProxy(invoker) to form a consumer-side interface proxy, so when the consumer-side interface invokes a method, it will call MockCluster Invoker. It can be said that this class is the entry to prepare to invoke the provider service, MockCluster Invoker. The invoker directly referenced by sterInvoker is a failover caller (in the case of a group specified, it is a mergeable caller), and the list of providers held by Failover Cluster Invoker is a dynamic list that updates the list of service providers based on changes in the registry.
The InvokeDelegate layer represents a provider, and it sets up a series of filters on the way to invoke provider services.
The Reference Count Exchange Client layer represents the specific connection of each invoker. They are encapsulated in the client attribute of DubboInvoker and are an array. This layer mainly deals with the channel of netty, and then sends information to the provider. Once a message is sent to the channel of netty, com.alibaba.dub will be called. Bo. remoting. transport. netty4. NettyCodecAdapter. InternalEncoder encodes parameters.
Dubbo's own channel processor is invoked, but for sending requests, dubbo's channel processing does not do more logic. If it is called synchronously, it just encapsulates the request into DefaultFuture and waits for the return of the service provider. If the service provider returns the data, then dubbo's channel will be very large. Big use, it will call MultiMessageHandler - > Heartbeat Handler - > All Channel Handler - > DecodeHandler - > Header Exchange Channel - > DubboProtocol # Request Handler. See the series of articles on the processing of requestor response data for specific logic.
Let's look at this: What happens when an interface calls a method? As mentioned earlier, MockCluster Invoker is the entry to start invoking providers
public Result com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke(Invocation invocation) throws RpcException { Result result = null; //Get the mock value String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); if (value.length() == 0 || value.equalsIgnoreCase("false")) { //no mock //No need for mock result = this.invoker.invoke(invocation); } else if (value.startsWith("force")) { if (logger.isWarnEnabled()) { logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl()); } //force:direct mock //Force a call to the mock service result = doMockInvoke(invocation, null); } else { //fail-mock try { result = this.invoker.invoke(invocation); } catch (RpcException e) { if (e.isBiz()) { throw e; } else { if (logger.isWarnEnabled()) { logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e); } result = doMockInvoke(invocation, e); } } } return result; }
The above code is mainly to check whether the call of mock service is needed. From the code point of view, it can be divided into two kinds, one is mandatory call, the other is called after the failure of calling service interface.
//Examples are Failover Cluster Invoker (failover) public Result com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke(final Invocation invocation) throws RpcException { //Check if the current invoker has been destroyed checkWhetherDestroyed(); LoadBalance loadbalance = null; // binding attachments into invocation. //Getting additional parameters from the rpc call context Map<String, String> contextAttachments = RpcContext.getContext().getAttachments(); if (contextAttachments != null && contextAttachments.size() != 0) { ((RpcInvocation) invocation).addAttachments(contextAttachments); } //Getting available providers List<Invoker<T>> invokers = list(invocation); if (invokers != null && !invokers.isEmpty()) { //Get load balancing policy, default is random policy loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl() .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE)); } //If it is an asynchronous call, the call id is generated by atomic operation RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); return doInvoke(invocation, invokers, loadbalance); }
The most important thing about the above code is to filter providers. Now let's see how it filters.
//It is an example of Registry Directory public List<Invoker<T>> com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory#list(Invocation invocation) throws RpcException { if (destroyed) { throw new RpcException("Directory already destroyed .url: " + getUrl()); } //The actual method of screening providers perfectly inherits spring's coding style List<Invoker<T>> invokers = doList(invocation); //Getting routes from the last service subscription List<Router> localRouters = this.routers; // local reference if (localRouters != null && !localRouters.isEmpty()) { for (Router router : localRouters) { try { //Check whether it is a run-time filter routing (a wave is filtered when subscribing) if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) { invokers = router.route(invokers, getConsumerUrl(), invocation); } } catch (Throwable t) { logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t); } } } return invokers; }
com.alibaba.dubbo.registry.integration.RegistryDirectory#doList
When I say everything in the comment, I put the method name of the next piece of code.
public List<Invoker<T>> com.alibaba.dubbo.registry.integration.RegistryDirectory#doList(Invocation invocation) { //Disallow access, throw errors if (forbidden) { // 1. No service provider 2. Service providers are disabled throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); } List<Invoker<T>> invokers = null; //This collection is the result of filtering at the start of service introduction, methodName - > List < Invoker > Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) { //Get the method name String methodName = RpcUtils.getMethodName(invocation); //Get the parameters we passed in when we called the interface Object[] args = RpcUtils.getArguments(invocation); if (args != null && args.length > 0 && args[0] != null && (args[0] instanceof String || args[0].getClass().isEnum())) { //If the first parameter is of enumeration type, the key in the localMethod InvokerMap collection is the method name +"."+enumeration value, and different providers can be invoked depending on the change of the first parameter value. invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter } if (invokers == null) { //Get the provider by method name invokers = localMethodInvokerMap.get(methodName); } if (invokers == null) { //Get the provider through * which is routed at service introduction start-up invokers = localMethodInvokerMap.get(Constants.ANY_VALUE); } if (invokers == null) { //Get the first group Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator(); if (iterator.hasNext()) { invokers = iterator.next(); } } } //Return return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers; }
After routing screening, the next is the home of load balancing.
public Result com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { List<Invoker<T>> copyinvokers = invokers; //If copyinvokers are empty, throw an error checkInvokers(copyinvokers, invocation); //Retries parameter value is obtained from url, indicating the number of failed retries, default is 2, if the first call is counted as 3. int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1; if (len <= 0) { len = 1; } // retry loop. RpcException le = null; // last exception. List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers. Set<String> providers = new HashSet<String>(len); //loop for (int i = 0; i < len; i++) { //Reselect before retry to avoid a change of candidate `invokers`. //NOTE: if `invokers` changed, then `invoked` also lose accuracy. //If i is greater than zero, it means that a retry failed. if (i > 0) { //Check that the current invoker is closed checkWhetherDestroyed(); //Re-pass routing screening because providers are likely to drop out at any time copyinvokers = list(invocation); // check again //Check copyinvokers again for value checkInvokers(copyinvokers, invocation); } //Select one through load balancing Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked); invoked.add(invoker); //Record in rpc context RpcContext.getContext().setInvokers((List) invoked); try { //Calling invoker of an executable remote provider Result result = invoker.invoke(invocation); //Even if the retry is successful, a log reminder will be printed if (le != null && logger.isWarnEnabled()) { logger.warn("Although retry the method " + invocation.getMethodName() + " in the service " + getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + ", but there have been failed providers " + providers + " (" + providers.size() + "/" + copyinvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le); } return result; } catch (RpcException e) { if (e.isBiz()) { // biz exception. throw e; } le = e; } catch (Throwable e) { le = new RpcException(e.getMessage(), e); } finally { //Record the address of the provider that has been invoked to print the log after successful retry and which machine calls failed! providers.add(invoker.getUrl().getAddress()); } } throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method " + invocation.getMethodName() + " in the service " + getInterface().getName() + ". Tried " + len + " times of the providers " + providers + " (" + providers.size() + "/" + copyinvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le); } | V //Select: Excluded providers, which usually have value when retried. These values were all failed in the last call. invokers: Candidate providers protected Invoker<T> com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException { if (invokers == null || invokers.isEmpty()) return null; //Get the method name String methodName = invocation == null ? "" : invocation.getMethodName(); //Judging whether to use stickiness strategy, stickiness is to use the same machine as possible for subsequent requests after using a provider's services. boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY); { //ignore overloaded method //Check that a provider was screened last time and that the provider is still providing services if (stickyInvoker != null && !invokers.contains(stickyInvoker)) { //If the provider is offline, set it to null stickyInvoker = null; } //ignore concurrency problem //If the service condition, then use the provider that was last filtered if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) { if (availablecheck && stickyInvoker.isAvailable()) { return stickyInvoker; } } } //Where Load Balancing Strategies Are Really Used Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected); //If stickiness is allowed, record the results of this screening if (sticky) { stickyInvoker = invoker; } return invoker; }
Most of the explanations have been written in the comments, which are too much to sum up. Now let's continue to analyze how dubbo filters providers through a load strategy.
private Invoker<T> com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException { if (invokers == null || invokers.isEmpty()) return null; //If there is only one provider, return directly if (invokers.size() == 1) return invokers.get(0); if (loadbalance == null) { //If no load balancing policy is specified, get the default loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE); } Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation); //If the `invoker` is in the `selected` or invoker is unavailable && availablecheck is true, reselect. //If the provider is an unavailable provider, it needs to be re-filtered if ((selected != null && selected.contains(invoker)) || (!invoker.isAvailable() && getUrl() != null && availablecheck)) { try { //Re-screening, if availablecheck is true, checks whether each provider is available Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck); if (rinvoker != null) { invoker = rinvoker; } else { //Check the index of current selected invoker, if it's not the last one, choose the one at index+1. //If retry fails to get the right provider, get the next or first invoker before re-filtering int index = invokers.indexOf(invoker); try { //Avoid collision invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0); } catch (Exception e) { logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e); } } } catch (Throwable t) { logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t); } } return invoker; }
Next we choose a load strategy for analysis. Here we use random strategy analysis and other self-analysis.
protected <T> Invoker<T> com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { //Get the number of providers int length = invokers.size(); // Number of invokers //Weight summation int totalWeight = 0; // The sum of weights boolean sameWeight = true; // Every invoker has the same weight? for (int i = 0; i < length; i++) { //Recycling to gain the weight of each provider int weight = getWeight(invokers.get(i), invocation); totalWeight += weight; // Sum //Check whether the weights of each provider are the same. If they are different, mark sameWeight as different, and unequal probability values will be triggered when weights are different. if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) { sameWeight = false; } } if (totalWeight > 0 && !sameWeight) { // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight. //Obtaining offsets from the sum of weights of 0-n providers int offset = random.nextInt(totalWeight); // Return a invoker based on the random value. for (int i = 0; i < length; i++) { //The offset subtracts the weight until it is less than zero. For different weights, their probability of being selected is different. For example, I have three providers, the first weights are 10, the second weights are 30, and the third weights are 60. //Their probability of being selected is 10%, 30%, 60%. offset -= getWeight(invokers.get(i), invocation); if (offset < 0) { return invokers.get(i); } } } // If all invokers have the same weight value or totalWeight=0, return evenly. //If the weights of each provider are the same, then it's simple and rude to randomize one of the n providers directly, and they have the same probability of being selected. return invokers.get(random.nextInt(length)); }
For the above code, it is necessary to explain that if we have three providers, the first provider has 10, the second provider has 60, and the third provider has 30, the probability of their being selected is 10%, 60%, 30%.
The sum of weights is 100. We randomly get a number between 0100. The probability that the number falls below 010 is 10%, the probability that it falls below 1070 is 60%, and the probability that it falls below 70100 is 30%.
The orange part represents the provider with a weight of 10, the blue part represents the provider with a weight of 60, the green part represents the provider with a weight of 30, and the total weight is 100. In this 100, I randomly take a value of 20 (offset in the code), so the 20 falls in the blue area when I use this 2. 0 minus the weight of the first provider is enough, only minus the weight of the blue part is not enough, so it chooses the provider whose weight is 60, and its probability of being selected is 60%.
After screening the invoker, we can begin to analyze the second level of decoration.
//... First floor 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 //... The third level
Its entry Invoke Delegate, for this layer of Invoke Delegate did not do more logic, directly invoked later, ProtocolFilter Wrapper, Listener Invoker Wrapper no longer analysis here, specific operations can see the request or response data processing series of articles, here the main analysis of different things, then It's Dubbo Invoker.
protected Result com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke(final Invocation invocation) throws Throwable { RpcInvocation inv = (RpcInvocation) invocation; final String methodName = RpcUtils.getMethodName(invocation); inv.setAttachment(Constants.PATH_KEY, getUrl().getPath()); inv.setAttachment(Constants.VERSION_KEY, version); ExchangeClient currentClient; //If there is only one connection, use it directly if (clients.length == 1) { currentClient = clients[0]; } else { //Take turns currentClient = clients[index.getAndIncrement() % clients.length]; } try { //Check whether it is an asynchronous call boolean isAsync = RpcUtils.isAsync(getUrl(), invocation); //Whether it is a two-way communication, that is, when a message is sent to the other party, the other party needs to respond to it. boolean isOneway = RpcUtils.isOneway(getUrl(), invocation); //Waiting overtime int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); //If isOneway is true, it means one-way transmission without the response of the other party. It is usually used in the case that the other party is a consumer and can only go in and out. if (isOneway) { //Whether to wait for all data to be sent, if true, the thread will block timeout time and wait for all data to be sent. boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false); //send data currentClient.send(inv, isSent); //Clear the future without getting the result of the other party's response (the other party will not respond). RpcContext.getContext().setFuture(null); //Return an empty result set directly return new RpcResult(); //Is it an asynchronous call? } else if (isAsync) { //The request interface wraps the inv parameter as a Request and stores the Future in com. alibaba. dubbo. remoting. exchange. support. DefaultFuture FUTURES //Blocking timeout, waiting for the provider to return the response ResponseFuture future = currentClient.request(inv, timeout); //Set RespseFuture to the rpc context for waiting for the response of the other party, and the user can get the future. RpcContext.getContext().setFuture(new FutureAdapter<Object>(future)); return new RpcResult(); } else { //Get interface response synchronously and clear the future s of rpc context RpcContext.getContext().setFuture(null); //Synchronized acquisition of interface response return (Result) currentClient.request(inv, timeout).get(); } } catch (TimeoutException e) { throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); } catch (RemotingException e) { throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); } }
This is followed by the encoding of messages. See the Request or Response Data Processing series for the encoding of messages, which will not be explained here.