Dubbo源碼學習07

RegistryProtocol.export服務導出流程:導出服務ExporterChangeableWrapper->註冊服務到註冊中心->訂閱註冊中心overrideSubscribeUrl數據;篇幅有限,本篇幅主要分析導出服務ExporterChangeableWrapper源碼實現

RegistryProtocol.export(final Invoker<T> originInvoker)

@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        //導出服務
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
        // 獲取註冊中心 URL,以 zookeeper 註冊中心爲例,得到的示例 URL 如下:
        // zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
        URL registryUrl = getRegistryUrl(originInvoker);

        //registry provider
        // 根據 URL 加載 Registry 實現類,比如 ZookeeperRegistry
        final Registry registry = getRegistry(originInvoker);
        // 獲取已註冊的服務提供者 URL,比如:
        // dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
        final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);

        //to judge to delay publish whether or not
        //獲取register參數;register表示是否註冊到註冊中心
        boolean register = registeredProviderUrl.getParameter("register", true);
        //緩存到ProviderConsumerRegTable的表中
        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
        //註冊服務到zookeeper
        if (register) {
            register(registryUrl, registeredProviderUrl);
            //找到該originInvoker對應的ProviderInvokerWrapper設置reg屬性爲true
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
        // FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因爲subscribed以服務名爲緩存的key,導致訂閱信息覆蓋
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
        //創建監聽器
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        //放入overrideSubscribeUrl對應的OverrideListener
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        // 向註冊中心進行訂閱 override 數據
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        //Ensure that a new exporter instance is returned every time export
        //創建並返回DestroyableExporter
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
    }

該方法核心代碼可以簡化爲

@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //1.導出invoker;
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
        final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
        ....
        boolean register = registeredProviderUrl.getParameter("register", true);
        //2.註冊服務到zookeeper
        if (register) {
            register(registryUrl, registeredProviderUrl);
        }
        ....
        //3.向註冊中心進行訂閱overrideSubscribeUrl
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
    }

doLocalExport(final Invoker<T> originInvoker)

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
        //獲取originInvoker對應的緩存key
        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) {
                    //創建invoker委託類對象
                    final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                    //調用protocol的export方法導出服務
                    //這個時候返回的url肯定是類似dubbo://....這樣子的所以protocol
                    //由於dubbo的spi機制,此時肯定是通過DubboProtocol的export方法
                    exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
                    bounds.put(key, exporter);
                }
            }
        }
        return exporter;
    }

通過originInvoker獲取緩存key,然後就是dubbo慣用的緩存伎倆,從緩存中獲取,如果沒有成功獲取到,調用成員變量protocol的export方法導出invoker,這裏的protocol還是Protocol$Adaptive,所以又會經過https://blog.csdn.net/qq_23536449/article/details/102639888文中描述的

QosProtocolWrapper,ProtocolFilterWrapper,ProtocolListenerWrapper,通過debug代碼可以清楚看到執行流程:

  • QosProtocolWrapper

  • ProtocolFilterWrapper

  • ProtocolListenerWrapper

  • 最後會進入到DubboProtocol的export方法中,下文會分析

getCacheKey(originInvoker)

private String getCacheKey(final Invoker<?> originInvoker) {
        URL providerUrl = getProviderUrl(originInvoker);
        String key = providerUrl.removeParameters("dynamic", "enabled").toFullString();
        return key;
    }

/**
     * Get the address of the providerUrl through the url of the invoker
     *
     * 通過調用者的地址獲取providerUrl的地址
     * @param origininvoker
     * @return
     */
    private URL getProviderUrl(final Invoker<?> origininvoker) {
        String export = origininvoker.getUrl().getParameterAndDecoded(Constants.EXPORT_KEY);
        if (export == null || export.length() == 0) {
            throw new IllegalArgumentException("The registry export url is null! registry: " + origininvoker.getUrl());
        }

        URL providerUrl = URL.valueOf(export);
        return providerUrl;
    }
  • key類似:dubbo://ip:port/com.alibaba.dubbo.study.day01.xml.service.EchoService?addListener.1.callback=true&addListener.retries=2&anyhost=true&application=echo-provider&bean.name=com.alibaba.dubbo.study.day01.xml.service.EchoService&bind.ip=169.254.22.149&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.study.day01.xml.service.EchoService&methods=echo,addListener&pid=3600&side=provider&timestamp=1572674213180

