那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Dubbo路由模塊及負(fù)載均衡的優(yōu)化手段

OSC開(kāi)源社區(qū) ? 來(lái)源: vivo互聯(lián)網(wǎng)技術(shù) ? 2023-11-02 09:56 ? 次閱讀

作者:vivo 互聯(lián)網(wǎng)中間件團(tuán)隊(duì)- Wang Xiaochuang

本文主要介紹在vivo內(nèi)部針對(duì)Dubbo路由模塊及負(fù)載均衡的一些優(yōu)化手段,主要是異步化+緩存,可減少在RPC調(diào)用過(guò)程中路由及負(fù)載均衡的CPU消耗,極大提升調(diào)用效率。

一、概要

vivo內(nèi)部Java技術(shù)棧業(yè)務(wù)使用的是Apache Dubbo框架,基于開(kāi)源社區(qū)2.7.x版本定制化開(kāi)發(fā)。在海量微服務(wù)集群的業(yè)務(wù)實(shí)踐中,我們發(fā)現(xiàn)Dubbo有一些性能瓶頸的問(wèn)題會(huì)極大影響業(yè)務(wù)邏輯的執(zhí)行效率,尤其是在集群規(guī)模數(shù)量較大時(shí)(提供方數(shù)量>100),路由及負(fù)載均衡方面有著較大的CPU消耗,從采集的火焰圖分析高達(dá)30%。為此我們針對(duì)vivo內(nèi)部常用路由策略及負(fù)載均衡進(jìn)行相關(guān)優(yōu)化,并取得了較好的效果。接下來(lái)主要跟大家分析一下相關(guān)問(wèn)題產(chǎn)生的根源,以及我們采用怎樣的方式來(lái)解決這些問(wèn)題。(當(dāng)前vivo內(nèi)部使用的Dubbo的主流版本是基于2.7.x進(jìn)行相關(guān)定制化開(kāi)發(fā)。)

二、背景知識(shí)

2.1 Dubbo客戶(hù)端調(diào)用流程

1.相關(guān)術(shù)語(yǔ)介紹

fc6a6922-78a4-11ee-939d-92fbcf53809c.png

2.主要流程

客戶(hù)端通過(guò)本地代理Proxy調(diào)用ClusterInvoker,ClusterInvoker從服務(wù)目錄Directory獲取服務(wù)列表后經(jīng)過(guò)路由鏈獲取新的服務(wù)列表、負(fù)載均衡從路由后的服務(wù)列表中根據(jù)不同的負(fù)載均衡策略選取一個(gè)遠(yuǎn)端Invoker后再發(fā)起遠(yuǎn)程RPC調(diào)用。

fc8083e2-78a4-11ee-939d-92fbcf53809c.png

2.2 Dubbo路由機(jī)制

Dubbo的路由機(jī)制實(shí)際是基于簡(jiǎn)單的責(zé)任鏈模式實(shí)現(xiàn),同時(shí)Router繼承了Comparable接口,自定義的路由可以設(shè)置不同的優(yōu)先級(jí)進(jìn)而定制化責(zé)任鏈上Router的順序。基于責(zé)任鏈模式可以支持多種路由策略串行執(zhí)行如就近路由+標(biāo)簽路由,或條件路由+就近路由等,且路由的配置支持基于接口級(jí)的配置也支持基于應(yīng)用級(jí)的配置。常見(jiàn)的路由方式主要有:就近路由,條件路由,標(biāo)簽路由等。具體的執(zhí)行過(guò)程如下圖所示:

fca0573a-78a4-11ee-939d-92fbcf53809c.png

1. 核心類(lèi)

Dubbo路由的核心類(lèi)主要有:RouterChain、RouterFactory 與 Router 。

(1)RouterChain

RouterChain是路由鏈的入口,其核心字段有

invokers(List 類(lèi)型)

初始服務(wù)列表由服務(wù)目錄Directory設(shè)置,當(dāng)前RouterChain要過(guò)濾的Invoker集合

builtinRouters(List類(lèi)型)

當(dāng)前RouterChain包含的自動(dòng)激活的Router集合

routers(List類(lèi)型)

包括所有要使用的路由由builtinRouters加上通過(guò)addRouters()方法添加的Router對(duì)象

RouterChain核心邏輯

public class RouterChain {


    // 注冊(cè)中心最后一次推送的服務(wù)列表
    private List> invokers = Collections.emptyList();


    // 所有路由,包括原生Dubbo基于注冊(cè)中心的路由規(guī)則如“route://” urls .
    private volatile List routers = Collections.emptyList();


    // 初始化自動(dòng)激活的路由
    private List builtinRouters = Collections.emptyList();
  
