Dubbo 延遲與粘滯連接

前言

大家好,今天開始給大家分享 — Dubbo 專題之 Dubbo 延遲和粘滯連接。在前一個(gè)章節(jié)中我們介紹了 Dubbo 并發(fā)控制,Dubbo 為我們提供兩大類的配置:消費(fèi)端的配置和服務(wù)提供端配置,我們分別可以對(duì)服務(wù)提供端和服務(wù)消費(fèi)端進(jìn)行并發(fā)數(shù)量的控制。同時(shí)我們也例舉了常見的使用場(chǎng)景并且進(jìn)行了源碼解析來(lái)分析其實(shí)現(xiàn)原理。有的小伙伴學(xué)習(xí)了并發(fā)控制可能會(huì)想到:如果我們的服務(wù)消費(fèi)端有大量的服務(wù)需要引用,那我們的 Dubbo 應(yīng)用程序可能啟動(dòng)相當(dāng)?shù)木徛湓蚴牵寒?dāng)我們消費(fèi)端應(yīng)用啟動(dòng)的時(shí)候需要獲取遠(yuǎn)程服務(wù)的代理對(duì)象的引用,如果我們每一個(gè)獲取的遠(yuǎn)程代理對(duì)象都在啟動(dòng)的時(shí)候創(chuàng)建連接,這樣必定會(huì)影響我們的應(yīng)用程序啟動(dòng),幸好我們的 Dubbo 提供一種配置的方式解決這個(gè)問(wèn)題。那同時(shí)也延伸出另外一個(gè)問(wèn)題,就是我們對(duì)某個(gè)服務(wù)的調(diào)用能不能一直分配到上次調(diào)用的服務(wù)提供者呢?帶著這些疑問(wèn)我們開始本章節(jié)學(xué)習(xí),我們會(huì)通過(guò)介紹什么是延遲和粘滯連接?怎樣通過(guò)參數(shù)配置改變默認(rèn)行為?來(lái)解決這些問(wèn)題。下面就讓我們快速開始吧!

1. 延遲和粘滯連接簡(jiǎn)介

通過(guò)前面對(duì) Dubbo 相關(guān)章節(jié)的介紹我相信大家應(yīng)該有個(gè)基本的概念就是我們 Dubbo 中服務(wù)消費(fèi)方持有服務(wù)提供端的服務(wù)引用,這個(gè)引用又通過(guò)層層的代理最終通過(guò)我們的 TCP/IP (底層使用Netty進(jìn)行網(wǎng)絡(luò)通訊)與遠(yuǎn)程服務(wù)端進(jìn)行通訊。在 Dubbo 中為了優(yōu)化消費(fèi)端獲取服務(wù)提供端的引用對(duì)象時(shí)候創(chuàng)建底層的物理連接,比如在與 Spring 集成中我們的引用對(duì)象需要通過(guò) Spring 容器進(jìn)行對(duì)外發(fā)布 Bean 實(shí)例,然而此時(shí)我們并沒有真正的使用代理對(duì)象,只是將代理對(duì)象交給 Spring 管理。為了使代理對(duì)象在真正使用的時(shí)候才去創(chuàng)建底層的物理從而減少底層連接的創(chuàng)建和釋放這就叫做延遲連接。同理粘滯連接的意思就是盡可能讓客戶端總是向同一提供者發(fā)起調(diào)用,除非該提供者掛了,再連另一臺(tái)。下圖簡(jiǎn)單的描述了粘滯連接在第二次、第三次調(diào)用服務(wù)的時(shí)候調(diào)用原服務(wù)提供者:

粘滯連接

2. 配置方式

下面我們主要通過(guò) XML 和 注解的方式進(jìn)行配置介紹:

2.1 延遲連接:

  1. XML 方式
<dubbo:reference id="bookFacade"
                     interface="com.muke.dubbocourse.common.api.BookFacade" lazy="true" ></dubbo:reference>
  1. 注解方式
@Reference(lazy = true)

2.2 粘滯連接

  1. XML 方式
<dubbo:reference id="bookFacade" interface="com.muke.dubbocourse.common.api.BookFacade" sticky="true" />

方法級(jí)別控制:

<dubbo:reference id="xxxService" interface="com.muke.dubbocourse.common.api.BookFacade">
    <dubbo:mothod name="queryAll" sticky="true" />
</dubbo:reference>
  1. 注解方式
@Reference(sticky = true)

從上面的配置中我們可以簡(jiǎn)單的總結(jié):延遲連接通過(guò)lazy進(jìn)行配置,粘滯連接使用sticky進(jìn)行配置。

3. 使用場(chǎng)景