DubboProtocol.export(Invoker<T> invoker)

 @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // export service.
        // 獲取服務標識,理解成服務座標也行。由服務組名,服務名,服務版本號以及端口組成。比如:
        // demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
        String key = serviceKey(url);
        // 創建dubbo的exporter
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        // 放入緩存中
        exporterMap.put(key, exporter);
        //export an stub service for dispatching event
        //導出存根服務以調度事件
        Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }
        //啓動服務器
        openServer(url);
        //優化序列化
        optimizeSerialization(url);
        return exporter;
    }

構造DubboExporter,放入緩存;存根服務處理(這裏可以略過);啓動服務器;優化序列化(非重點關心)

openServer(url);

private void openServer(URL url) {
        // 獲取 host:port,並將其作爲服務器實例的 key,用於標識當前的服務器實例
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        //客戶端可導出僅用作服務調用的服務
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
        if (isServer) {
            //獲取address對應的server,
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                //創建服務器實例並放入緩存中
                serverMap.put(key, createServer(url));
            } else {
                // server supports reset, use together with override
                // 服務器已創建,則根據 url 中的配置重置服務器
                server.reset(url);
            }
        }
    }

如上,在同一臺機器上(單網卡),同一個端口上僅允許啓動一個服務器實例。若某個端口上已有服務器實例,此時則調用 reset 方法重置服務器的一些配置,讓我們先來分析下創建服務器實例方法。

createServer(URL url)

private ExchangeServer createServer(URL url) {
        // send readonly event when server closes, it's enabled by default
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
        // enable heartbeat by default
        // 添加心跳檢測配置到url中
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
        // 獲取server參數
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
        // 通過 SPI 檢測是否存在 server 參數所代表的 Transporter 拓展,不存在則拋出異常
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        // 添加編碼解碼器參數
        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
        ExchangeServer server;
        try {
            //創建ExchangeServer
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
        /**
         * 獲取client代表的Transporter是否存在,如果不存在拋出異常好了
         */
        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            // 獲取所有的 Transporter 實現類名稱集合,比如 supportedTypes = [netty, mina]
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            // 檢測當前 Dubbo 所支持的 Transporter 實現類名稱列表中,
            // 是否包含 client 所表示的 Transporter,若不包含,則拋出異常
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
    }

添加默認參數,然後檢測指定的server類型(netty,minal,gizzly)的Transproter是否存在,使用Exchangers創建的bind方法創建server實例,檢測是否支持 client 參數所表示的 Transporter 拓展,不存在也是拋出異常。

Exchangers.bind(url, requestHandler)

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        return getExchanger(url).bind(url, handler);
    }
public static Exchanger getExchanger(URL url) {
        //獲取url中的exchanger屬性,沒有使用默認的header
        String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
        return getExchanger(type);
    }
public static Exchanger getExchanger(String type) {
        //dubbo的spi機制
        return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
    }

通過Exchanger命名理解設計他的初衷,交換層,輸入url和一個handler,輸出server(比如類型爲HeaderExchangeServer),然後dubbo本身支持spi擴展,所以exchanger也支持擴展(dubbo默認實現了一個擴展名稱爲header的Exchager即下文的HeaderExchanger)。

HeaderExchanger.bind(URL url, ExchangeHandler handler)

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

調用Transporters.bind方法創建Server(包裝handler爲DecodeHandler),類型可以爲Netty、Mina、Grizzly,使用HeaderExchageServer裝飾創建出來的Server,HeaderExchangeServer爲Server提供了發送空閒channel檢測、channel重連的功能

Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))

public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handlers == null || handlers.length == 0) {
            throw new IllegalArgumentException("handlers == null");
        }
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        return getTransporter().bind(url, handler);
    }

public static Transporter getTransporter() {
        return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
    }

通過spi獲取獲取不同類型的Transporter,默認是NettyTransporter,至於爲什麼請看Transporter$Adaptive的bind方法

