分佈式專題-分佈式服務治理04-Dubbo源碼分析(下篇)

前言

歡迎關注本套Java系列文章,導航地址Java架構師成長之路

關於Dubbo,本系列文章主要講三方面內容。前兩講我們已經瞭解到Dubbo的基本特性,常用配置、自適應擴展點與服務發佈,服務註冊的過程。

本節我們講3~7

  1. Dubbo Extension擴展點
  2. 服務發佈過程
  3. 消費端初始化過程
  4. 服務端調用過程
  5. Directory
  6. Cluster
  7. LoadBalance

Tips:本節文末有Dubbo中文註釋版的源碼哦~

消費端初始化過程

消費端的代碼解析是從下面這段代碼開始的
<dubbo:reference id=“xxxService” interface=“xxx.xxx.Service”/>
ReferenceBean(afterPropertiesSet) ->getObject() ->get()->init()->createProxy 最終會獲得一個代理對象。

createProxy第375行
前面很多代碼都是初始化的動作,需要仔細分析的代碼代碼從createProxy第375行開始

List<URL> us = loadRegistries(false); //從註冊中心上獲得相應的協議url地址
if (us != null && us.size() > 0) {
       for (URL u : us) {
           URL monitorUrl = loadMonitor(u); 
           if (monitorUrl != null) {
               map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
           }
           urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
       }
}
if (urls == null || urls.size() == 0) {
       throw new IllegalStateException("No such any registry to reference " + interfaceName  + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
   }
if (urls.size() == 1) {
    invoker = refprotocol.refer(interfaceClass, urls.get(0)); //獲得invoker代理對象
} else {
    List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
    URL registryURL = null;
    for (URL url : urls) {
        invokers.add(refprotocol.refer(interfaceClass, url));
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            registryURL = url; // 用了最後一個registry url
        }
    }
    if (registryURL != null) { // 有 註冊中心協議的URL
        // 對有註冊中心的Cluster 只用 AvailableCluster
        URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); 
        invoker = cluster.join(new StaticDirectory(u, invokers));
    }  else { // 不是 註冊中心的URL
        invoker = cluster.join(new StaticDirectory(invokers));
    }
}

refprotocol.refer
refprotocol這個對象,定義的代碼如下,是一個自適應擴展點,得到的是Protocol$Adaptive

Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

直接找到Protocol$Adaptive代碼中的refer代碼塊如下
這段代碼中,根據當前的協議url,得到一個指定的擴展點,傳遞進來的參數中,協議地址爲registry://,所以,我們可以直接定位到RegistryProtocol.refer代碼

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

RegistryProtocol.refer
這個方法裏面的代碼,基本上都能看懂
1.根據根據url獲得註冊中心,這個registry是zookeeperRegistry
2.調用doRefer,按方法,傳遞了幾個參數, 其中有一個culster參數,這個需要注意下

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
       url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
       Registry registry = registryFactory.getRegistry(url);
       if (RegistryService.class.equals(type)) {
           return proxyFactory.getInvoker((T) registry, type, url);
       }
       // group="a,b" or group="*"
       Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
       String group = qs.get(Constants.GROUP_KEY);
       if (group != null && group.length() > 0 ) {
           if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
                   || "*".equals( group ) ) {
               return doRefer( getMergeableCluster(), registry, type, url );
           }
       }
       return doRefer(cluster, registry, type, url);
   }

cluster
doRefer方法中有一個參數是cluster,我們找到它的定義代碼如下,。又是一個自動注入的擴展點。

private Cluster cluster;

public void setCluster(Cluster cluster) {
    this.cluster = cluster;
}

從下面的代碼可以看出,這個不僅僅是一個擴展點,而且方法層面上,還有一個@Adaptive,表示會動態生成一個自適應適配器Cluster$Adaptive

@SPI(FailoverCluster.NAME)
public interface Cluster {

    /**
     * Merge the directory invokers to a virtual invoker.
     * 
     * @param <T>
     * @param directory
     * @return cluster invoker
     * @throws RpcException
     */
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;

}