根據(jù)前面的介紹我們大概理解了什么是延遲和粘滯連接。其中延遲連接就是為了減少無(wú)用的連接而在真正使用對(duì)象時(shí)候才創(chuàng)建連接,而粘滯連接是為了多次調(diào)用都盡可能地使用同一個(gè)服務(wù)提供者也有減少服務(wù)連接創(chuàng)建的作用。下面我們簡(jiǎn)單的介紹幾種常見使用場(chǎng)景:

  1. 當(dāng)我們的服務(wù)消費(fèi)端需要大量的引用服務(wù)提供者或者創(chuàng)建遠(yuǎn)程連接成本非常高(一般指耗時(shí)時(shí)間)時(shí)我們可以考慮開啟延遲連接。

  2. 假設(shè)我們的應(yīng)用有大量的靜態(tài)數(shù)據(jù)需要加載到應(yīng)用本地緩存( JVM 緩存)時(shí)當(dāng)?shù)谝淮握{(diào)用Service A進(jìn)行緩存加載,那么在第二次調(diào)用的時(shí)候我們期望也調(diào)用剛才已經(jīng)存在緩存的服務(wù) Service A 這樣提高了服務(wù)的訪問(wèn)速度。這種場(chǎng)景可以使用粘滯連接。

  3. 如果我們的應(yīng)用調(diào)用過(guò)程存在某種狀態(tài),例如:調(diào)用服務(wù) Service A 進(jìn)行用戶登錄返回 token,那第二次調(diào)用查詢用戶信息的時(shí)候需要根據(jù)攜帶的token來(lái)查詢用戶的登錄狀態(tài),此時(shí)如果訪問(wèn) Service A 那么用戶登錄的 token 信息是存的,如果訪問(wèn)到 Service B 這時(shí)就不存在 token (假設(shè)這里沒有使用分布式緩存)。這種場(chǎng)景可以使用粘滯連接。

4. 示例演示

下面我以獲取圖書列表為例進(jìn)行演示。項(xiàng)目結(jié)構(gòu)如下:

idea

我們的延遲連接主要配置在服務(wù)消費(fèi)端dubbo-consumer-xml.xml配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-consumer" logger="log4j"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!--lazy="true"延遲連接-->
    <dubbo:reference id="bookFacade"
                     interface="com.muke.dubbocourse.common.api.BookFacade" lazy="true" ></dubbo:reference>

</beans>

通過(guò)lazy="true"配置消費(fèi)端引用服務(wù)提供者服務(wù)時(shí)開啟延遲連接。下面我們繼續(xù)看看粘滯連接配置:

    <!--sticky="true"開啟粘滯連接 -->
    <dubbo:reference id="bookFacade"
                     interface="com.muke.dubbocourse.common.api.BookFacade" sticky="true" ></dubbo:reference>

通過(guò)sticky="true"配置消費(fèi)端引用服務(wù)提供者服務(wù)時(shí)開啟粘滯連接。

Tips:這里在演示粘滯連接的時(shí)候小伙伴們?cè)诓渴饝?yīng)用的時(shí)候至少需要部署兩個(gè)或以上的實(shí)例才能看出效果。如果我們開啟粘滯連接那么我們可以看到總是訪問(wèn)同一個(gè)服務(wù)提供者。

5. 實(shí)現(xiàn)原理

下面我們通過(guò)源碼的方式簡(jiǎn)單的分析它們的實(shí)現(xiàn)原理。

首先是延遲連接其核心方法org.apache.dubbo.rpc.protocol.dubbo. DubboProtocol#protocolBindingRefer代碼如下:

    /***
     *
     * 協(xié)議綁定并創(chuàng)建連接
     *
     * @author liyong
     * @date 16:11 2020-03-08
     * @param serviceType
     * @param url
     * @exception
     * @return org.apache.dubbo.rpc.Invoker<T>
     **/
    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        optimizeSerialization(url);

        // 創(chuàng)建DubboInvoker并且創(chuàng)建網(wǎng)絡(luò)連接
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }

這里的 DubboInvoker 是我們遠(yuǎn)程 RPC 調(diào)用的封裝,這里org.apache.dubbo.rpc.protocol.dubbo. DubboProtocol#getClients方法創(chuàng)建客戶端連接并且綁定請(qǐng)求處理器核心代碼如下:

/***
     *
     * 獲取客戶端連接,并且綁定了請(qǐng)求處理器
     *
     * @author liyong
     * @date 16:55 2020-03-08
     * @param url
     * @exception
     * @return org.apache.dubbo.remoting.exchange.ExchangeClient[]
     **/
    private ExchangeClient[] getClients(URL url) {
        //...
        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            if (useShareConnect) {
                clients[i] = shareClients.get(i);//共享連接

            } else {
                clients[i] = initClient(url);//不共享 新建連接
            }
        }

        return clients;
    }