package com.alibaba.dubbo.remoting;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter {
    public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }

    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.bind(arg0, arg1);
    }
}

NettyTransporter.bind(URL url, ChannelHandler listener)

@Override
    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

創建一個Netty類型的服務實例!!!NettyServer的類繼承關係圖如下

  • ChannelHandler:主要是處理channel的,比如channel關閉、連接,發送消息,接收消息等操作
  • Resetable:該接口實現類可以實現例如Server相關屬性的重置
  • AbstractPeer:主要是保存了服務提供這協議的url和通過委託成員變量ChannelHandler實現了ChannelHandler接口的方法和EndPoint接口的方法
/**
     * channel事件處理器
     */
    private final ChannelHandler handler;
    /**
     * 第一個服務提供者協議的url地址
     */
    private volatile URL url;
  • AbstractEndPoint:實現了Resetable接口reset方法,使得我們可以重置編碼器、超時時間、連接超時時間
/**
     * 編碼解碼器
     */
    private Codec2 codec;
    /**
     *  超時時間
     */
    private int timeout;
    /**
     * 連接超時時間
     */
    private int connectTimeout;
  • AbstractServer:該類實現了Server接口,作爲Mina、Netty、Grizzly類型的服務端統一實現,並且該類重寫了AbstractEndPoint的reset方法
 ExecutorService executor;
    /**
     * url host:port地址。
     */
    private InetSocketAddress localAddress;
    /**
     * 如果是多網卡,並且指定了 bind.ip、bind.port,如果爲空,與localAddress相同。
     */
    private InetSocketAddress bindAddress;
    /**
     *  AbstractServer#accepts未使用到。
     */
    private int accepts;
    /**
     * 空閒時間
     */
    private int idleTimeout = 600; //600 seconds
  • NettyServer:服務端實現類
/**
     * < ip:port, channel> 所有通道。
     */
    private Map<String, Channel> channels; // <ip:port, channel>
    /**
     * netty 服務端啓動器。
     */
    private ServerBootstrap bootstrap;
    /**
     * 服務端監聽通道。
     */
    private io.netty.channel.Channel channel;
    /**
     * Netty boss線程組(負責連接事件)
     */
    private EventLoopGroup bossGroup;
    /**
     *  work線程組(負責IO事件)
     */
    private EventLoopGroup workerGroup;
  • 回顧下上在DubboProtocol.openServer(URL url)代碼中的如下代碼片段可知其真正的實現在AbstractServer中。

AbstractServer.reset(URL url)