   private RouterChain(URL url) {
    //通過(guò)ExtensionLoader加載可自動(dòng)激活的RouterFactory
        List extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
                .getActivateExtension(url, ROUTER_KEY);
    // 由工廠(chǎng)類(lèi)生成自動(dòng)激活的路由策略
        List routers = extensionFactories.stream()
                .map(factory -> factory.getRouter(url))
                .collect(Collectors.toList());


        initWithRouters(routers);
    }
  
    // 添加額外路由
    public void addRouters(List routers) {
        List newRouters = new ArrayList<>();
        newRouters.addAll(builtinRouters);
        newRouters.addAll(routers);
        Collections.sort(newRouters, comparator);
        this.routers = newRouters;
    }
  
   public List> route(URL url, Invocation invocation) {
        List> finalInvokers = invokers;
    // 遍歷全部的Router對(duì)象,執(zhí)行路由規(guī)則
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }
}

(2)RouterFactory為Router的工廠(chǎng)類(lèi)

RouterFactory接口定義

@SPI
public interface RouterFactory {


    @Adaptive("protocol")
    Router getRouter(URL url);
}

(3)Router

Router是真正的路由實(shí)現(xiàn)策略,由RouterChain進(jìn)行調(diào)用,同時(shí)Router繼承了Compareable接口,可以根據(jù)業(yè)務(wù)邏輯設(shè)置不同的優(yōu)先級(jí)。

Router主要接口定義

public interface Router extends Comparable {


   
    /**
     *
     * @param invokers   帶過(guò)濾實(shí)例列表
     * @param url        消費(fèi)方url
     * @param invocation 會(huì)話(huà)信息
     * @return routed invokers
     * @throws RpcException
     */
     List> route(List> invokers, URL url, Invocation invocation) throws RpcException;




    /**
     * 當(dāng)注冊(cè)中心的服務(wù)列表發(fā)現(xiàn)變化,或有動(dòng)態(tài)配置變更會(huì)觸發(fā)實(shí)例信息的變化
     * 當(dāng)時(shí)2.7.x的Dubbo并沒(méi)有真正使用這個(gè)方法,可基于此方法進(jìn)行路由緩存
     * @param invokers invoker list
     * @param       invoker's type
     */
    default  void notify(List> invokers) {


    }


}

2.同機(jī)房?jī)?yōu)先路由的實(shí)現(xiàn)

為方便大家了解路由的實(shí)現(xiàn),給大家展示一下就近路由的核心代碼邏輯

public  List> route(List> invokers, URL consumerUrl, Invocation invocation) throws RpcException {
        if (!this.enabled) {
            return invokers;
        }


      
    // 獲取本地機(jī)房信息
        String local = getSystemProperty(LOC);
        if (invokers == null || invokers.size() == 0) {
            return invokers;
        }
        List> result = new ArrayList>();
        for (Invoker invoker: invokers) {
      // 獲取與本地機(jī)房一致的invoker并加入列表中
            String invokerLoc = getProperty(invoker, invocation, LOC);
            if (local.equals(invokerLoc)) {
                result.add(invoker);
            }
        }


        if (result.size() > 0) {
            if (fallback){
                // 開(kāi)啟服務(wù)降級(jí),available.ratio = 當(dāng)前機(jī)房可用服務(wù)節(jié)點(diǎn)數(shù)量 / 集群可用服務(wù)節(jié)點(diǎn)數(shù)量
                int curAvailableRatio = (int) Math.floor(result.size() * 100.0d / invokers.size());
                if (curAvailableRatio <= availableRatio) {
                    return invokers;
                }
            }


            return result;
        } else if (force) {
            return result;
        } else {
            return invokers;
        }


    }

2.3 Dubbo負(fù)載均衡

Dubbo的負(fù)載均衡實(shí)現(xiàn)比較簡(jiǎn)單基本都是繼承抽象類(lèi)進(jìn)行實(shí)現(xiàn),主要作用就是根據(jù)具體的策略在路由之后的服務(wù)列表中篩選一個(gè)實(shí)例進(jìn)行遠(yuǎn)程RPC調(diào)用,默認(rèn)的負(fù)載均衡策略是隨機(jī)。

整體類(lèi)圖如下所示:

fcaba144-78a4-11ee-939d-92fbcf53809c.jpg

LoadBalance接口定義

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {


    /**
     * 從服務(wù)列表中篩選一個(gè).
     *
     * @param invokers   invokers.
     * @param url        refer url
     * @param invocation invocation.
     * @return selected invoker.
     */
    @Adaptive("loadbalance")
     Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException;


}

隨機(jī)負(fù)載均衡核心代碼解析

   // 預(yù)熱過(guò)程權(quán)重計(jì)算
   static int calculateWarmupWeight(int uptime, int warmup, int weight) {
        int ww = (int) (uptime / ((float) warmup / weight));
        return ww < 1 ? 1 : (Math.min(ww, weight));
    }