Cluster$Adaptive
通過debug的方式,,獲取到ClusterAdaptiveclusterdoReferClusterAdaptive這個適配器,代碼如下。我們知道cluster這個對象的實例以後,繼續看doRefer方法; 注意:這裏的ClusterAdaptive也並不單純,大家還記得在講擴展點的時候有一個擴展點裝飾器嗎?如果這個擴展點存在一個構造函數,並且構造函數就是擴展接口本身,那麼這個擴展點就會這個wrapper裝飾,而Cluster被裝飾的是:MockClusterWrapper

public class Cluster$Adaptive implements com.alibaba.dubbo.rpc.cluster.Cluster {
    public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("cluster", "failover");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
        com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
        return extension.join(arg0);
    }
}

RegistryProtocol.doRefer
這段代碼中,有一個RegistryDirectory,可能看不懂,我們暫時先忽略,等會單獨講.(基於註冊中心動態發現服務提供者)

  1. 將consumer://協議地址註冊到註冊中心
  2. 訂閱zookeeper地址的變化
  3. 調用cluster.join()方法
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
    if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
            Constants.PROVIDERS_CATEGORY 
            + "," + Constants.CONFIGURATORS_CATEGORY 
            + "," + Constants.ROUTERS_CATEGORY));
    return cluster.join(directory);
}

cluster.join
由前面的Cluster$Adaptive這個類中的join方法的分析,得知cluster.join會調用MockClusterWrapper.join方法, 然後再調用FailoverCluster.join方法。
MockClusterWrapper.join
這個意思很明顯了。也就是我們上節課講過的mock容錯機制,如果出現異常情況,會調用MockClusterInvoker,否則,調用FailoverClusterInvoker.

public class MockClusterWrapper implements Cluster {

   private Cluster cluster;

   public MockClusterWrapper(Cluster cluster) {
      this.cluster = cluster;
   }

   public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
      return new MockClusterInvoker<T>(directory,
            this.cluster.join(directory));
   }
}

小結
refprotocol.ref,這個方法,會返回一個MockClusterInvoker(FailoverClusterInvoker)。這裏面一定還有疑問,我們先把主線走完,再回過頭看看什麼是cluster、什麼是directory
proxyFactory.getProxy(invoker);
再回到ReferenceConfig這個類,在createProxy方法的最後一行,調用proxyFactory.getProxy(invoker). 把前面生成的invoker對象作爲參數,再通過proxyFactory工廠去獲得一個代理對象。接下來我們分析下這段代碼做了什麼。
其實前面在分析服務發佈的時候,基本分析過了,所以再看這段代碼,應該會很熟悉
ProxyFactory, 會生成一個動態的自適應適配器。ProxyFactory$Adaptive,然後調用這個適配器中的getProxy方法,代碼如下

public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }

很顯然,又是通過javassist實現的一個動態代理,我們來看看JavassistProxyFactory.getProxy
JavassistProxyFactory.getProxy
通過javasssist動態字節碼生成動態代理類,

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

proxy.getProxy(interfaces)
在Proxy.getProxy這個類的如下代碼中添加斷點,在debug下可以看到動態字節碼如下

public java.lang.String sayHello(java.lang.String arg0){
  Object[] args = new Object[1]; 
  args[0] = ($w)$1; 
  Object ret = handler.invoke(this, methods[0], args); 
return (java.lang.String)ret;
}

上面紅色部分代碼的handler,就是在JavassistProxyFactory.getProxy中。傳遞的new InvokerInvocationHandler(invoker)
看到這裏,

服務調用過程

什麼時候和服務端建立連接呢?

前面我們通過代碼分析到了,消費端的初始化過程,但是似乎沒有看到客戶端和服務端建立NIO連接。實際上,建立連接的過程在消費端初始化的時候就建立好的,只是前面我們沒有分析,代碼在RegistryProtocol.doRefer方法內的directory.subscribe方法中。

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
    if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
            Constants.PROVIDERS_CATEGORY 
            + "," + Constants.CONFIGURATORS_CATEGORY 
            + "," + Constants.ROUTERS_CATEGORY));
    return cluster.join(directory);
}

directory.subscribe
調用鏈爲: RegistryDirectory.subscribe ->FailbackRegistry. subscribe->- AbstractRegistry.subscribe>zookeeperRegistry.doSubscribe

