Dubbo源碼學習14

本篇幅主要分析DubboProtocol.refer方法創建invoker

DubboProtocol.refer(Class<T> serviceType, URL url)

@Override
    public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
        //序列化優化
        optimizeSerialization(url);
        //創建invoker
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);
        return invoker;
    }

上述方法創建了一個DubboInvoker,Invoker 是 Dubbo 的核心模型,代表一個可執行體。在服務提供方,Invoker 用於調用服務提供類。在服務消費方,Invoker 用於執行遠程調用。這裏有個方法的調用getClients(url)方法節目組金瓶梅掛牌,創建ExchangeClient實例(單個或者多個),ExchangeClient基於NettyClient與NettyServer進行通信。

getClients(URL url)

private ExchangeClient[] getClients(URL url) {
        // 是否共享連接
        boolean service_share_connect = false;
        // 獲取連接數,默認爲0
        int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
        // 如果未配置,則共享連接,否則,一項服務的一個連接
        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) {
                //獲取共享的ExchangeClient
                clients[i] = getSharedClient(url);
            } else {
                //初始化新的客戶端
                clients[i] = initClient(url);
            }
        }
        return clients;
    }

根據url中的sessions數量判斷是否創建共享連接,如果需要創建共享連接則通過getSharedClient獲取共享連接,如果客戶端未建立,則創建客戶端。

getSharedClient(URL url)

private ExchangeClient getSharedClient(URL url) {
        String key = url.getAddress();
        //獲取帶有引用計數功能的ExchangeClient
        ReferenceCountExchangeClient client = referenceClientMap.get(key);
        //獲取到了並且client未被關閉增加引用計數
        if (client != null) {
            if (!client.isClosed()) {
                client.incrementAndGetCount();
                return client;
            } else {
                //客戶端被關閉了,根據key刪除掉吧
                referenceClientMap.remove(key);
            }
        }
        //放入該key對應的鎖對象
        locks.putIfAbsent(key, new Object());
        //加鎖重新獲取
        synchronized (locks.get(key)) {
            if (referenceClientMap.containsKey(key)) {
                return referenceClientMap.get(key);
            }
            //通過url初始化exchangeClient
            ExchangeClient exchangeClient = initClient(url);
            //包裝exchangeClient爲帶有引用計數功能的
            client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
            //加入緩存
            referenceClientMap.put(key, client);
            ghostClientMap.remove(key);
            //鎖刪除
            locks.remove(key);
            return client;
        }
    }

該方法首先從referenceClientMap緩存中獲取ReferenceCountExchangeClient即帶有引用計數功能的ExchangeClient實例,否則通過調用initClient方法初始化ExchangeClient,然後通過裝飾器將創建的exchangeClient包裝爲ReferenceCountExchangeClient對象加入緩存中,並返回ReferenceCountExchangeClient。

initClient(URL url)

private ExchangeClient initClient(URL url) {

        // 獲取客戶端的類型默認值爲netty
        String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
        // 添加編解碼器與心跳配置
        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

        // BIO is not allowed since it has severe performance issue.
        //檢測客戶端類型是否存在,不存在則拋出異常
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported client type: " + str + "," +
                    " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
        }
        //
        ExchangeClient client;
        try {
            // 獲取 lazy 配置,並根據配置值決定創建的客戶端類型
            if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
                client = new LazyConnectExchangeClient(url, requestHandler);
            } else {
                // 創建普通 ExchangeClient 實例
                client = Exchangers.connect(url, requestHandler);
            }
        } catch (RemotingException e) {
            throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
        }
        return client;
    }

initClient 方法首先獲取用戶配置的客戶端類型,默認爲 netty。然後檢測用戶配置的客戶端類型是否存在,不存在則拋出異常。最後根據 lazy 配置決定創建什麼類型的客戶端。

Exchangers.connect(URL url, ExchangeHandler handler)

public static ExchangeClient connect(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");
        //獲取到HeaderExchageClient
        return getExchanger(url).connect(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);
    }

getExchanger 會通過 SPI 加載 HeaderExchangeClient 實例

HeaderExchanger.connect(URL url,ExchangeHandler handler)

@Override
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

handler的包裝處理:DubboProtocol.ExchangeHandler->HeaderExchangeHandler->DecodeHandler,通過Transporters.connect方法創建ExchangeClient對象實例,包裝ExchangeClient對象爲HeaderExchangeClient對象實例

Transporters.connect(URL url,ChannelHandler... handlers)

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

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

通過dubbo的spi機制獲取到NettyTransporter,然後調用connect方法,創建NettyClient對象

NettyTransporter.connect(URL url, ChannelHandler listener)

@Override
    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }

NettyClient.java

  • ChannelHandler:主要是處理channel的,比如channel關閉、連接,發送消息,接收消息等操作
  • Resetable:該接口實現類可以實現例如Client相關屬性的重置
  • AbstractPeer:主要是保存了服務提供這協議的url和通過委託成員變量ChannelHandler實現了ChannelHandler接口的方法和EndPoint接口的方法
 /**
     * channel事件處理器
     */
    private final ChannelHandler handler;
    /**
     * 第一個服務提供者協議的url地址
     */
    private volatile URL url;
  • Channel:Netty爲了統一實現不同類型NIO框架的框架對Channel處理而做出的抽象接口
  • Client:約定客戶端必須要實現Client接口的reconnect方法
  • AbstractEndpoint:繼承AbstractPeer並實現了Resetable接口reset方法,使得我們可以AbstractPeer的Url屬性重置編碼器、超時時間、連接超時時間
  /**
     * 編碼解碼器
     */
    private Codec2 codec;
    /**
     *  超時時間
     */
    private int timeout;
    /**
     * 連接超時時間
     */
    private int connectTimeout;
  • AbstractClient:該類實現了Client接口和Channel接口的方法,Mina、Netty、Grizzly類型的客戶端統一實現,並且該類重寫了AbstractEndPoint的reset方法
/**
     * 客戶端線程池ID自增器
     */
    private static final AtomicInteger CLIENT_THREAD_POOL_ID = new AtomicInteger();
    /**
     * 客戶端連接重連線程池
     */
    private static final ScheduledThreadPoolExecutor reconnectExecutorService = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("DubboClientReconnectTimer", true));
    /**
     * 客戶端連接服務端獨佔鎖,保證一個客戶端同時只會一個線程在執行連接動作
     */
    private final Lock connectLock = new ReentrantLock();
    /**
     *  消息發送時,如果當前客戶端未連接,是否發起重連操作
     */
    private final boolean send_reconnect;
    /**
     * 記錄重連的次數
     */
    private final AtomicInteger reconnect_count = new AtomicInteger(0);
    // Reconnection error log has been called before?
    /**
     * 連接出錯後是否打印過ERROR日誌
     */
    private final AtomicBoolean reconnect_error_log_flag = new AtomicBoolean(false);
    // reconnect warning period. Reconnect warning interval (log warning after how many times) //for test
    /**
     * 對連接異常,以WARN級別日誌輸出的頻率,默認第一次是以Error日誌,然後每出現reconnect_warning_period次後,就打印一次warn級別日誌
     */
    private final int reconnect_warning_period;
    /**
     * 關閉服務的超時時間
     */
    private final long shutdown_timeout;
    /**
     * 客戶端線程池
     */
    protected volatile ExecutorService executor;
    /**
     * 重連的Future
     */
    private volatile ScheduledFuture<?> reconnectExecutorFuture = null;
    // the last successed connected time
    /**
     * 上一次重連時間戳
     */
    private long lastConnectedTime = System.currentTimeMillis();
public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
        //調用父類方法初始化url、handler
        super(url, handler);
        /**
         * 初始化send_reconnect 、shutdown_timeout、reconnect_warning_period(默認1小時打印一次日誌)
         */
        send_reconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);

        shutdown_timeout = url.getParameter(Constants.SHUTDOWN_TIMEOUT_KEY, Constants.DEFAULT_SHUTDOWN_TIMEOUT);
        reconnect_warning_period = url.getParameter("reconnect.waring.period", 1800);

        try {
            //模板方法,委託子類實現,此方法還未真正的連接到服務端
            doOpen();
        } catch (Throwable t) {
            close();
            throw new RemotingException(url.toInetSocketAddress(), null,
                    "Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                            + " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
        }
        try {
            // 這裏會連接nettyServer服務器
            connect();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());
            }
        } catch (RemotingException t) {
            if (url.getParameter(Constants.CHECK_KEY, true)) {
                close();
                throw t;
            } else {
                logger.warn("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                        + " connect to the server " + getRemoteAddress() + " (check == false, ignore and retry later!), cause: " + t.getMessage(), t);
            }
        } catch (Throwable t) {
            close();
            throw new RemotingException(url.toInetSocketAddress(), null,
                    "Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                            + " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
        }
        //從DataStore中獲取線程池
        executor = (ExecutorService) ExtensionLoader.getExtensionLoader(DataStore.class)
                .getDefaultExtension().get(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
        //從DataStore中移除線程池
        ExtensionLoader.getExtensionLoader(DataStore.class)
                .getDefaultExtension().remove(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
    }
  • a.調用父類構造函數初始化handler、和url
  • b.初始化send_reconnect 、shutdown_timeout、reconnect_warning_period(默認1小時打印一次日誌)
  • c.委託子類實現doOpen方法初始化客戶端
  • d.connect()方法,真正建立TCP連接,其主要邏輯是開啓重連任務,然後委託不同的客戶端實現類型實現doConnect()方法打開TCP連接,設置重連次數和重連標識。
protected void connect() throws RemotingException {
        //加鎖
        connectLock.lock();
        try {
            //已經連接成功返回
            if (isConnected()) {
                return;
            }
            //重連任務調度開啓
            initConnectStatusCheckCommand();
            //委託子類實現真正的連接邏輯
            doConnect();
            //還未連接到服務器
            if (!isConnected()) {
                throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
                        + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
                        + ", cause: Connect wait timeout: " + getConnectTimeout() + "ms.");
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("Successed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
                            + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
                            + ", channel is " + this.getChannel());
                }
            }
            //設置重連次數爲0
            reconnect_count.set(0);
            //設置重連error log標識爲false
            reconnect_error_log_flag.set(false);
        } catch (RemotingException e) {
            throw e;
        } catch (Throwable e) {
            throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "
                    + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()
                    + ", cause: " + e.getMessage(), e);
        } finally {
            connectLock.unlock();
        }
    }
  • e.從DataStore中獲取線程池,然後從DataStore中移除該線程池
  • NettyClient:繼承AbstractClient,真正的客戶端類,主要負責與服務端通信。