int getWeight(Invoker invoker, Invocation invocation) {
       int weight;
       URL url = invoker.getUrl();
       // 多注冊(cè)中心場(chǎng)景下的,注冊(cè)中心權(quán)重獲取
       if (UrlUtils.isRegistryService(url)) {
           weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
       } else {
           weight = url.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
           if (weight > 0) {
               // 獲取實(shí)例啟動(dòng)時(shí)間
               long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);
               if (timestamp > 0L) {
                   long uptime = System.currentTimeMillis() - timestamp;
                   if (uptime < 0) {
                       return 1;
                   }
                   // 獲取預(yù)熱時(shí)間
                   int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
                   if (uptime > 0 && uptime < warmup) {
                       weight = calculateWarmupWeight((int)uptime, warmup, weight);
                   }
               }
           }
       }
       return Math.max(weight, 0);
   }


@Override
    protected  Invoker doSelect(List> invokers, URL url, Invocation invocation) {
        // Number of invokers
        int length = invokers.size();
        // Every invoker has the same weight?
        boolean sameWeight = true;
        // the weight of every invokers
        int[] weights = new int[length];
        // the first invoker's weight
        int firstWeight = getWeight(invokers.get(0), invocation);
        weights[0] = firstWeight;
        // The sum of weights
        int totalWeight = firstWeight;
        for (int i = 1; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            // save for later use
            weights[i] = weight;
            // Sum
            totalWeight += weight;
            if (sameWeight && weight != firstWeight) {
                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.
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            // Return a invoker based on the random value.
            for (int i = 0; i < length; i++) {
                offset -= weights[i];
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        return invokers.get(ThreadLocalRandom.current().nextInt(length));
    }

預(yù)熱解釋

預(yù)熱是為了讓剛啟動(dòng)的實(shí)例流量緩慢增加,因?yàn)閷?shí)例剛啟動(dòng)時(shí)各種資源可能還沒(méi)建立連接,相關(guān)代碼可能還是處于解釋執(zhí)行,仍未變?yōu)镴IT執(zhí)行,此時(shí)業(yè)務(wù)邏輯較慢,不應(yīng)該加載過(guò)大的流量,否則有可能造成較多的超時(shí)。Dubbo默認(rèn)預(yù)熱時(shí)間為10分鐘,新部署的實(shí)例的流量會(huì)在預(yù)熱時(shí)間段內(nèi)層線(xiàn)性增長(zhǎng),最終與其他實(shí)例保持一致。Dubbo預(yù)熱機(jī)制的實(shí)現(xiàn)就是通過(guò)控制權(quán)重來(lái)實(shí)現(xiàn)。如默認(rèn)權(quán)重100,預(yù)熱時(shí)間10分鐘,則第一分鐘權(quán)重為10,第二分鐘為20,以此類(lèi)推。

具體預(yù)熱效果圖如下:

fcc7b19a-78a4-11ee-939d-92fbcf53809c.png

三、問(wèn)題分析

使用Dubbo的業(yè)務(wù)方反饋,他們通過(guò)火焰圖分析發(fā)現(xiàn)Dubbo的負(fù)載均衡模塊+路由模塊占用CPU超過(guò)了30%,框架層面的使用率嚴(yán)重影響了業(yè)務(wù)邏輯的執(zhí)行效率急需進(jìn)行優(yōu)化。通過(guò)火焰圖分析,具體占比如下圖,其中該機(jī)器在業(yè)務(wù)忙時(shí)的CPU使用率在60%左右,閑時(shí)在30%左右。

fce07ff4-78a4-11ee-939d-92fbcf53809c.png

通過(guò)火焰圖分析,負(fù)載均衡主要的消耗是在 getWeight方法。

fcee495e-78a4-11ee-939d-92fbcf53809c.png

路由的主要消耗是在route方法:

同機(jī)房?jī)?yōu)先路由

fd10675a-78a4-11ee-939d-92fbcf53809c.png

接口級(jí)標(biāo)簽路由+應(yīng)用級(jí)標(biāo)簽路由

fd2d9672-78a4-11ee-939d-92fbcf53809c.png

這些方法都有一個(gè)特點(diǎn),那就是遍歷執(zhí)行。如負(fù)載均衡,針對(duì)每一個(gè)invoker都需要通過(guò)getWeight方法進(jìn)行權(quán)重的計(jì)算;就近路由的router方法對(duì)于每一個(gè)invoker都需要通過(guò)url獲取及機(jī)房信息進(jìn)行匹配計(jì)算。