@Override
    public void reset(URL url) {
        if (url == null) {
            return;
        }
        try {
            //url中是否包含accepts屬性,存在並且大於0則重置Server的accepts
            if (url.hasParameter(Constants.ACCEPTS_KEY)) {
                int a = url.getParameter(Constants.ACCEPTS_KEY, 0);
                if (a > 0) {
                    this.accepts = a;
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
        try {
            //url中是否包含idle.timeout屬性,存在並且大於0則重置Server的idle.timeout
            if (url.hasParameter(Constants.IDLE_TIMEOUT_KEY)) {
                int t = url.getParameter(Constants.IDLE_TIMEOUT_KEY, 0);
                if (t > 0) {
                    this.idleTimeout = t;
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
        try {
            //url是否包含threads屬性,並且executor是ThreadPoolExecutor的實例並且線程池未被關閉
            if (url.hasParameter(Constants.THREADS_KEY)
                    && executor instanceof ThreadPoolExecutor && !executor.isShutdown()) {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                //url中的threads數量
                int threads = url.getParameter(Constants.THREADS_KEY, 0);
                //線程池最大容量
                int max = threadPoolExecutor.getMaximumPoolSize();
                //線程池核心線程數
                int core = threadPoolExecutor.getCorePoolSize();
                if (threads > 0 && (threads != max || threads != core)) {
                    if (threads < core) {
                        threadPoolExecutor.setCorePoolSize(threads);
                        if (core == max) {
                            threadPoolExecutor.setMaximumPoolSize(threads);
                        }
                    } else {
                        threadPoolExecutor.setMaximumPoolSize(threads);
                        if (core == max) {
                            threadPoolExecutor.setCorePoolSize(threads);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
        //合併url中的參數並設置給AbstractPeer的url屬性
        super.setUrl(getUrl().addParameters(url.getParameters()));
    }

/**
     * Add parameters to a new url.
     *
     * @param parameters parameters in key-value pairs
     * @return A new URL
     */
    public URL addParameters(Map<String, String> parameters) {
        if (parameters == null || parameters.size() == 0) {
            return this;
        }
        //判斷當前URL實例的parameter是否與參數中的parameters
        //是否相同,包括鍵和值,不全相同返回false
        boolean hasAndEqual = true;
        for (Map.Entry<String, String> entry : parameters.entrySet()) {
            String value = getParameters().get(entry.getKey());
            if (value == null) {
                if (entry.getValue() != null) {
                    hasAndEqual = false;
                    break;
                }
            } else {
                if (!value.equals(entry.getValue())) {
                    hasAndEqual = false;
                    break;
                }
            }
        }
        // return immediately if there's no change
        //沒有變化直接返回當前url
        if (hasAndEqual) return this;
        //如果有變化合並並且返回新的map
        Map<String, String> map = new HashMap<String, String>(getParameters());
        map.putAll(parameters);
        return new URL(protocol, username, password, host, port, path, map);
    }

覆蓋NettyServer的accepts、idleTimeout、threadPoolExecutor的配置;通過調用AbstractPeer的setUrl方法設置新的Url成員變量的值。

HeaderExchangeServer.reset(URL url)

@Override
    public void reset(URL url) {
        server.reset(url);
        try {
            //url中是否包含heartbeat或者heartbeat.timeout屬性
            if (url.hasParameter(Constants.HEARTBEAT_KEY)
                    || url.hasParameter(Constants.HEARTBEAT_TIMEOUT_KEY)) {
                int h = url.getParameter(Constants.HEARTBEAT_KEY, heartbeat);
                int t = url.getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, h * 3);
                if (t < h * 2) {
                    throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
                }
                if (h != heartbeat || t != heartbeatTimeout) {
                    heartbeat = h;
                    heartbeatTimeout = t;
                    startHeartbeatTimer();
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
    }
//發送心跳
private void startHeartbeatTimer() {
        stopHeartbeatTimer();
        if (heartbeat > 0) {
            heartbeatTimer = scheduled.scheduleWithFixedDelay(
                    new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
                        @Override
                        public Collection<Channel> getChannels() {
                            return Collections.unmodifiableCollection(
                                    HeaderExchangeServer.this.getChannels());
                        }
                    }, heartbeat, heartbeatTimeout),
                    heartbeat, heartbeat, TimeUnit.MILLISECONDS);
        }
    }
    //停止心跳那啥
    private void stopHeartbeatTimer() {
        try {
            ScheduledFuture<?> timer = heartbeatTimer;
            if (timer != null && !timer.isCancelled()) {
                timer.cancel(true);
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        } finally {
            heartbeatTimer = null;
        }
    }

可以看到HeaderExchageServer其實是在NettyServer的基礎上增加了下面兩個功能見HeartBeatTask.java

  • a. 對channel進行 空閒時間檢測,超過則關閉連接,節省資源。
  • b. 如果server關閉,則發送消息給client端,不再發送請求到該server。

HeartBeatTask.run

@Override
    public void run() {
        try {
            long now = System.currentTimeMillis();
            for (Channel channel : channelProvider.getChannels()) {
                if (channel.isClosed()) {
                    continue;
                }
                try {
                    //channel上次讀數據的時間戳
                    Long lastRead = (Long) channel.getAttribute(
                            HeaderExchangeHandler.KEY_READ_TIMESTAMP);
                    //上次寫數據的時間戳
                    Long lastWrite = (Long) channel.getAttribute(
                            HeaderExchangeHandler.KEY_WRITE_TIMESTAMP);
                    //讀的時間戳不爲null並且空閒讀時間大於心跳
                    if ((lastRead != null && now - lastRead > heartbeat)
                            //寫的時間戳不爲null並且空閒寫時間大於心跳
                            || (lastWrite != null && now - lastWrite > heartbeat)) {
                        /**
                         * 發送心跳數據
                         */
                        Request req = new Request();
                        req.setVersion(Version.getProtocolVersion());
                        req.setTwoWay(true);
                        req.setEvent(Request.HEARTBEAT_EVENT);
                        channel.send(req);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Send heartbeat to remote channel " + channel.getRemoteAddress()
                                    + ", cause: The channel has no data-transmission exceeds a heartbeat period: " + heartbeat + "ms");
                        }
                    }
                    //空閒讀時間戳不爲null並且空閒讀大於心跳超時時間
                    if (lastRead != null && now - lastRead > heartbeatTimeout) {
                        logger.warn("Close channel " + channel
                                + ", because heartbeat read idle time out: " + heartbeatTimeout + "ms");
                        //channel是Client則重新連接
                        if (channel instanceof Client) {
                            try {
                                ((Client) channel).reconnect();
                            } catch (Exception e) {
                                //do nothing
                            }
                        //否則關閉channel
                        } else {
                            channel.close();
                        }
                    }
                } catch (Throwable t) {
                    logger.warn("Exception when heartbeat to remote channel " + channel.getRemoteAddress(), t);
                }
            }
        } catch (Throwable t) {
            logger.warn("Unhandled exception when heartbeat, cause: " + t.getMessage(), t);
        }
    }

NettyServer.java

  • 構造函數代碼如下:通過super關鍵字調用父類方法,解析url中的參數給AbstractServer、AbstractPeer、AbstractEndpoint屬性賦值,值得注意的是AbstractServer中的doOpen()方法,該方法是個模板方法,子類實現doOpen方法啓動服務器
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }

public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();

        String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
        int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
        if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
            bindIp = NetUtils.ANYHOST;
        }
        bindAddress = new InetSocketAddress(bindIp, bindPort);
        //設置可接受最大連接數
        this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
        //連接空閒時間
        this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
        try {
            doOpen();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
            }
        } catch (Throwable t) {
            throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
                    + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
        }
        //fixme replace this with better method
        DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
        executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
    }

ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))

public static ChannelHandler wrap(ChannelHandler handler, URL url) {
        return ChannelHandlers.getInstance().wrapInternal(handler, url);
    }

protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
        return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
                .getAdaptiveExtension().dispatch(handler, url)));
    }

Dispatcher通過dubbo的spi機制最終爲AllDispatcher,我們來看下Dispatcher$Adaptive的代碼就能知曉原因

package com.alibaba.dubbo.remoting;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Dispatcher$Adaptive implements com.alibaba.dubbo.remoting.Dispatcher {
    public com.alibaba.dubbo.remoting.ChannelHandler dispatch(com.alibaba.dubbo.remoting.ChannelHandler arg0, com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = url.getParameter("dispatcher", url.getParameter("dispather", url.getParameter("channel.handler", "all")));
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Dispatcher) name from url(" + url.toString() + ") use keys([dispatcher, dispather, channel.handler])");
        com.alibaba.dubbo.remoting.Dispatcher extension = (com.alibaba.dubbo.remoting.Dispatcher) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Dispatcher.class).getExtension(extName);
        return extension.dispatch(arg0, arg1);
    }
}