/**
     * IO線程組,同一個JVM中所有的客戶端公用一個IO線程組,且線程數固定爲(32與CPU核數+1的最小值)。
     */
    private static final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(Constants.DEFAULT_IO_THREADS, new DefaultThreadFactory("NettyClientWorker", true));
    /**
     * Netty客戶端啓動實例
     */
    private Bootstrap bootstrap;
    /**
     * 客戶端連接,請copy其引用使用。
     */
    private volatile Channel channel;
public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
        super(url, wrapChannelHandler(url, handler));
    }

不難看出,NettyClient的構造函數主要是調用父類AbstractClient的構造函數進行屬性初始化

AbstractClient.wrapChannelHandler(URL url,ChannelHandler handler)

protected static ChannelHandler wrapChannelHandler(URL url, ChannelHandler handler) {
        //在url添加threadname屬性
        url = ExecutorUtil.setThreadName(url, CLIENT_THREAD_POOL_NAME);
        //在url添加threadpool屬性值爲cached
        url = url.addParameterIfAbsent(Constants.THREADPOOL_KEY, Constants.DEFAULT_CLIENT_THREADPOOL);
        return ChannelHandlers.wrap(handler, url);
    }

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)));
    }

Dubbo源碼學習07可知我們的chandler的整體包裝流程: MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler ->  DecodeHandler ->  HeaderExchangeHandler -> DubboProtocol.requestHandler

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

NettyClient.doOpen()

@Override
    protected void 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);
            }
        });
    }

標準的NettyClient的啓動模板,創建NettyClientHandler,配置客戶端啓動實例的屬性,設置連接超時時間,最小連接超時時間爲3s,初始化ChannelInitializer添加編解碼器,NettyClientHandler

NettyClient.doConnect()

@Override
    protected void doConnect() throws Throwable {
        long start = System.currentTimeMillis();
        //連接到遠程服務
        ChannelFuture future = bootstrap.connect(getConnectAddress());
        try {
            boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS);

            if (ret && future.isSuccess()) {
                Channel newChannel = future.channel();
                try {
                    // Close old channel
                    //關閉oldchannel
                    Channel oldChannel = NettyClient.this.channel; // copy reference
                    if (oldChannel != null) {
                        try {
                            if (logger.isInfoEnabled()) {
                                logger.info("Close old netty channel " + oldChannel + " on create new netty channel " + newChannel);
                            }
                            oldChannel.close();
                        } finally {
                            NettyChannel.removeChannelIfDisconnected(oldChannel);
                        }
                    }
                } finally {
                    //如果當前客戶端被關閉了,關閉newChannel
                    if (NettyClient.this.isClosed()) {
                        try {
                            if (logger.isInfoEnabled()) {
                                logger.info("Close new netty channel " + newChannel + ", because the client closed.");
                            }
                            newChannel.close();
                        } finally {
                            NettyClient.this.channel = null;
                            NettyChannel.removeChannelIfDisconnected(newChannel);
                        }
                    } else {
                        //將newChannel賦值給client的channel
                        NettyClient.this.channel = newChannel;
                    }
                }
            } else if (future.cause() != null) {
                throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
                        + getRemoteAddress() + ", error message is:" + future.cause().getMessage(), future.cause());
            } else {
                throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
                        + getRemoteAddress() + " client-side timeout "
                        + getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client "
                        + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion());
            }
        } finally {
            if (!isConnected()) {
                //future.cancel(true);
            }
        }
    }

調用doOpen方法創建的bootstrap實例發起連接,等待個connecTimeout超時時間去連接到服務端,如果連接成功:關閉以前的channel,判斷客戶端是否已經關閉,如果已經關閉,調用channel.close()方法關閉channel。

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