我們分析一下getWeight及router時(shí)間復(fù)雜度,發(fā)現(xiàn)是O(n)的時(shí)間復(fù)雜度,而且路由是由路由鏈組成的,每次每個(gè) Router的route方法調(diào)用邏輯都會(huì)遍歷實(shí)例列表,那么當(dāng)實(shí)例列表數(shù)量過(guò)大時(shí),每次匹配的計(jì)算的邏輯過(guò)大,那么就會(huì)造成大量的計(jì)算成本,導(dǎo)致占用大量cpu,同時(shí)也導(dǎo)致路由負(fù)載均衡效率低下。

綜上所述,罪惡的的根源就是遍歷導(dǎo)致的,當(dāng)服務(wù)提供方數(shù)量越多,影響越大。

四、優(yōu)化方案

知道了問(wèn)題所在,我們來(lái)分析一下是否有優(yōu)化空間。

4.1 路由優(yōu)化

1. 優(yōu)化一:關(guān)閉無(wú)效路由

通過(guò)火焰圖分析,我們發(fā)現(xiàn)有部分業(yè)務(wù)即使完全不使用應(yīng)用級(jí)的標(biāo)簽路由,原生的TagRouter也存在遍歷邏輯,原因是為了支持靜態(tài)的標(biāo)簽路由,其實(shí)這部分的開(kāi)銷(xiāo)也不少,那對(duì)于根本不會(huì)使用應(yīng)用級(jí)標(biāo)簽路由的可以手動(dòng)進(jìn)行關(guān)閉。關(guān)閉方式如下:

客戶(hù)端統(tǒng)一關(guān)閉

dubbo.consumer.router=-tag

服務(wù)級(jí)別關(guān)閉

注解方式:

@DubboReference(parameters = {"router","-tag"})

xml方式:

2. 優(yōu)化二:提前計(jì)算路由結(jié)果并進(jìn)行緩存

每次路由目前都是進(jìn)行實(shí)時(shí)計(jì)算,但是在大多數(shù)情況下,我們的實(shí)例列表是穩(wěn)定不變的,只有在發(fā)布窗口或配置變更窗口內(nèi)實(shí)例列表才會(huì)發(fā)生變更,那我們是否可以考慮緩存呢。如就近路由,可以以機(jī)房為key進(jìn)行機(jī)房實(shí)例的全量緩存。針對(duì)接口級(jí)標(biāo)簽路由可以緩存不同標(biāo)簽值指定的實(shí)例信息。

我們知道路由的執(zhí)行過(guò)程是責(zé)任鏈模式,每一個(gè)Router的實(shí)例列表入?yún)?shí)際上是一個(gè)Router的結(jié)果,可參考公式:target = rn(…r3(r2(r1(src))))。那么所有的路由可以基于注冊(cè)中心推送的原始服務(wù)列表進(jìn)行路由計(jì)算并緩存,然后不同的路由結(jié)果相互取交集就能得到最終的結(jié)果,當(dāng)實(shí)例信息發(fā)生變更時(shí),緩存失效并重新計(jì)算。

3. 緩存更新時(shí)機(jī)

當(dāng)注冊(cè)中心或者動(dòng)態(tài)配置有變更時(shí),相關(guān)通知會(huì)給到服務(wù)目錄Directory,Directory收到通知后會(huì)重新創(chuàng)建服務(wù)列表,并把服務(wù)列表同步到路由鏈RouterChain,RouterChain再按順序通知其鏈上的Router,各個(gè)Router再進(jìn)行緩存清除并重新進(jìn)行路由結(jié)果的計(jì)算及進(jìn)行緩存。相關(guān)時(shí)序圖如下所示:

fd4c288a-78a4-11ee-939d-92fbcf53809c.png

4. 具體路由流程

進(jìn)入具體路由方法時(shí),先判斷是否存在緩存的路由值,且緩存值的epoch必須與上一個(gè)路由的epoch需一致,此時(shí)緩存才生效,然后緩存值與上個(gè)Router的結(jié)果取交集。

如果不存在緩存或epoch不一致則重新進(jìn)行實(shí)時(shí)的路由計(jì)算。

fd61c2d0-78a4-11ee-939d-92fbcf53809c.jpg

引入epoch的原因主要是保證各個(gè)路由策略緩存信息的一致性,保證所有的緩存計(jì)算都是基于同一份原始數(shù)據(jù)。當(dāng)實(shí)例信息發(fā)生變更時(shí),epoch會(huì)自動(dòng)進(jìn)行更新。

5. BitMap引入

上文我們說(shuō)到,不同的路由策略之間的結(jié)果是取交集的,然后最終的結(jié)果才送入負(fù)載均衡流程。那如何在緩存的同時(shí),加快交集的計(jì)算呢。答案就是基于位圖:BitMap。

BitMap的基本原理就是用一個(gè)bit位來(lái)存放某種狀態(tài),適用于大規(guī)模數(shù)據(jù)的查找及位運(yùn)算操作。如在路由場(chǎng)景,先基于全量的推送數(shù)據(jù)進(jìn)行計(jì)算緩存。如果某個(gè)實(shí)例被路由選中,則其值為1,若兩個(gè)路由的結(jié)果要取交集,那直接對(duì)BitMap進(jìn)行"&"運(yùn)行即可。