public void subscribe(URL url) {
    setConsumerUrl(url);
    registry.subscribe(url, this);
}

FailbackRegistry. subscribe
調用FailbackRegistry.subscribe 進行訂閱,這裏有一個特殊處理,如果訂閱失敗,則會添加到定時任務中進行重試

@Override
public void subscribe(URL url, NotifyListener listener) {
    super.subscribe(url, listener);
    removeFailedSubscribed(url, listener);
    try {
        // 向服務器端發送訂閱請求
        doSubscribe(url, listener);

zookeeperRegistry. doSubscribe
調用zookeeperRegistry執行真正的訂閱操作,這段代碼太長,我就不貼出來了,這裏面主要做兩個操作
1.對providers/routers/configurator三個節點進行創建和監聽
2.調用notify(url,listener,urls) 將已經可用的列表進行通知

AbstractRegistry.notify

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    if (url == null) {
        throw new IllegalArgumentException("notify url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("notify listener == null");
    }
    if ((urls == null || urls.size() == 0) 
            && ! Constants.ANY_VALUE.equals(url.getServiceInterface())) {
        logger.warn("Ignore empty notify urls for subscribe url " + url);
        return;
    }
    if (logger.isInfoEnabled()) {
        logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
    }
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    for (URL u : urls) {
        if (UrlUtils.isMatch(url, u)) {
           String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
           List<URL> categoryList = result.get(category);
           if (categoryList == null) {
              categoryList = new ArrayList<URL>();
              result.put(category, categoryList);
           }
           categoryList.add(u);
        }
    }
    if (result.size() == 0) {
        return;
    }
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        saveProperties(url);
        listener.notify(categoryList);
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

服務端接收消息處理過程

NettyHandler. messageReceived
接收消息的時候,通過NettyHandler.messageReceived作爲入口。

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
    try {
        handler.received(channel, e.getMessage());
    } finally {
        NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
    }
}

handler.received
這個handler是什麼呢?還記得在服務發佈的時候,組裝了一系列的handler嗎?代碼如下
HeaderExchanger.bind## 什麼是Directory?

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

NettyServer
接着又在Nettyserver中,wrap了多個handler

public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
    super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
    return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
            .getAdaptiveExtension().dispatch(handler, url)));
}

所以服務端的handler處理鏈爲

MultiMessageHandler(HeartbeatHandler(AllChannelHandler(DecodeHandler)))
MultiMessageHandler: 複合消息處理
HeartbeatHandler:心跳消息處理,接收心跳併發送心跳響應
AllChannelHandler:業務線程轉化處理器,把接收到的消息封裝成ChannelEventRunnable可執行任務給線程池處理
DecodeHandler:業務解碼處理器

HeaderExchangeHandler.received

交互層請求響應處理,有三種處理方式

  1. handlerRequest,雙向請求
  2. handler.received 單向請求
  3. handleResponse 響應消息
public void received(Channel channel, Object message) throws RemotingException {
    channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
    ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
    try {
        if (message instanceof Request) {
            // handle request.
            Request request = (Request) message;
            if (request.isEvent()) {
                handlerEvent(channel, request);
            } else {
                if (request.isTwoWay()) {
                    Response response = handleRequest(exchangeChannel, request);
                    channel.send(response);
                } else {
                    handler.received(exchangeChannel, request.getData());
                }
            }
        } else if (message instanceof Response) {
            handleResponse(channel, (Response) message);
        } else if (message instanceof String) {
            if (isClientSide(channel)) {
                Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
                logger.error(e.getMessage(), e);
            } else {
                String echo = handler.telnet(channel, (String) message);
                if (echo != null && echo.length() > 0) {
                    channel.send(echo);
                }
            }
        } else {
            handler.received(exchangeChannel, message);
        }
    } finally {
        HeaderExchangeChannel.removeChannelIfDisconnected(channel);
    }
}

