<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis大集群擴(kuò)容性能優(yōu)化實(shí)踐

          共 7551字,需瀏覽 16分鐘

           ·

          2021-10-18 08:49

          作者:vivo互聯(lián)網(wǎng)數(shù)據(jù)庫(kù)團(tuán)隊(duì)—Yuan Jianwei


          一、背景


          在現(xiàn)網(wǎng)環(huán)境,一些使用Redis集群的業(yè)務(wù)隨著業(yè)務(wù)量的上漲,往往需要進(jìn)行節(jié)點(diǎn)擴(kuò)容操作。


          之前有了解到運(yùn)維同學(xué)對(duì)一些節(jié)點(diǎn)數(shù)比較大的Redis集群進(jìn)行擴(kuò)容操作后,業(yè)務(wù)側(cè)反映集群性能下降,具體表現(xiàn)在訪問(wèn)時(shí)延增長(zhǎng)明顯。


          某些業(yè)務(wù)對(duì)Redis集群訪問(wèn)時(shí)延比較敏感,例如現(xiàn)網(wǎng)環(huán)境對(duì)模型實(shí)時(shí)讀取,或者一些業(yè)務(wù)依賴讀取Redis集群的同步流程,會(huì)影響業(yè)務(wù)的實(shí)時(shí)流程時(shí)延。業(yè)務(wù)側(cè)可能無(wú)法接受。


          為了找到這個(gè)問(wèn)題的根因,我們對(duì)某一次的Redis集群遷移操作后的集群性能下降問(wèn)題進(jìn)行排查。


          1.1 問(wèn)題描述


          這一次具體的Redis集群?jiǎn)栴}的場(chǎng)景是:某一個(gè)Redis集群進(jìn)行過(guò)擴(kuò)容操作。業(yè)務(wù)側(cè)使用Hiredis-vip進(jìn)行Redis集群訪問(wèn),進(jìn)行MGET操作。


          業(yè)務(wù)側(cè)感知到訪問(wèn)Redis集群的時(shí)延變高。


          1.2 現(xiàn)網(wǎng)環(huán)境說(shuō)明


          • 目前現(xiàn)網(wǎng)環(huán)境部署的Redis版本多數(shù)是3.x或者4.x版本;

          • 業(yè)務(wù)訪問(wèn)Redis集群的客戶端品類繁多,較多的使用Jedis。本次問(wèn)題排查的業(yè)務(wù)使用客戶端Hiredis-vip進(jìn)行訪問(wèn);

          • Redis集群的節(jié)點(diǎn)數(shù)比較大,規(guī)模是100+;

          • 集群之前存在擴(kuò)容操作。


          1.3 觀察現(xiàn)象


          因?yàn)闀r(shí)延變高,我們從幾個(gè)方面進(jìn)行排查:

          • 帶寬是否打滿;

          • CPU是否占用過(guò)高;

          • OPS是否很高;


          通過(guò)簡(jiǎn)單的監(jiān)控排查,帶寬負(fù)載不高。但是發(fā)現(xiàn)CPU表現(xiàn)異常:



          1.3.1 對(duì)比ops和CPU負(fù)載


          觀察業(yè)務(wù)反饋使用的MGET和CPU負(fù)載,我們找到了對(duì)應(yīng)的監(jiān)控曲線。


          從時(shí)間上分析,MGET和CPU負(fù)載高并沒(méi)有直接關(guān)聯(lián)。業(yè)務(wù)側(cè)反饋的是MGET的時(shí)延普遍增高。此處看到MGET的OPS和CPU負(fù)載是錯(cuò)峰的。



          此處可以暫時(shí)確定業(yè)務(wù)請(qǐng)求和CPU負(fù)載暫時(shí)沒(méi)有直接關(guān)系,但是從曲線上可以看出:在同一個(gè)時(shí)間軸上,業(yè)務(wù)請(qǐng)求和cpu負(fù)載存在錯(cuò)峰的情況,兩者間應(yīng)該有間接關(guān)系。


          1.3.2 對(duì)比Cluster指令OPS和CPU負(fù)載


          由于之前有運(yùn)維側(cè)同事有反饋集群進(jìn)行過(guò)擴(kuò)容操作,必然存在slot的遷移。


          考慮到業(yè)務(wù)的客戶端一般都會(huì)使用緩存存放Redis集群的slot拓?fù)湫畔?,因此懷疑Cluster指令會(huì)和CPU負(fù)載存在一定聯(lián)系。


          我們找到了當(dāng)中確實(shí)有一些聯(lián)系:



          此處可以明顯看到:某個(gè)實(shí)例在執(zhí)行Cluster指令的時(shí)候,CPU的使用會(huì)明顯上漲。


          根據(jù)上述現(xiàn)象,大致可以進(jìn)行一個(gè)簡(jiǎn)單的聚焦:

          • 業(yè)務(wù)側(cè)執(zhí)行MGET,因?yàn)橐恍┰驁?zhí)行了Cluster指令;

          • Cluster指令因?yàn)橐恍┰驅(qū)е翪PU占用較高影響其他操作;

          • 懷疑Cluster指令是性能瓶頸。


          同時(shí),引申幾個(gè)需要關(guān)注的問(wèn)題:

          • 為什么會(huì)有較多的Cluster指令被執(zhí)行?

          • 為什么Cluster指令執(zhí)行的時(shí)候CPU資源比較高?

          • 為什么節(jié)點(diǎn)規(guī)模大的集群遷移slot操作容易“中招”?


          二、問(wèn)題排查


          2.1 Redis熱點(diǎn)排查


          我們對(duì)一臺(tái)現(xiàn)場(chǎng)出現(xiàn)了CPU負(fù)載高的Redis實(shí)例使用perf top進(jìn)行簡(jiǎn)單的分析:



          從上圖可以看出來(lái),函數(shù)(ClusterReplyMultiBulkSlots)占用的CPU資源高達(dá) 51.84%,存在異常。


          2.1.1 ClusterReplyMultiBulkSlots實(shí)現(xiàn)原理


          我們對(duì)clusterReplyMultiBulkSlots函數(shù)進(jìn)行分析:

          void clusterReplyMultiBulkSlots(client *c) {    /* Format: 1) 1) start slot     *            2) end slot     *            3) 1) master IP     *               2) master port     *               3) node ID     *            4) 1) replica IP     *               2) replica port     *               3) node ID     *           ... continued until done     */     int num_masters = 0;    void *slot_replylen = addDeferredMultiBulkLength(c);     dictEntry *de;    dictIterator *di = dictGetSafeIterator(server.cluster->nodes);    while((de = dictNext(di)) != NULL) {        /*注意:此處是對(duì)當(dāng)前Redis節(jié)點(diǎn)記錄的集群所有主節(jié)點(diǎn)都進(jìn)行了遍歷*/        clusterNode *node = dictGetVal(de);        int j = 0, start = -1;         /* Skip slaves (that are iterated when producing the output of their         * master) and  masters not serving any slot. */        /*跳過(guò)備節(jié)點(diǎn)。備節(jié)點(diǎn)的信息會(huì)從主節(jié)點(diǎn)側(cè)獲取。*/        if (!nodeIsMaster(node) || node->numslots == 0) continue;        for (j = 0; j < CLUSTER_SLOTS; j++) {            /*注意:此處是對(duì)當(dāng)前節(jié)點(diǎn)中記錄的所有slot進(jìn)行了遍歷*/            int bit, i;            /*確認(rèn)當(dāng)前節(jié)點(diǎn)是不是占有循環(huán)終端的slot*/            if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {                if (start == -1) start = j;            }            /*簡(jiǎn)單分析,此處的邏輯大概就是找出連續(xù)的區(qū)間,是的話放到返回中;不是的話繼續(xù)往下遞歸slot。              如果是開(kāi)始的話,開(kāi)始一個(gè)連續(xù)區(qū)間,直到和當(dāng)前的不連續(xù)。*/            if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {                int nested_elements = 3; /* slots (2) + master addr (1). */                void *nested_replylen = addDeferredMultiBulkLength(c);                 if (bit && j == CLUSTER_SLOTS-1) j++;                 /* If slot exists in output map, add to it's list.                 * else, create a new output map for this slot */                if (start == j-1) {                    addReplyLongLong(c, start); /* only one slot; low==high */                    addReplyLongLong(c, start);                } else {                    addReplyLongLong(c, start); /* low */                    addReplyLongLong(c, j-1);   /* high */                }                start = -1;                 /* First node reply position is always the master */                addReplyMultiBulkLen(c, 3);                addReplyBulkCString(c, node->ip);                addReplyLongLong(c, node->port);                addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);                 /* Remaining nodes in reply are replicas for slot range */                for (i = 0; i < node->numslaves; i++) {                    /*注意:此處遍歷了節(jié)點(diǎn)下面的備節(jié)點(diǎn)信息,用于返回*/                    /* This loop is copy/pasted from clusterGenNodeDescription()                     * with modifications for per-slot node aggregation */                    if (nodeFailed(node->slaves[i])) continue;                    addReplyMultiBulkLen(c, 3);                    addReplyBulkCString(c, node->slaves[i]->ip);                    addReplyLongLong(c, node->slaves[i]->port);                    addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);                    nested_elements++;                }                setDeferredMultiBulkLength(c, nested_replylen, nested_elements);                num_masters++;            }        }    }    dictReleaseIterator(di);    setDeferredMultiBulkLength(c, slot_replylen, num_masters);} /* Return the slot bit from the cluster node structure. *//*該函數(shù)用于判斷指定的slot是否屬于當(dāng)前clusterNodes節(jié)點(diǎn)*/int clusterNodeGetSlotBit(clusterNode *n, int slot) {    return bitmapTestBit(n->slots,slot);} /* Test bit 'pos' in a generic bitmap. Return 1 if the bit is set, * otherwise 0. *//*此處流程用于判斷指定的的位置在bitmap上是否為1*/int bitmapTestBit(unsigned char *bitmap, int pos) {    off_t byte = pos/8;    int bit = pos&7;    return (bitmap[byte] & (1<0;}typedef struct clusterNode {    ...    /*使用一個(gè)長(zhǎng)度為CLUSTER_SLOTS/8的char數(shù)組對(duì)當(dāng)前分配的slot進(jìn)行記錄*/    unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */    ...} clusterNode;


          每一個(gè)節(jié)點(diǎn)(ClusterNode)使用位圖(char slots[CLUSTER_SLOTS/8])存放slot的分配信息。


          簡(jiǎn)要說(shuō)一下BitmapTestBit的邏輯:clusterNode->slots是一個(gè)長(zhǎng)度為CLUSTER_SLOTS/8的數(shù)組。CLUSTER_SLOTS是固定值16384。數(shù)組上的每一個(gè)位分別代表一個(gè)slot。此處的bitmap數(shù)組下標(biāo)則是0到2047,slot的范圍是0到16383。


          因?yàn)橐袛鄍os這個(gè)位置的bit上是否是1,因此:


          • off_t byte = pos/8:拿到在bitmap上對(duì)應(yīng)的哪一個(gè)字節(jié)(Byte)上存放這個(gè)pos位置的信息。因?yàn)橐粋€(gè)Byte有8個(gè)bit。使用pos/8可以指導(dǎo)需要找的Byte在哪一個(gè)。此處把bitmap當(dāng)成數(shù)組處理,這里對(duì)應(yīng)的便是對(duì)應(yīng)下標(biāo)的Byte。

          • int bit = pos&7:拿到是在這個(gè)字節(jié)上對(duì)應(yīng)哪一個(gè)bit表示這個(gè)pos位置的信息。&7其實(shí)就是%8??梢韵胂髮?duì)pos每8個(gè)一組進(jìn)行分組,最后一組(不滿足8)的個(gè)數(shù)對(duì)應(yīng)的便是在bitmap對(duì)應(yīng)的Byte上對(duì)應(yīng)的bit數(shù)組下標(biāo)位置。

          • (bitmap[byte] & (1<:判斷對(duì)應(yīng)的那個(gè)bit在bitmap[byte]上是否存在。


          以slot為10001進(jìn)行舉例:



          因此10001這個(gè)slot對(duì)應(yīng)的是下標(biāo)1250的Byte,要校驗(yàn)的是下標(biāo)1的bit。


          對(duì)應(yīng)在ClusterNode->slots上的對(duì)應(yīng)位置:



          圖示綠色的方塊表示bitmap[1250],也就是對(duì)應(yīng)存放slot 10001的Byte;紅框標(biāo)識(shí)(bit[1])對(duì)應(yīng)的就是1<


          總結(jié)ClusterNodeGetSlotBit的概要邏輯是:判斷當(dāng)前的這個(gè)slot是否分配在當(dāng)前node上。因此ClusterReplyMultiBulkSlots大概邏輯表示如下:



          大概步驟如下:

          • 對(duì)每一個(gè)節(jié)點(diǎn)進(jìn)行遍歷;

          • 對(duì)于每一個(gè)節(jié)點(diǎn),遍歷所有的slots,使用ClusterNodeGetSlotBit判斷遍歷中的slot是否分配于當(dāng)前節(jié)點(diǎn);


          從獲取CLUSTER SLOTS指令的結(jié)果來(lái)看,可以看到,復(fù)雜度是<集群主節(jié)點(diǎn)個(gè)數(shù)> *。其中slot的總個(gè)數(shù)是16384,固定值。


          2.1.2 Redis熱點(diǎn)排查總結(jié)


          就目前來(lái)看,CLUSTER SLOTS指令時(shí)延隨著Redis集群的主節(jié)點(diǎn)個(gè)數(shù),線性增長(zhǎng)。而這次我們排查的集群主節(jié)點(diǎn)數(shù)比較大,可以解釋這次排查的現(xiàn)網(wǎng)現(xiàn)象中CLUSTER SLOTS指令時(shí)延為何較大。


          2.2 客戶端排查


          了解到運(yùn)維同學(xué)們存在擴(kuò)容操作,擴(kuò)容完成后必然涉及到一些key在訪問(wèn)的時(shí)候存在MOVED的錯(cuò)誤。


          當(dāng)前使用的Hiredis-vip客戶端代碼進(jìn)行簡(jiǎn)單的瀏覽,簡(jiǎn)要分析以下當(dāng)前業(yè)務(wù)使用的Hiredis-vip客戶端在遇到MOVED的時(shí)候會(huì)怎樣處理。由于其他的大部分業(yè)務(wù)常用的Jedis客戶端,此處也對(duì)Jedis客戶端對(duì)應(yīng)流程進(jìn)行簡(jiǎn)單分析。


          2.2.1 Hiredis-vip對(duì)MOVED處理實(shí)現(xiàn)原理


          Hiredis-vip針對(duì)MOVED的操作:


          查看Cluster_update_route的調(diào)用過(guò)程:


          此處的cluster_update_route_by_addr進(jìn)行了CLUSTER SLOT操作??梢钥吹剑?dāng)獲取到MOVED報(bào)錯(cuò)的時(shí)候,Hiredis-vip會(huì)重新更新Redis集群拓?fù)浣Y(jié)構(gòu),有下面的特性:


          • 因?yàn)楣?jié)點(diǎn)通過(guò)ip:port作為key,哈希方式一樣,如果集群拓?fù)漕愃?,多個(gè)客戶端很容易同時(shí)到同一個(gè)節(jié)點(diǎn)進(jìn)行訪問(wèn);

          • 如果某個(gè)節(jié)點(diǎn)訪問(wèn)失敗,會(huì)通過(guò)迭代器找下一個(gè)節(jié)點(diǎn),由于上述的原因,多個(gè)客戶端很容易同時(shí)到下一個(gè)節(jié)點(diǎn)進(jìn)行訪問(wèn)。


          2.2.2 Jedis對(duì)MOVED處理實(shí)現(xiàn)原理


          對(duì)Jedis客戶端代碼進(jìn)行簡(jiǎn)單瀏覽,發(fā)現(xiàn)如果存在MOVED錯(cuò)誤,會(huì)調(diào)用renewSlotCache。


          繼續(xù)看renewSlotCache的調(diào)用,此處可以確認(rèn):Jedis在集群模式下在遇到MOVED的報(bào)錯(cuò)時(shí)候,會(huì)發(fā)送Redis命令CLUSTER SLOTS,重新拉取Redis集群的slot拓?fù)浣Y(jié)構(gòu)。


          2.2.3 客戶端實(shí)現(xiàn)原理小結(jié)


          由于Jedis是Java的Redis客戶端,Hiredis-vip是c++的Redis客戶端,可以簡(jiǎn)單認(rèn)為這種異常處理機(jī)制是共性操作。


          對(duì)客戶端集群模式下對(duì)MOVED的流程梳理大概如下:



          總的來(lái)說(shuō):

          1)使用客戶端緩存的slot拓?fù)溥M(jìn)行對(duì)key的訪問(wèn);

          2)Redis節(jié)點(diǎn)返回正常:

          • 訪問(wèn)正常,繼續(xù)后續(xù)操作

          3)Redis節(jié)點(diǎn)返回MOVED:

          • 對(duì)Redis節(jié)點(diǎn)進(jìn)行CLUSTER SLOTS指令執(zhí)行,更新拓?fù)洌?/p>

          • 使用新的拓?fù)鋵?duì)key重新訪問(wèn)。


          2.2.3 客戶端排查小結(jié)


          Redis集群正在擴(kuò)容,也就是必然存在一些Redis客戶端在訪問(wèn)Redis集群遇到MOVED,執(zhí)行Redis指令CLUSTER SLOTS進(jìn)行拓?fù)浣Y(jié)構(gòu)更新。


          如果遷移的key命中率高,CLUSTER SLOTS指令會(huì)更加頻繁的執(zhí)行。這樣導(dǎo)致的結(jié)果是遷移過(guò)程中Redis集群會(huì)持續(xù)被客戶端執(zhí)行CLUSTER SLOTS指令。


          2.3 排查小結(jié)


          此處,結(jié)合Redis側(cè)的CLUSTER SLOTS機(jī)制以及客戶端對(duì)MOVED的處理邏輯,可以解答之前的幾個(gè)個(gè)問(wèn)題:


          為什么會(huì)有較多的Cluster指令被執(zhí)行?

          因?yàn)榘l(fā)生過(guò)遷移操作,業(yè)務(wù)訪問(wèn)一些遷移過(guò)的key會(huì)拿到MOVED返回,客戶端會(huì)對(duì)該返回重新拉取slot拓?fù)湫畔?,?zhí)行CLUSTER SLOTS。


          為什么Cluster指令執(zhí)行的時(shí)候CPU資源比較高?

          分析Redis源碼,發(fā)現(xiàn)CLUSTER SLOT指令的時(shí)間復(fù)雜度和主節(jié)點(diǎn)個(gè)數(shù)成正比。業(yè)務(wù)當(dāng)前的Redis集群主節(jié)點(diǎn)個(gè)數(shù)比較多,自然耗時(shí)高,占用CPU資源高。


          為什么節(jié)點(diǎn)規(guī)模大的集群遷移slot操作容易“中招”?

          遷移操作必然帶來(lái)一些客戶端訪問(wèn)key的時(shí)候返回MOVED;

          客戶端對(duì)于MOVED的返回會(huì)執(zhí)行CLUSTER SLOTS指令;

          CLUSTER SLOTS指令隨著集群主節(jié)點(diǎn)個(gè)數(shù)的增加,時(shí)延會(huì)上升;

          業(yè)務(wù)的訪問(wèn)在slot的遷移期間會(huì)因?yàn)镃LUSTER SLOTS的時(shí)延上升,在外部的感知是執(zhí)行指令的時(shí)延升高。


          此段與本文無(wú)關(guān)(webflux 是兼容Spring MVC 基于@Controller,@RequestMapping等注解的編程開(kāi)發(fā)方式的,可以做到平滑切換)


          三、優(yōu)化


          3.1 現(xiàn)狀分析


          根據(jù)目前的情況來(lái)看,客戶端遇到MOVED進(jìn)行CLUSTER SLOTS執(zhí)行是正常的流程,因?yàn)樾枰录旱膕lot拓?fù)浣Y(jié)構(gòu)提高后續(xù)的集群訪問(wèn)效率。


          此處流程除了Jedis,Hiredis-vip,其他的客戶端應(yīng)該也會(huì)進(jìn)行類似的slot信息緩存優(yōu)化。此處流程優(yōu)化空間不大,是Redis的集群訪問(wèn)機(jī)制決定。


          因此對(duì)Redis的集群信息記錄進(jìn)行分析。


          3.1.1 Redis集群元數(shù)據(jù)分析


          集群中每一個(gè)Redis節(jié)點(diǎn)都會(huì)有一些集群的元數(shù)據(jù)記錄,記錄于server.cluster,內(nèi)容如下:

          typedef struct clusterState {    ...    dict *nodes;          /* Hash table of name -> clusterNode structures */    /*nodes記錄的是所有的節(jié)點(diǎn),使用dict記錄*/    ...    clusterNode *slots[CLUSTER_SLOTS];/*slots記錄的是slot數(shù)組,內(nèi)容是node的指針*/    ...} clusterState;


          如2.1所述,原有邏輯通過(guò)遍歷每個(gè)節(jié)點(diǎn)的slot信息獲得拓?fù)浣Y(jié)構(gòu)。


          3.1.2 Redis集群元數(shù)據(jù)分析


          觀察CLUSTER SLOTS的返回結(jié)果:

          /* Format: 1) 1) start slot *            2) end slot *            3) 1) master IP *               2) master port *               3) node ID *            4) 1) replica IP *               2) replica port *               3) node ID *           ... continued until done */


          結(jié)合server.cluster中存放的集群信息,筆者認(rèn)為此處可以使用server.cluster->slots進(jìn)行遍歷。因?yàn)閟erver.cluster->slots已經(jīng)在每一次集群的拓?fù)渥兓玫搅烁?,保存的是?jié)點(diǎn)指針。


          3.2 優(yōu)化方案


          簡(jiǎn)單的優(yōu)化思路如下:


          • 對(duì)slot進(jìn)行遍歷,找出slot中節(jié)點(diǎn)是連續(xù)的塊;

          • 當(dāng)前遍歷的slot的節(jié)點(diǎn)如果和之前遍歷的節(jié)點(diǎn)一致,說(shuō)明目前訪問(wèn)的slot和前面的是在同一個(gè)節(jié)點(diǎn)下,也就是是在某個(gè)節(jié)點(diǎn)下的“連續(xù)”的slot區(qū)域內(nèi);

          • 當(dāng)前遍歷的slot的節(jié)點(diǎn)如果和之前遍歷的節(jié)點(diǎn)不一致,說(shuō)明目前訪問(wèn)的slot和前面的不同,前面的“連續(xù)”slot區(qū)域可以進(jìn)行輸出;而當(dāng)前slot作為下一個(gè)新的“連續(xù)”slot區(qū)域的開(kāi)始。


          因此只要對(duì)server.cluster->slots進(jìn)行遍歷,可以滿足需求。簡(jiǎn)單表示大概如下:



          這樣的時(shí)間復(fù)雜度降低到。


          3.3 實(shí)現(xiàn)


          優(yōu)化邏輯如下:

          void clusterReplyMultiBulkSlots(client * c) {    /* Format: 1) 1) start slot     *            2) end slot     *            3) 1) master IP     *               2) master port     *               3) node ID     *            4) 1) replica IP     *               2) replica port     *               3) node ID     *           ... continued until done     */    clusterNode *n = NULL;    int num_masters = 0, start = -1;    void *slot_replylen = addReplyDeferredLen(c);     for (int i = 0; i <= CLUSTER_SLOTS; i++) {        /*對(duì)所有slot進(jìn)行遍歷*/        /* Find start node and slot id. */        if (n == NULL) {            if (i == CLUSTER_SLOTS) break;            n = server.cluster->slots[i];            start = i;            continue;        }         /* Add cluster slots info when occur different node with start         * or end of slot. */        if (i == CLUSTER_SLOTS || n != server.cluster->slots[i]) {            /*遍歷主節(jié)點(diǎn)下面的備節(jié)點(diǎn),添加返回客戶端的信息*/            addNodeReplyForClusterSlot(c, n, start, i-1);            num_masters++;            if (i == CLUSTER_SLOTS) break;            n = server.cluster->slots[i];            start = i;        }    }    setDeferredArrayLen(c, slot_replylen, num_masters);}


          通過(guò)對(duì)server.cluster->slots進(jìn)行遍歷,找到某個(gè)節(jié)點(diǎn)下的“連續(xù)”的slot區(qū)域,一旦后續(xù)不連續(xù),把之前的“連續(xù)”slot區(qū)域的節(jié)點(diǎn)信息以及其備節(jié)點(diǎn)信息進(jìn)行輸出,然后繼續(xù)下一個(gè)“連續(xù)”slot區(qū)域的查找于輸出。


          四、優(yōu)化結(jié)果對(duì)比


          對(duì)兩個(gè)版本的Redis的CLUSTER SLOTS指令進(jìn)行橫向?qū)Ρ取?/p>


          4.1 測(cè)試環(huán)境&壓測(cè)場(chǎng)景


          操作系統(tǒng):manjaro 20.2


          硬件配置

          • CPU:AMD Ryzen 7 4800H

          • DRAM:DDR4 3200MHz 8G*2


          Redis集群信息

          1)持久化配置

          • 關(guān)閉aof

          • 關(guān)閉bgsave

          2)集群節(jié)點(diǎn)信息:

          • 節(jié)點(diǎn)個(gè)數(shù):100

          • 所有節(jié)點(diǎn)都是主節(jié)點(diǎn)


          壓測(cè)場(chǎng)景

          • 使用benchmark工具對(duì)集群?jiǎn)蝹€(gè)節(jié)點(diǎn)持續(xù)發(fā)送CLUSTER SLOTS指令;

          • 對(duì)其中一個(gè)版本壓測(cè)完后,回收集群,重新部署后再進(jìn)行下一輪壓測(cè)。


          4.2 CPU資源占用對(duì)比


          perf導(dǎo)出火焰圖。原有版本:



          優(yōu)化后:



          可以明顯看到,優(yōu)化后的占比大幅度下降?;痉项A(yù)期。


          4.3 耗時(shí)對(duì)比


          在上進(jìn)行測(cè)試,嵌入耗時(shí)測(cè)試代碼:

          else if (!strcasecmp(c->argv[1]->ptr,"slots") && c->argc == 2) {        /* CLUSTER SLOTS */        long long now = ustime();        clusterReplyMultiBulkSlots(c);        serverLog(LL_NOTICE,            "cluster slots cost time:%lld us", ustime() - now);    }


          輸入日志進(jìn)行對(duì)比;


          原版的日志輸出

          37351:M 06 Mar 2021 16:11:39.313 * cluster slots cost time:2061 us。


          優(yōu)化后版本日志輸出

          35562:M 06 Mar 2021 16:11:27.862 * cluster slots cost time:168 us。


          從耗時(shí)上看下降明顯:從2000+us 下降到200-us;在100個(gè)主節(jié)點(diǎn)的集群中的耗時(shí)縮減到原來(lái)的8.2%;優(yōu)化結(jié)果基本符合預(yù)期。


          五、總結(jié)


          這里可以簡(jiǎn)單描述下文章上述的動(dòng)作從而得出的這樣的一個(gè)結(jié)論:性能缺陷


          簡(jiǎn)單總結(jié)下上述的排查以及優(yōu)化過(guò)程:


          • Redis大集群因?yàn)镃LUSTER命令導(dǎo)致某些節(jié)點(diǎn)的訪問(wèn)延遲明顯;

          • 使用perf top指令對(duì)Redis實(shí)例進(jìn)行排查,發(fā)現(xiàn)clusterReplyMultiBulkSlots命令占用CPU資源異常;

          • 對(duì)clusterReplyMultiBulkSlots進(jìn)行分析,該函數(shù)存在明顯的性能問(wèn)題;

          • 對(duì)clusterReplyMultiBulkSlots進(jìn)行優(yōu)化,性能提升明顯。


          從上述的排查以及優(yōu)化過(guò)程可以得出一個(gè)結(jié)論:目前的Redis在CLUSTER SLOT指令存在性能缺陷。


          因?yàn)镽edis的數(shù)據(jù)分片機(jī)制,決定了Redis集群模式下的key訪問(wèn)方法是緩存slot的拓?fù)湫畔?。?yōu)化點(diǎn)也只能在CLUSTER SLOTS入手。而Redis的集群節(jié)點(diǎn)個(gè)數(shù)一般沒(méi)有這么大,問(wèn)題暴露的不明顯。


          其實(shí)Hiredis-vip的邏輯也存在一定問(wèn)題。如2.2.1所說(shuō),Hiredis-vip的slot拓?fù)涓路椒ㄊ潜闅v所有的節(jié)點(diǎn)挨個(gè)進(jìn)行CLUSTER SLOTS。如果Redis集群規(guī)模較大而且業(yè)務(wù)側(cè)的客戶端規(guī)模較多,會(huì)出現(xiàn)連鎖反應(yīng):


          • 1)如果Redis集群較大,CLUSTER SLOTS響應(yīng)比較慢;

          • 2)如果某個(gè)節(jié)點(diǎn)沒(méi)有響應(yīng)或者返回報(bào)錯(cuò),Hiredis-vip客戶端會(huì)對(duì)下一個(gè)節(jié)點(diǎn)繼續(xù)進(jìn)行請(qǐng)求;

          • 3)Hiredis-vip客戶端中對(duì)Redis集群節(jié)點(diǎn)迭代遍歷的方法相同(因?yàn)榧旱男畔⒃诟鱾€(gè)客戶端基本一致),此時(shí)當(dāng)客戶端規(guī)模較大的時(shí)候,某個(gè)Redis節(jié)點(diǎn)可能存在阻塞,就會(huì)導(dǎo)致hiredis-vip客戶端遍歷下一個(gè)Redis節(jié)點(diǎn);

          • 4)大量Hiredis-vip客戶端挨個(gè)地對(duì)一些Redis節(jié)點(diǎn)進(jìn)行訪問(wèn),如果Redis節(jié)點(diǎn)無(wú)法負(fù)擔(dān)這樣的請(qǐng)求,這樣會(huì)導(dǎo)致Redis節(jié)點(diǎn)在大量Hiredis-vip客戶端的“遍歷”下挨個(gè)請(qǐng)求:

          結(jié)合上述第3點(diǎn),可以想象一下:有1w個(gè)客戶端對(duì)該Redis集群進(jìn)行訪問(wèn)。因?yàn)槟硞€(gè)命中率較高的key存在遷移操作,所有的客戶端都需要更新slot拓?fù)?。由于所有客戶端緩存的集群?jié)點(diǎn)信息相同,因此遍歷各個(gè)節(jié)點(diǎn)的順序是一致的。這1w個(gè)客戶端都使用同樣的順序?qū)焊鱾€(gè)節(jié)點(diǎn)進(jìn)行遍歷地操作CLUSTER SLOTS。由于CLUSTER SLOTS在大集群中性能較差,Redis節(jié)點(diǎn)很容易會(huì)被大量客戶端請(qǐng)求導(dǎo)致不可訪問(wèn)。Redis節(jié)點(diǎn)會(huì)根據(jù)遍歷順序依次被大部分的客戶端(例如9k+個(gè)客戶端)訪問(wèn),執(zhí)行CLUSTER SLOTS指令,導(dǎo)致Redis節(jié)點(diǎn)挨個(gè)被阻塞。


          • 5)最終的表現(xiàn)是大部分Redis節(jié)點(diǎn)的CPU負(fù)載暴漲,很多Hiredis-vip客戶端則繼續(xù)無(wú)法更新slot拓?fù)洹?/p>


          最終結(jié)果是大規(guī)模的Redis集群在進(jìn)行slot遷移操作后,在大規(guī)模的Hiredis-vip客戶端訪問(wèn)下業(yè)務(wù)側(cè)感知是普通指令時(shí)延變高,而Redis實(shí)例CPU資源占用高漲。這個(gè)邏輯可以進(jìn)行一定優(yōu)化。


          目前上述分節(jié)3的優(yōu)化已經(jīng)提交并合并到Redis 6.2.2版本中。


          六、參考資料


          1、Hiredis-vip: https://github.com

          2、Jedis: https://github.com/redis/jedis

          3、Redis: https://github.com/redis/redis

          4、Perf:https://perf.wiki.kernel.org


          瀏覽 69
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩AV无码一区二区三 | 欧美网站免费 | 国产操逼视频暴操特操 | 色婷婷精品国产一区二区三区 | 91免费三级片网站 |