全量緩存示意圖:

fd7666fe-78a4-11ee-939d-92fbcf53809c.jpg

路由交集計(jì)算示步驟:

按照路由鏈依次計(jì)算,

tagRouter->vivoTag->vivoNearestRouter

(1)tagRouter計(jì)算邏輯:

按照Invocation計(jì)算出目標(biāo)的Tag,假設(shè)是tag1

然后從緩存Cache根據(jù)key:tag1,取出對(duì)應(yīng)的targetAddrPool

將原始傳入的addrPool

與targetAddrPool

得到結(jié)果resultAddrPool

將resultAddrPool傳入vivoTagRouter

(2)vivoTag計(jì)算邏輯:

按照Invocation計(jì)算出目標(biāo)的Tag,假設(shè)是tabB

然后從緩存Cache根據(jù)key:tag1,取出對(duì)應(yīng)的targetAddrPool

將上一次傳入的addrPool

與targetAddrPool

得到結(jié)果resultAddrPooll

將resultAddrPool傳入

vivoNearestRouter

(3)vivoNearestRouter計(jì)算邏輯

從環(huán)境變量取出當(dāng)前機(jī)房,假設(shè)是bj01

然后從緩存Cache根據(jù)key:bj01,取出對(duì)應(yīng)的targetAddrPool

將上一次傳入的addrPool

與targetAddrPool

取出resultAddrPool

將上一次傳入的addrPool

與targetAddrPool

得到結(jié)果resultAddrPool

將resultAddrPool為最終路由結(jié)果,傳遞給LoadBalance

fd94ea52-78a4-11ee-939d-92fbcf53809c.png

6. 基于緩存的同機(jī)房?jī)?yōu)先路由源碼解析

緩存刷新

 /**
     * Notify router chain of the initial addresses from registry at the first time.
     * Notify whenever addresses in registry change.
     */
    public void setInvokers(List> invokers) {
    // 創(chuàng)建帶epoch的BitList
        this.invokers = new BitList>(invokers == null ? Collections.emptyList() : invokers,createBitListEpoch());
        routers.forEach(router -> router.notify(this.invokers));
    }

同機(jī)房?jī)?yōu)先路由源碼解讀

public  List> route(List> invokers, URL consumerUrl, Invocation invocation) throws RpcException {
        …………//省略非核心代碼
        BitList> bitList = (BitList>) invokers;
    //獲取路由結(jié)果
        BitList> result = getNearestInvokersWithCache(bitList);
        if (result.size() > 0) {
            if (fallback) {
                // 開(kāi)啟服務(wù)降級(jí),available.ratio = 當(dāng)前機(jī)房可用服務(wù)節(jié)點(diǎn)數(shù)量 / 集群可用服務(wù)節(jié)點(diǎn)數(shù)量
                int curAvailableRatio = (int) Math.floor(result.size() * 100.0d / invokers.size());
                if (curAvailableRatio <= availableRatio) {
                    return invokers;
                }
            }


            return result;
        } else if (force) {
            return result;
        } else {
            return invokers;
        }


    }   
   /**
     * 獲取緩存列表
     * @param invokers
     * @param 
     * @return
     */
    private  BitList> getNearestInvokersWithCache(BitList> invokers) {
        ValueWrapper valueWrapper = getCache(getSystemProperty(LOC));
        // 是否存在緩存
        if (valueWrapper != null) {
            BitList> invokerBitList = (BitList>) valueWrapper.get();
            // 緩存的epoch與源列表是否一致
            if (invokers.isSameEpoch(invokerBitList)) {
                BitList> tmp = invokers.clone();
                // 結(jié)果取交集
                return tmp.and(invokerBitList);
            }
        }
        // 緩存不存在 實(shí)時(shí)計(jì)算放回
        return getNearestInvokers(invokers);
    }
  
  /**
     * 新服務(wù)列表通知
     * @param invokers
     * @param 
     */
 @Override
    public  void notify(List> invokers) {
        clear();
        if (invokers != null && invokers instanceof BitList) {
            BitList> bitList = (BitList>) invokers;
      // 設(shè)置最后一次更新的服務(wù)列表
            lastNotify = bitList.clone();
            if (!CollectionUtils.isEmpty(invokers) && this.enabled) {
        // 獲取機(jī)房相同的服務(wù)列表并進(jìn)行緩存
                setCache(getSystemProperty(LOC), getNearestInvokers(lastNotify));
            }
        }
    }

4.2 負(fù)載均衡優(yōu)化

1.優(yōu)化一