handleRequest
處理請求並返回response

Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException {
    Response res = new Response(req.getId(), req.getVersion());
    if (req.isBroken()) {
        Object data = req.getData();
        String msg;
        if (data == null) msg = null;
        else if (data instanceof Throwable) msg = StringUtils.toString((Throwable) data);
        else msg = data.toString();
        res.setErrorMessage("Fail to decode request due to: " + msg);
        res.setStatus(Response.BAD_REQUEST);

        return res;
    }
    // find handler by message class.
    Object msg = req.getData();
    try {
        // handle data.
        Object result = handler.reply(channel, msg);
        res.setStatus(Response.OK);
        res.setResult(result);
    } catch (Throwable e) {
        res.setStatus(Response.SERVICE_ERROR);
        res.setErrorMessage(StringUtils.toString(e));
    }
    return res;
}

ExchangeHandlerAdaptive.replay(DubboProtocol)
調用DubboProtocol中定義的ExchangeHandlerAdaptive.replay方法處理消息

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
    
    public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
     invoker.invoke(inv);
}

那接下來invoker.invoke會調用哪個類中的方法呢?還記得在RegistryDirectory中發佈本地方法的時候,對invoker做的包裝嗎?通過InvokerDelegete對原本的invoker做了一層包裝,而原本的invoker是什麼呢?是一個JavassistProxyFactory生成的動態代理吧。所以此處的invoker應該是
Filter(Listener(InvokerDelegete(AbstractProxyInvoker (Wrapper.invokeMethod)))
RegistryDirectory生成invoker的代碼如下

private <T> ExporterChangeableWrapper<T>  doLocalExport(final Invoker<T> originInvoker){
    String key = getCacheKey(originInvoker);
    ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
    if (exporter == null) {
        synchronized (bounds) {
            exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
            if (exporter == null) {
                final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);
                bounds.put(key, exporter);
            }
        }
    }
    return (ExporterChangeableWrapper<T>) exporter;
}

Directory

集羣目錄服務Directory, 代表多個Invoker, 可以看成List,它的值可能是動態變化的比如註冊中心推送變更。集羣選擇調用服務時通過目錄服務找到所有服務
StaticDirectory: 靜態目錄服務, 它的所有Invoker通過構造函數傳入, 服務消費方引用服務的時候, 服務對多註冊中心的引用,將Invokers集合直接傳入 StaticDirectory構造器,再由Cluster僞裝成一個Invoker;StaticDirectory的list方法直接返回所有invoker集合;
RegistryDirectory: 註冊目錄服務, 它的Invoker集合是從註冊中心獲取的, 它實現了NotifyListener接口實現了回調接口notify(List)

Directory目錄服務的更新過程

RegistryProtocol.doRefer方法,也就是消費端在初始化的時候,這裏涉及到了RegistryDirectory這個類。然後執行cluster.join(directory)方法。這些代碼在上節課有分析過。
cluster.join其實就是將Directory中的多個Invoker僞裝成一個Invoker, 對上層透明,包含集羣的容錯機制

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);//對多個invoker進行組裝
    directory.setRegistry(registry); //ZookeeperRegistry
    directory.setProtocol(protocol); //protocol=Protocol$Adaptive
    //url=consumer://192.168.111....
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
    //會把consumer://192...  註冊到註冊中心
    if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        //zkClient.create()
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
            Constants.PROVIDERS_CATEGORY 
            + "," + Constants.CONFIGURATORS_CATEGORY 
            + "," + Constants.ROUTERS_CATEGORY));
    //Cluster$Adaptive
    return cluster.join(directory);
}

directory.subscribe

訂閱節點的變化

  1. 當zookeeper上指定節點發生變化以後,會通知到RegistryDirectory的notify方法
  2. 將url轉化爲invoker對象

調用過程中invokers的使用

再調用過程中,AbstractClusterInvoker.invoke方法中,

public Result invoke(final Invocation invocation) throws RpcException {

    checkWhetherDestroyed();

    LoadBalance loadbalance;
    
    List<Invoker<T>> invokers = list(invocation); 
    if (invokers != null && invokers.size() > 0) {
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                .getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
    } else {
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
    }
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    return doInvoke(invocation, invokers, loadbalance);
}

list方法

從directory中獲得invokers

