作者: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ǔ)介紹
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)用。
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ò)程如下圖所示:
1. 核心類(lèi)
Dubbo路由的核心類(lèi)主要有:RouterChain、RouterFactory 與 Router 。
(1)RouterChain
RouterChain是路由鏈的入口,其核心字段有
invokers(List
初始服務(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),給大家展示一下就近路由的核心代碼邏輯
publicList > 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)圖如下所示:
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 protectedInvoker 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ù)熱效果圖如下:
三、問(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%左右。
通過(guò)火焰圖分析,負(fù)載均衡主要的消耗是在 getWeight方法。
路由的主要消耗是在route方法:
同機(jī)房?jī)?yōu)先路由
接口級(jí)標(biāo)簽路由+應(yīng)用級(jí)標(biāo)簽路由
這些方法都有一個(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í)序圖如下所示:
4. 具體路由流程
進(jìn)入具體路由方法時(shí),先判斷是否存在緩存的路由值,且緩存值的epoch必須與上一個(gè)路由的epoch需一致,此時(shí)緩存才生效,然后緩存值與上個(gè)Router的結(jié)果取交集。
如果不存在緩存或epoch不一致則重新進(jìn)行實(shí)時(shí)的路由計(jì)算。
引入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)行即可。
全量緩存示意圖:
路由交集計(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
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)先路由源碼解讀
publicList > 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ò)誤。
分組路由示意
/** * * @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)下表:
備注:-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ā)揮集群整體的性能。
-
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)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
路由器負(fù)載均衡怎么配置
聊聊Dubbo - Dubbo可擴(kuò)展機(jī)制實(shí)戰(zhàn)
Dubbo開(kāi)源現(xiàn)狀與未來(lái)規(guī)劃
Dubbo Cloud Native 之路的實(shí)踐與思考
路由器負(fù)載均衡如何設(shè)置_路由器負(fù)載均衡的模式詳解
![<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>的模式詳解](https://file1.elecfans.com//web2/M00/A7/1F/wKgZomUMQoSAScE-AAApWlBWgz0613.png)
Dubbo解析及原理淺析
![<b class='flag-5'>Dubbo</b>解析及原理淺析](https://file.elecfans.com/web1/M00/45/E0/o4YBAFp6scqAIVa6AAEpe7RR3SA010.png)
基于流量矩陣的負(fù)載均衡路由機(jī)制
Dubbo源代碼實(shí)現(xiàn)服務(wù)調(diào)用的動(dòng)態(tài)代理和負(fù)載均衡
解密負(fù)載均衡技術(shù)和負(fù)載均衡算法
浮動(dòng)靜態(tài)路由及負(fù)載均衡
![浮動(dòng)靜態(tài)<b class='flag-5'>路由</b>及<b class='flag-5'>負(fù)載</b><b class='flag-5'>均衡</b>](https://file.elecfans.com//web2/M00/94/A0/pYYBAGP8WxWAVpTPAAA4CV0CXwo307.png)
評(píng)論