針對(duì)getWeight方法,我們發(fā)現(xiàn)有部分業(yè)務(wù)邏輯較為消耗cpu,但是在大多數(shù)場(chǎng)景下業(yè)務(wù)方并不會(huì)使用到,于是進(jìn)行優(yōu)化。

getWeight方法優(yōu)化

優(yōu)化前:
//這里主要要用多注冊(cè)中心場(chǎng)景下,注冊(cè)中心權(quán)重的獲取,絕大多數(shù)情況下并不會(huì)有這個(gè)邏輯
 if (UrlUtils.isRegistryService(url)) {
           weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
       }  
優(yōu)化后:
 if (invoker instanceof ClusterInvoker && UrlUtils.isRegistryService(url)) {
            weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
   }

2.優(yōu)化二

遍歷是罪惡的源泉,而實(shí)例的數(shù)量決定這罪惡的深淺,我們有什么辦法減少負(fù)載均衡過(guò)程中的遍歷呢。一是根據(jù)group及version劃分不同的集群,但是這需要涉及到業(yè)務(wù)方代碼或配置層面的改動(dòng),會(huì)帶來(lái)額外的成本。所以我們放棄了。

二是沒(méi)有什么是加一層解決不了的問(wèn)題,為了盡量減少進(jìn)入負(fù)載均衡的節(jié)點(diǎn)數(shù)量,考慮新增一個(gè)墊底的路由策略,在走完所有的路由策略后,若節(jié)點(diǎn)數(shù)量>自定義數(shù)量后,進(jìn)行虛擬分組,虛擬分組的策略也可進(jìn)行自定義,然后隨機(jī)篩選一組進(jìn)入負(fù)載均衡。此時(shí)進(jìn)入負(fù)載均衡的實(shí)例數(shù)量就會(huì)有倍數(shù)的下降。

需要注意的是分組路由必須保證是在路由鏈的最后一環(huán),否則會(huì)導(dǎo)致其他路由計(jì)算錯(cuò)誤。

fdb3db2e-78a4-11ee-939d-92fbcf53809c.png

分組路由示意

  /**
     * 
     * @param invokers 待分組實(shí)例列表
     * @param groupNum 分組數(shù)量
     * @param 
     * @return
     */
    public  List> doGroup(List> invokers, int groupNum) {
        int listLength = invokers.size() / groupNum;
        List> result = new ArrayList<>(listLength);
        int random = ThreadLocalRandom.current().nextInt(groupNum);
        for (int i = random; i < invokers.size(); i = i + groupNum) {
            result.add(invokers.get(i));
        }
        return result;
    }

五、優(yōu)化效果

針對(duì)優(yōu)化前和優(yōu)化后,我們編寫(xiě)Demo工程分別壓測(cè)了不配置路由/配置就近+標(biāo)簽路由場(chǎng)景。Provider節(jié)點(diǎn)梯度設(shè)置100/500/1000/2000/5000,TPS在1000左右,記錄了主機(jī)的cpu等性能指標(biāo),并打印火焰圖。發(fā)現(xiàn),配置路由后,采用相同并發(fā),優(yōu)化后的版本tps明顯高于優(yōu)化前版本,且新版本相較于沒(méi)有配置路由時(shí)tps顯著提高,下游節(jié)點(diǎn)數(shù)大于2000時(shí),tps提升達(dá)到100%以上,下游節(jié)點(diǎn)數(shù)越多,AvgCpu優(yōu)化效果越明顯,并且路由及負(fù)載均衡CPU占比明顯更低,詳細(xì)數(shù)據(jù)可見(jiàn)下表:

fdce5094-78a4-11ee-939d-92fbcf53809c.png

fde739a6-78a4-11ee-939d-92fbcf53809c.png

備注:-tag,表示顯式禁用原生Dubbo應(yīng)用級(jí)標(biāo)簽路由。該路由默認(rèn)開(kāi)啟。

六、總結(jié)