protected  List<Invoker<T>> list(Invocation invocation) throws RpcException {
   List<Invoker<T>> invokers = directory.list(invocation);
   return invokers;
}

什麼是Cluster?

LoadBalance

LoadBalance負載均衡, 負責從多個 Invokers中選出具體的一個Invoker用於本次調用,調用過程中包含了負載均衡的算法。

負載均衡代碼訪問入口

在AbstractClusterInvoker.invoke中代碼如下,通過名稱獲得指定的擴展點。RandomLoadBalance

public Result invoke(final Invocation invocation) throws RpcException {

    checkWhetherDestroyed();

    LoadBalance loadbalance;
    
    List<Invoker<T>> invokers = list(invocation);
    if (invokers != null && invokers.size() > 0) {
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                .getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
    } else {
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
    }
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    return doInvoke(invocation, invokers, loadbalance);
}

AbstractClusterInvoker.doselect

調用LoadBalance.select方法,講invokers按照指定算法進行負載

private Invoker<T> doselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
    if (invokers == null || invokers.size() == 0)
        return null;
    if (invokers.size() == 1)
        return invokers.get(0);
    // 如果只有兩個invoker,退化成輪循
    if (invokers.size() == 2 && selected != null && selected.size() > 0) {
        return selected.get(0) == invokers.get(0) ? invokers.get(1) : invokers.get(0);
    }
    Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
    
    //如果 selected中包含(優先判斷) 或者 不可用&&availablecheck=true 則重試.
    if( (selected != null && selected.contains(invoker))
            ||(!invoker.isAvailable() && getUrl()!=null && availablecheck)){
        try{
            Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
            if(rinvoker != null){
                invoker =  rinvoker;
            }else{
                //看下第一次選的位置,如果不是最後,選+1位置.
                int index = invokers.indexOf(invoker);
                try{
                    //最後在避免碰撞
                    invoker = index <invokers.size()-1?invokers.get(index+1) :invoker;
                }catch (Exception e) {
                    logger.warn(e.getMessage()+" may because invokers list dynamic change, ignore.",e);
                }
            }
        }catch (Throwable t){
            logger.error("clustor relselect fail reason is :"+t.getMessage() +" if can not slove ,you can set cluster.availablecheck=false in url",t);
        }
    }
    return invoker;
} 

默認情況下,LoadBalance使用的是Random算法,但是這個隨機和我們理解上的隨機還是不一樣的,因爲他還有個概念叫weight(權重)

RandomLoadBalance

假設有四個集羣節點A,B,C,D,對應的權重分別是1,2,3,4,那麼請求到A節點的概率就爲1/(1+2+3+4) = 10%.B,C,D節點依次類推爲20%,30%,40%.

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    int length = invokers.size(); // 總個數
    int totalWeight = 0; // 總權重
    boolean sameWeight = true; // 權重是否都一樣
    for (int i = 0; i < length; i++) {
        int weight = getWeight(invokers.get(i), invocation);
        totalWeight += weight; // 累計總權重
        if (sameWeight && i > 0
                && weight != getWeight(invokers.get(i - 1), invocation)) {
            sameWeight = false; // 計算所有權重是否一樣
        }
    }
    if (totalWeight > 0 && ! sameWeight) {
        // 如果權重不相同且權重大於0則按總權重數隨機
        int offset = random.nextInt(totalWeight);
        // 並確定隨機值落在哪個片斷上
        for (int i = 0; i < length; i++) {
            offset -= getWeight(invokers.get(i), invocation);
            if (offset < 0) {
                return invokers.get(i);
            }
        }
    }
    // 如果權重相同或權重爲0則均等隨機
    return invokers.get(random.nextInt(length));
}

後記

關於Dubbo自適應擴展點:
在這裏插入圖片描述
實際上,XX$Adeptive 適配器 是根據不同的協議,動態生成代碼,來適配相應的Protocol的,這也是Dubbo裏常見的技術手段~

dubbo源碼講到這裏就完結了,最後再給一張官方經典圖:
在這裏插入圖片描述

更多架構知識,歡迎關注本套Java系列文章-地址導航Java架構師成長之路

Dubbo中文註釋版:DubboV2.5.4下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章