其中initClient方法創(chuàng)建連接核心代碼如下:

      private ExchangeClient initClient(URL url) {

        //...

        ExchangeClient client;
        try {
            //是否延遲連接 lazy="true"
            if (url.getParameter(LAZY_CONNECT_KEY, false)) {
                //這里并沒有創(chuàng)建連接對(duì)象
                client = new LazyConnectExchangeClient(url, requestHandler);

            } else {
                //HeaderExchangeClient連接到服務(wù)器并綁定請(qǐng)求處理器
                client = Exchangers.connect(url, requestHandler);
            }

        } catch (RemotingException e) {
           //...
        }

        return client;
    }

根據(jù)我們的配置獲取LAZY_CONNECT_KEY參數(shù)(lazy)的值,當(dāng)我們配置為true時(shí)創(chuàng)建LazyConnectExchangeClient對(duì)象,為false是創(chuàng)建物理連接。

下面繼續(xù)討論粘滯連接核心方法org.apache.dubbo.rpc.cluster.support. AbstractClusterInvoker#select如下:

protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {

        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        String methodName = invocation == null ? StringUtils.EMPTY_STRING : invocation.getMethodName();

        //獲取粘滯連接配置
        boolean sticky = invokers.get(0).getUrl()
                .getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY);

        //ignore overloaded method
        if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
            stickyInvoker = null;
        }
        //開啟粘滯連接配置 且存在已經(jīng)創(chuàng)建的stickyInvoker粘滯連接
        if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
            //有效性檢測(cè)
            if (availablecheck && stickyInvoker.isAvailable()) {
                return stickyInvoker;
            }
        }

        //根據(jù)負(fù)載均衡策略進(jìn)行服務(wù)選擇
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);

        if (sticky) {
            //如果配置粘滯連接為true 則當(dāng)前選擇的Invoker保存在stickyInvoker粘滯連接變量
            stickyInvoker = invoker;
        }
        return invoker;
    }

從上面的代碼我們可以看出從 URL 中獲取 sticky 配置判斷是否開啟粘滯連接,如果開啟那么在第一次獲取 Invoker 時(shí) stickyInvokernull 創(chuàng)建一個(gè) Invoker 代理對(duì)象,當(dāng)?shù)诙潍@取 Invoker 時(shí)會(huì)判斷是否存在 stickyInvoker 如果存在且沒有被排除則繼續(xù)使用前面保存下來(lái)的 Invoker 代理對(duì)象也就是stickyInvoker。

Tips:這里 URL 包括我們配置的 XML 或注解配置中的參數(shù)、通訊協(xié)議、序列化方式等等參數(shù),可以參考前面章節(jié)詳細(xì)描述。

6. 小結(jié)

在本小節(jié)中我們主要學(xué)習(xí)了 Dubbo 延遲和粘滯連接,同時(shí)我們也分析了延遲和粘滯連接的實(shí)現(xiàn)原理。延遲連接本質(zhì)上是延遲 Dubbo 底層物理連接的創(chuàng)建,而粘滯連接本質(zhì)上是重復(fù)利用已創(chuàng)建的連接。

本節(jié)課程的重點(diǎn)如下:

  1. 理解 Dubbo 延遲和粘滯連接

  2. 了解了延遲和粘滯連接使用方式

  3. 了解延延遲和粘滯連接使用場(chǎng)景

  4. 了解延遲和粘滯連接實(shí)現(xiàn)原理

作者

個(gè)人從事金融行業(yè),就職過(guò)易極付、思建科技、某網(wǎng)約車平臺(tái)等重慶一流技術(shù)團(tuán)隊(duì),目前就職于某銀行負(fù)責(zé)統(tǒng)一支付系統(tǒng)建設(shè)。自身對(duì)金融行業(yè)有強(qiáng)烈的愛好。同時(shí)也實(shí)踐大數(shù)據(jù)、數(shù)據(jù)存儲(chǔ)、自動(dòng)化集成和部署、分布式微服務(wù)、響應(yīng)式編程、人工智能等領(lǐng)域。同時(shí)也熱衷于技術(shù)分享創(chuàng)立公眾號(hào)和博客站點(diǎn)對(duì)知識(shí)體系進(jìn)行分享。關(guān)注公眾號(hào):青年IT男 獲取最新技術(shù)文章推送!

博客地址: http://youngitman.tech

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容