經(jīng)過(guò)我們關(guān)閉不必要的路由邏輯、對(duì)路由緩存+異步化計(jì)算、新增分組路由等優(yōu)化后,Dubbo在負(fù)載均衡及路由模塊整體的性能有了顯著的提升,為業(yè)務(wù)方節(jié)省了不少CPU資源。在正常業(yè)務(wù)場(chǎng)景下當(dāng)提供方數(shù)量達(dá)到2000及以上時(shí),tps提升可達(dá)100%以上,消費(fèi)方平均CPU使用率下降約27%,且提供方數(shù)量越多優(yōu)化效果越明顯。但是我們也發(fā)現(xiàn)當(dāng)前的隨機(jī)負(fù)載均衡依然還是會(huì)消耗一定的CPU資源,且只能保證流量是均衡的。當(dāng)前我們的應(yīng)用基本部署在虛擬機(jī)及容器上。這兩者均存在超賣(mài)的狀況,且同等配置的宿主機(jī)性能存在較大差異等問(wèn)題。最終會(huì)導(dǎo)致部分請(qǐng)求超時(shí)、無(wú)法最大化利用提供方的資源。我們下一步將會(huì)引入Dubbo 3.2的自適應(yīng)負(fù)載均衡并進(jìn)行調(diào)優(yōu)減少其CPU使用率波動(dòng)較大的問(wèn)題,其次我們自身也擴(kuò)展了基于CPU負(fù)載均衡的單一因子算法,最終實(shí)現(xiàn)不同性能的機(jī)器CPU負(fù)載趨于均衡,最大程度發(fā)揮集群整體的性能。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10902

    瀏覽量

    212996
  • 路由
    +關(guān)注

    關(guān)注

    0

    文章

    278

    瀏覽量

    41930
  • 負(fù)載均衡
    +關(guān)注

    關(guān)注

    0

    文章

    113

    瀏覽量

    12390
  • vivo
    +關(guān)注

    關(guān)注

    12

    文章

    3310

    瀏覽量

    63669
  • Dubbo
    +關(guān)注

    關(guān)注

    0

    文章

    20

    瀏覽量

    3193

原文標(biāo)題:Dubbo路由及負(fù)載均衡性能優(yōu)化