AllDispatcher.dispatch(ChannelHandler handler,URL url)

/**
 * default thread pool configure
 */
public class AllDispatcher implements Dispatcher {

    public static final String NAME = "all";

    @Override
    public ChannelHandler dispatch(ChannelHandler handler, URL url) {
        return new AllChannelHandler(handler, url);
    }

}

handler的包裝流程如下

DubboProtocol.createServer
    #此時requestHandler爲DubboProtocol中的匿名內部類實現ExchangeHandlerAdapter
    ->Exchangers.bind(url,requestHandler) 
        #該方法對handler做了兩次包裝ExchangeHandlerAdapter->HeaderExchangeHandler->DecodeHandler
        ->HeaderExchanger.bind(url,handler)
            #到這裏handler已經爲DecodeHandler了
            ->NettyServer(url,handler)
                #該方法首先通過dubbo的spi機制將handler包裝爲AllChannelHandler->HeartbeatHandler->MultiMessageHandler
                ->ChannelHandlers.wrapInternal(handler,url)

 MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler ->  DecodeHandler ->  HeaderExchangeHandler -> DubboProtocol.requestHandler

  • MultiMessageHandler:檢查消息是否爲MutiMessage ,如果 是,分開單條調用後續handler
  • HeartbeatHanlder:1.在每個channel動作,對channel標記時間屬性, 2. 檢查是否心跳請求,是則直接返回心跳,不繼續後續請求。
  •  AllChannelHandler : 1. 將後續handler 包裝成 ChannelEventRunnable,捕獲後續執行的異常,記錄日誌 。 2. 包裝的runnable 放到獨立線程池運行, 達到全流程異步化效果。
  • DecodeHandler:判斷message的種類然後解碼