文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    路由負(fù)載均衡怎么配置

    路由負(fù)載均衡是一種重要的網(wǎng)絡(luò)技術(shù),它能夠?qū)⒍鄠€(gè)網(wǎng)絡(luò)連接的流量分配到多個(gè)路由器上,以提高網(wǎng)絡(luò)的性能和穩(wěn)定性。本文將詳細(xì)介紹路由
    的頭像 發(fā)表于 12-13 11:17 ?3547次閱讀

    聊聊Dubbo - Dubbo可擴(kuò)展機(jī)制實(shí)戰(zhàn)

    。RandomLoadBalance.NAME是一個(gè)常量,值是"random",是一個(gè)隨機(jī)負(fù)載均衡的實(shí)現(xiàn)。 random的定義在配置文件META-INF/dubbo
    發(fā)表于 06-04 17:33

    Dubbo開(kāi)源現(xiàn)狀與未來(lái)規(guī)劃

    、多機(jī)房路由、灰度路由,以及更豐富的負(fù)載均衡策略。再往上通過(guò)元數(shù)據(jù)的優(yōu)化,我們可以更清晰的分開(kāi)注冊(cè)層和配置層,從而可以在注冊(cè)層加入更多的對(duì)微
    發(fā)表于 07-05 15:21

    Dubbo Cloud Native 之路的實(shí)踐與思考

    Dubbo “隨機(jī)“、”輪詢(xún)“ 以及 ”最少活躍調(diào)用數(shù)“ 負(fù)載均衡算法中均體現(xiàn)。以上討論的兩種框架均屬于 Java 實(shí)現(xiàn),而中間的 Kong 則是更為通用的實(shí)現(xiàn),通常它作為 API 服務(wù)網(wǎng)關(guān),后面
    發(fā)表于 07-05 16:05

    考慮負(fù)載均衡的多下一跳路由自愈方法

    考慮負(fù)載均衡的多下一跳路由自愈方法_陳天平
    發(fā)表于 01-03 18:00 ?0次下載

    HBase負(fù)載均衡分析及優(yōu)化策略

    HBase負(fù)載均衡分析及優(yōu)化策略_黃偉建
    發(fā)表于 01-03 17:41 ?0次下載

    路由負(fù)載均衡如何設(shè)置_路由負(fù)載均衡的模式詳解

    路由器中加入負(fù)載均衡技術(shù)已經(jīng)不是新鮮事。那么現(xiàn)在已經(jīng)衍生出很多種路由負(fù)載模式,不同的模式狀態(tài)下,可以完成不同的任務(wù)。而且模式間也可以進(jìn)行轉(zhuǎn)
    發(fā)表于 01-01 19:43 ?3.9w次閱讀
    <b class='flag-5'>路由</b>器<b class='flag-5'>負(fù)載</b><b class='flag-5'>均衡</b>如何設(shè)置_<b class='flag-5'>路由</b>器<b class='flag-5'>負(fù)載</b><b class='flag-5'>均衡</b>的模式詳解

    Dubbo解析及原理淺析

    本文詳細(xì)闡述了Dubbo及其原理。Dubbo是一種分布式服務(wù)框架。dubbo除了可以提供服務(wù)之外,還可以實(shí)現(xiàn)軟負(fù)載均衡。它還提供了兩個(gè)功能M
    的頭像 發(fā)表于 02-07 15:57 ?2884次閱讀
    <b class='flag-5'>Dubbo</b>解析及原理淺析

    基于流量矩陣的負(fù)載均衡路由機(jī)制

    智慧協(xié)同網(wǎng)絡(luò)具有能夠?qū)崟r(shí)準(zhǔn)確測(cè)算流量矩陣的特點(diǎn)。將流量矩陣作為約束,對(duì)負(fù)載均衡路由優(yōu)化問(wèn)題進(jìn)行建模,利用拉格朗日對(duì)偶方法,將原問(wèn)題轉(zhuǎn)化為優(yōu)化
    發(fā)表于 02-12 11:18 ?0次下載

    Dubbo源代碼實(shí)現(xiàn)服務(wù)調(diào)用的動(dòng)態(tài)代理和負(fù)載均衡

    我們知道,Dubbo將服務(wù)調(diào)用封裝成普通的Spring的Bean,于是我們可以像使用本地的Spring Bean一樣,來(lái)調(diào)用遠(yuǎn)端的Dubbo服務(wù),并有LoadBalance和Failover的功能
    發(fā)表于 03-12 14:35 ?0次下載

    解密負(fù)載均衡技術(shù)和負(fù)載均衡算法

    負(fù)載均衡器是一種軟件或硬件設(shè)備,它起到了將網(wǎng)絡(luò)流量分散到一組服務(wù)器的作用,可以防止任何一臺(tái)服務(wù)器過(guò)載。負(fù)載均衡算法就是負(fù)載
    的頭像 發(fā)表于 11-12 09:16 ?1187次閱讀

    浮動(dòng)靜態(tài)路由負(fù)載均衡

    負(fù)載均衡:當(dāng)數(shù)據(jù)有多條可選路徑前往同一目的網(wǎng)絡(luò),可以通過(guò)配置相同優(yōu)先級(jí)和開(kāi)銷(xiāo)的靜態(tài)路由來(lái)實(shí)現(xiàn)負(fù)載均衡,使得數(shù)據(jù)的傳輸
    的頭像 發(fā)表于 02-27 15:26 ?1493次閱讀
    浮動(dòng)靜態(tài)<b class='flag-5'>路由</b>及<b class='flag-5'>負(fù)載</b><b class='flag-5'>均衡</b>

    如何確定適合的負(fù)載均衡比例

    路由器的負(fù)載均衡是一種應(yīng)用于網(wǎng)絡(luò)中的技術(shù),它可以平衡網(wǎng)絡(luò)流量的分配,提高網(wǎng)絡(luò)的性能和穩(wěn)定性。在配置路由器的負(fù)載
    的頭像 發(fā)表于 12-15 10:36 ?1717次閱讀

    華納云:什么是負(fù)載均衡優(yōu)化資源利用率的策略

    負(fù)載均衡是現(xiàn)代計(jì)算機(jī)網(wǎng)絡(luò)架構(gòu)中不可或缺的一部分,它通過(guò)智能分配請(qǐng)求和任務(wù),確保系統(tǒng)資源的高效利用。本文將探討負(fù)載均衡的概念、工作原理、優(yōu)化
    的頭像 發(fā)表于 10-28 16:07 ?225次閱讀

    多鏈路負(fù)載均衡設(shè)置在哪里?

    多鏈路負(fù)載均衡設(shè)置涉及交換機(jī)、路由器和(可選)負(fù)載均衡器的設(shè)置。首先規(guī)劃網(wǎng)絡(luò)拓?fù)浜虸P地址,備份設(shè)備配置。然后,在交換機(jī)上配置VLAN和Tr
    的頭像 發(fā)表于 11-13 10:19 ?186次閱讀
    大发888开户日博备用| 百家乐官网是否违法| 伟德亚洲娱乐城| 百家乐官网斗视频游戏| 彰化市| 百家乐官网最新赌王| 现金百家乐官网赢钱| 做生意发财招财图像| 电脑版百家乐下注技巧| 大发888娱乐场下载com| 大发888娱乐城 17| 百家乐官网赌术揭秘| 真人百家乐官网蓝盾娱乐网| 百家乐注册下注平台| JJ百家乐的玩法技巧和规则| 大发888娱乐场官方| 网络投注| 蓝盾百家乐官网打法| 百家乐打大必赢之法| 网上玩百家乐游戏有人挣到钱了吗| 大发888娱乐游戏下载| 百家乐官网硬币打法| 博彩百家乐官网最新优惠| 个人百家乐策略| 大发888大发娱乐城| 富易堂百家乐官网娱乐城| 捷豹百家乐官网娱乐城| 法拉利百家乐的玩法技巧和规则 | 全讯网bbin888.com| 百家乐官网增值公式| 网上百家乐追杀| 威尼斯人娱乐城赌博| 道真| 盛大百家乐官网的玩法技巧和规则 | 朝阳区| 百家乐游戏发展| 24山在风水中的作用| 大发888.comwf| 网上百家乐官网是叫九五至尊么| 温州市百家乐鞋业| 德保县|