Netty.doOpen()

@Override
    protected void doOpen() throws Throwable {
        bootstrap = new ServerBootstrap();

        bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
        workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
                new DefaultThreadFactory("NettyServerWorker", true));

        final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
        channels = nettyServerHandler.getChannels();

        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                        ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                                .addLast("decoder", adapter.getDecoder())
                                .addLast("encoder", adapter.getEncoder())
                                .addLast("handler", nettyServerHandler);
                    }
                });
        // bind
        ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
        channelFuture.syncUninterruptibly();
        channel = channelFuture.channel();

    }

Netty服務端啓動的標準模板:主要關注的點就是添加的編碼器、解碼器、NettyServerHandler。

目前的handler包裝鏈如下:

NettyServerHandler->NettyServer-> MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler ->  DecodeHandler ->  HeaderExchangeHandler -> DubboProtocol.requestHandler

NettyCodecAdapter.java

  • 成員變量如下
//內部編碼
private final ChannelHandler encoder = new InternalEncoder();
//內部解碼器
private final ChannelHandler decoder = new InternalDecoder();
//AbstractEndpoint中的編解碼屬性codec怎麼獲取的參考AbstractEndpoit.getChannelCodec(URL url)
private final Codec2 codec;

 

InternalEncoder.java

private class InternalEncoder extends MessageToByteEncoder {

        @Override
        protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
            com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out);
            Channel ch = ctx.channel();
            NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);
            try {
                codec.encode(channel, buffer, msg);
            } finally {
                NettyChannel.removeChannelIfDisconnected(ch);
            }
        }
    }

委託給編解碼器codec處理

InternalDecoder.java

private class InternalDecoder extends ByteToMessageDecoder {

        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {

            ChannelBuffer message = new NettyBackedChannelBuffer(input);

            NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);

            Object msg;

            int saveReaderIndex;

            try {
                // decode object.
                do {
                    saveReaderIndex = message.readerIndex();
                    try {
                        msg = codec.decode(channel, message);
                    } catch (IOException e) {
                        throw e;
                    }
                    if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
                        message.readerIndex(saveReaderIndex);
                        break;
                    } else {
                        //is it possible to go here ?
                        if (saveReaderIndex == message.readerIndex()) {
                            throw new IOException("Decode without read data.");
                        }
                        if (msg != null) {
                            out.add(msg);
                        }
                    }
                } while (message.readable());
            } finally {
                NettyChannel.removeChannelIfDisconnected(ctx.channel());
            }
        }
    }

委託給編碼器codec處理

codec是怎麼獲取到的?

在doOpen中有以下代碼getCodec()

 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);

getCodec();該方法通過父類AbstractEndpoint繼承而來,我們看下codec在AbstractEndpoint的初始化邏輯

protected static Codec2 getChannelCodec(URL url) {
        //獲取url中的codec屬性默認值爲telnet
        String codecName = url.getParameter(Constants.CODEC_KEY, "telnet");
        //如果Codec2的SPI實現類包含名稱爲codecName編解碼器的擴展直接加載返回
        if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
            return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
        } else {
            //加載Spi擴展Codec的名稱爲codecName的編解碼器
            return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
                    .getExtension(codecName));
        }
    }

未完待續....

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