国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

淺析Redis大Key | 京東云技術(shù)團(tuán)隊(duì)

這篇具有很好參考價(jià)值的文章主要介紹了淺析Redis大Key | 京東云技術(shù)團(tuán)隊(duì)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

一、背景

在京東到家購(gòu)物車(chē)系統(tǒng)中,用戶(hù)基于門(mén)店能夠?qū)ι唐愤M(jìn)行加車(chē)操作。用戶(hù)與門(mén)店商品使用Redis的Hash類(lèi)型存儲(chǔ),如下代碼塊所示。不知細(xì)心的你有沒(méi)有發(fā)現(xiàn),如果單門(mén)店加車(chē)商品過(guò)多,或者門(mén)店過(guò)多時(shí),此Key就會(huì)越來(lái)越大,從而影響線上業(yè)務(wù)。

userPin:{
      storeId:{門(mén)店下加車(chē)的所有商品基本信息},
      storeId:{門(mén)店下加車(chē)的所有商品基本信息},
      ......
}


二、BigKey的界定和如何產(chǎn)生

2.1、BigKey的界定

BigKey稱(chēng)為大Key,通常以Key對(duì)應(yīng)Value的存儲(chǔ)大小,或者Key對(duì)應(yīng)Value的數(shù)量來(lái)進(jìn)行綜合判斷。對(duì)于大Key也沒(méi)有嚴(yán)格的定義區(qū)分,針對(duì)String與非String結(jié)構(gòu),給出如下定義:

  • String:String類(lèi)型的 Key 對(duì)應(yīng)的 Value 超過(guò) 10KB
  • 非String結(jié)構(gòu)(Hash,Set,ZSet,List):Value的數(shù)量達(dá)到10000個(gè),或者Vaule的總大小為100KB
  • 集群中Key的總數(shù)超過(guò)1億

2.2、如何產(chǎn)生

1、數(shù)據(jù)結(jié)構(gòu)設(shè)置不合理,例如集合中元素唯一時(shí),應(yīng)該使用Set替換List;

2、針對(duì)業(yè)務(wù)缺少預(yù)估性,沒(méi)有預(yù)見(jiàn)Value動(dòng)態(tài)增長(zhǎng);

3、Key沒(méi)有設(shè)置過(guò)期時(shí)間,把緩存當(dāng)成垃圾桶,一直再往里面扔,但是從不處理。

三、BigKey的危害

3.1、數(shù)據(jù)傾斜

redis數(shù)據(jù)傾斜分為數(shù)據(jù)訪問(wèn)傾斜和**數(shù)據(jù)量?jī)A斜,**會(huì)導(dǎo)致該Key所在的數(shù)據(jù)分片節(jié)點(diǎn)CPU使用率、帶寬使用率升高,從而影響該分片上所有Key的處理。

**數(shù)據(jù)訪問(wèn)傾斜:**某節(jié)點(diǎn)中key的QPS高于其他節(jié)點(diǎn)中的Key

**數(shù)據(jù)量?jī)A斜:**某節(jié)點(diǎn)中key的大小高于其他節(jié)點(diǎn)中的Key,如下圖,實(shí)例1中的Key1存儲(chǔ)高于其他實(shí)例。

淺析Redis大Key | 京東云技術(shù)團(tuán)隊(duì),云服務(wù),redis,京東云,bootstrap,key

3.2、網(wǎng)絡(luò)阻塞

Redis服務(wù)器是一個(gè)事件驅(qū)動(dòng)程序,有文件事件和時(shí)間事件,文件事件和時(shí)間事件都是主線程完成。其中文件事件就是服務(wù)器對(duì)套接字操作的抽象,客戶(hù)端與服務(wù)端的通信會(huì)產(chǎn)生相應(yīng)的文件事件,服務(wù)器通過(guò)監(jiān)聽(tīng)并處理這些事件來(lái)完成一系列網(wǎng)絡(luò)通信操作。

Redis基于Reactor模式開(kāi)發(fā)了自己的網(wǎng)絡(luò)事件處理器,即文件事件處理器,該處理器內(nèi)部使用I/O多路復(fù)用程序,可同時(shí)監(jiān)聽(tīng)多個(gè)套接字,并根據(jù)套接字執(zhí)行的任務(wù)來(lái)關(guān)聯(lián)不同的事件處理器。文件事件處理器以單線程的方式運(yùn)行,但是通過(guò)I/O多路復(fù)用程序來(lái)監(jiān)聽(tīng)多個(gè)套接字,既實(shí)現(xiàn)了高性能網(wǎng)絡(luò)通信模型,又保持了內(nèi)部單線程設(shè)計(jì)的簡(jiǎn)單性。文件事件處理器構(gòu)成如下圖:

淺析Redis大Key | 京東云技術(shù)團(tuán)隊(duì),云服務(wù),redis,京東云,bootstrap,key

文件事件是對(duì)套接字操作的抽象,包括連接應(yīng)答,寫(xiě)入,讀取,關(guān)閉,因?yàn)橐粋€(gè)服務(wù)器會(huì)連接多個(gè)套接字,所以文件事件可能并發(fā)出現(xiàn),即使文件事件并發(fā)的出現(xiàn),但是I/O多路復(fù)用程序會(huì)將套接字放入一個(gè)隊(duì)列,通過(guò)隊(duì)列有序的,同步的每次一個(gè)套接字的方式向文件事件分派器傳送套接字,當(dāng)讓一個(gè)套接字產(chǎn)生的事件被處理完畢后,I/O多路復(fù)用程序才會(huì)繼續(xù)向文件事件分派器傳送下一個(gè)套接字,當(dāng)有大key時(shí),單次操作時(shí)間延長(zhǎng),導(dǎo)致網(wǎng)絡(luò)阻塞。

3.3、慢查詢(xún)

嚴(yán)重影響 QPS 、TP99 等指標(biāo),對(duì)大Key進(jìn)行的慢操作會(huì)導(dǎo)致后續(xù)的命令被阻塞,從而導(dǎo)致一系列慢查詢(xún)。

3.4、CPU壓力

當(dāng)單Key過(guò)大時(shí),每一次訪問(wèn)此Key都可能會(huì)造成Redis阻塞,其他請(qǐng)求只能等待了。如果應(yīng)用中設(shè)置了超時(shí)等,那么上層就會(huì)拋出異常信息。最后刪除的時(shí)候也會(huì)造成redis阻塞,到時(shí)候內(nèi)存中數(shù)據(jù)量過(guò)大,就會(huì)造成CPU負(fù)載過(guò)高。單個(gè)分片cpu占用率過(guò)高,其他分片無(wú)法擁有cpu資源,從而被影響。此外,大 key 對(duì)持久化也有些影響。fork 操作會(huì)拷貝父進(jìn)程的頁(yè)表項(xiàng),如果過(guò)大,會(huì)占用更多頁(yè)表,主線程阻塞拷貝需要一定的時(shí)間。

四、如何檢測(cè)BigKey

4.1、redis-cli --bigkeys

首先我們從運(yùn)行結(jié)果出發(fā)。首先通過(guò)腳本插入一些數(shù)據(jù)到redis中,然后執(zhí)行redis-cli的–bigkeys選項(xiàng)

$ redis-cli --bigkeys

# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type.  You can use -i 0.01 to sleep 0.01 sec
# per SCAN command (not usually needed).
-------- 第一部分start -------
[00.00%] Biggest string found so far 'key-419' with 3 bytes
[05.14%] Biggest list   found so far 'mylist' with 100004 items
[35.77%] Biggest string found so far 'counter:__rand_int__' with 6 bytes
[73.91%] Biggest hash   found so far 'myobject' with 3 fields

-------- 第一部分end -------

-------- summary -------

-------- 第二部分start -------
Sampled 506 keys in the keyspace!
Total key length in bytes is 3452 (avg len 6.82)

Biggest string found 'counter:__rand_int__' has 6 bytes
Biggest   list found 'mylist' has 100004 items
Biggest   hash found 'myobject' has 3 fields
-------- 第二部分end -------

-------- 第三部分start -------
504 strings with 1403 bytes (99.60% of keys, avg size 2.78)
1 lists with 100004 items (00.20% of keys, avg size 100004.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
1 hashs with 3 fields (00.20% of keys, avg size 3.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)
-------- 第三部分end -------


以下我們分三步對(duì)bigkeys選項(xiàng)源碼原理進(jìn)行解析,簡(jiǎn)要流程如下圖:

淺析Redis大Key | 京東云技術(shù)團(tuán)隊(duì),云服務(wù),redis,京東云,bootstrap,key

4.1.1、第一部分是如何進(jìn)行找key的呢?

Redis找bigkey的函數(shù)是static void findBigKeys(int memkeys, unsigned memkeys_samples),因?yàn)楱Cmemkeys選項(xiàng)和–bigkeys選項(xiàng)是公用同一個(gè)函數(shù),所以使用memkeys時(shí)會(huì)有額外兩個(gè)參數(shù)memkeys、memkeys_sample,但這和–bigkeys選項(xiàng)沒(méi)關(guān)系,所以不用理會(huì)。findBigKeys具體函數(shù)框架為:

1.申請(qǐng)6個(gè)變量用以統(tǒng)計(jì)6種數(shù)據(jù)類(lèi)型的信息(每個(gè)變量記錄該數(shù)據(jù)類(lèi)型的key的總數(shù)量、bigkey是哪個(gè)等信息)

typedef struct {
    char *name;//數(shù)據(jù)類(lèi)型,如string
    char *sizecmd;//查詢(xún)大小命令,如string會(huì)調(diào)用STRLEN
    char *sizeunit;//單位,string類(lèi)型為bytes,而hash為field
    unsigned long long biggest;//最大key信息域,此數(shù)據(jù)類(lèi)型最大key的大小,如string類(lèi)型是多少bytes,hash為多少field
    unsigned long long count;//統(tǒng)計(jì)信息域,此數(shù)據(jù)類(lèi)型的key的總數(shù)
    unsigned long long totalsize;//統(tǒng)計(jì)信息域,此數(shù)據(jù)類(lèi)型的key的總大小,如string類(lèi)型是全部string總共多少bytes,hash為全部hash總共多少field
    sds biggest_key;//最大key信息域,此數(shù)據(jù)類(lèi)型最大key的鍵名,之所以在數(shù)據(jù)結(jié)構(gòu)末尾是考慮字節(jié)對(duì)齊
} typeinfo;

    dict *types_dict = dictCreate(&typeinfoDictType);
    typeinfo_add(types_dict, "string", &type_string);
    typeinfo_add(types_dict, "list", &type_list);
    typeinfo_add(types_dict, "set", &type_set);
    typeinfo_add(types_dict, "hash", &type_hash);
    typeinfo_add(types_dict, "zset", &type_zset);
    typeinfo_add(types_dict, "stream", &type_stream);


2.調(diào)用scan命令迭代地獲取一批key(注意只是key的名稱(chēng),類(lèi)型和大小scan命令不返回)

/* scan循環(huán)掃描 */
do {
    /* 計(jì)算完成的百分比情況 */
    pct = 100 * (double)sampled/total_keys;//這里記錄下掃描的進(jìn)度

    /* 獲取一些鍵并指向鍵數(shù)組 */
    reply = sendScan(&it);//這里發(fā)送SCAN命令,結(jié)果保存在reply中
    keys  = reply->element[1];//keys來(lái)保存這次scan獲取的所有鍵名,注意只是鍵名,每個(gè)鍵的數(shù)據(jù)類(lèi)型是不知道的。
    ......

} while(it != 0); 


3.對(duì)每個(gè)key獲取它的數(shù)據(jù)類(lèi)型(type)和key的大?。╯ize)

/* 檢索類(lèi)型,然后檢索大小*/
getKeyTypes(types_dict, keys, types);
getKeySizes(keys, types, sizes, memkeys, memkeys_samples);


4.如果key的大小大于已記錄的最大值的key,則更新最大key的信息

/* Now update our stats */
for(i=0;i<keys->elements;i++) {
    ......//前面已解析

    //如果遍歷到比記錄值更大的key時(shí)
    if(type->biggest<sizes[i]) {
        /* Keep track of biggest key name for this type */
        if (type->biggest_key)
            sdsfree(type->biggest_key);
        //更新最大key的鍵名
        type->biggest_key = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
        if(!type->biggest_key) {
            fprintf(stderr, "Failed to allocate memory for key!\n");
            exit(1);
        }

        //每當(dāng)找到一個(gè)更大的key時(shí)則輸出該key信息
        printf(
            "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
            pct, type->name, type->biggest_key, sizes[i],
            !memkeys? type->sizeunit: "bytes");

        /* Keep track of the biggest size for this type */
        //更新最大key的大小
        type->biggest = sizes[i];
    }

    ......//前面已解析
}


5.對(duì)每個(gè)key更新對(duì)應(yīng)數(shù)據(jù)類(lèi)型的統(tǒng)計(jì)信息

/* 現(xiàn)在更新統(tǒng)計(jì)數(shù)據(jù) */
for(i=0;i<keys->elements;i++) {
    typeinfo *type = types[i];
    /* 跳過(guò)在SCAN和TYPE之間消失的鍵 */
    if(!type)
        continue;

    //對(duì)每個(gè)key更新每種數(shù)據(jù)類(lèi)型的統(tǒng)計(jì)信息
    type->totalsize += sizes[i];//某數(shù)據(jù)類(lèi)型(如string)的總大小增加
    type->count++;//某數(shù)據(jù)類(lèi)型的key數(shù)量增加
    totlen += keys->element[i]->len;//totlen不針對(duì)某個(gè)具體數(shù)據(jù)類(lèi)型,將所有key的鍵名的長(zhǎng)度進(jìn)行統(tǒng)計(jì),注意只統(tǒng)計(jì)鍵名長(zhǎng)度。
    sampled++;//已經(jīng)遍歷的key數(shù)量

    ......//后續(xù)解析

    /* 更新整體進(jìn)度 */
    if(sampled % 1000000 == 0) {
        printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
    }
}


4.1.2、第二部分是如何執(zhí)行的?

1.輸出統(tǒng)計(jì)信息、最大key信息

   /* We're done */
    printf("\n-------- summary -------\n\n");
    if (force_cancel_loop) printf("[%05.2f%%] ", pct);
    printf("Sampled %llu keys in the keyspace!\n", sampled);
    printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
       totlen, totlen ? (double)totlen/sampled : 0);


2.首先輸出總共掃描了多少個(gè)key、所有key的總長(zhǎng)度是多少。

/* Output the biggest keys we found, for types we did find */
    di = dictGetIterator(types_dict);
    while ((de = dictNext(di))) {
        typeinfo *type = dictGetVal(de);
        if(type->biggest_key) {
            printf("Biggest %6s found '%s' has %llu %s\n", type->name, type->biggest_key,
               type->biggest, !memkeys? type->sizeunit: "bytes");
        }
    }
    dictReleaseIterator(di);


4.1.3、第三部分是如何執(zhí)行的?

di為字典迭代器,用以遍歷types_dict里面的所有dictEntry。de = dictNext(di)則可以獲取下一個(gè)dictEntry,de是指向dictEntry的指針。又因?yàn)閠ypeinfo結(jié)構(gòu)體保存在dictEntry的v域中,所以用dictGetVal獲取。然后就是輸出typeinfo結(jié)構(gòu)體里面保存的最大key相關(guān)的數(shù)據(jù),包括最大key的鍵名和大小。

  di = dictGetIterator(types_dict);
    while ((de = dictNext(di))) {
        typeinfo *type = dictGetVal(de);
        printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
           type->count, type->name, type->totalsize, !memkeys? type->sizeunit: "bytes",
           sampled ? 100 * (double)type->count/sampled : 0,
           type->count ? (double)type->totalsize/type->count : 0);
    }
    dictReleaseIterator(di);


4.2、使用開(kāi)源工具發(fā)現(xiàn)大Key

在不影響線上服務(wù)的同時(shí)得到精確的分析報(bào)告。使用redis-rdb-tools工具以定制化方式找出大Key,該工具能夠?qū)edis的RDB文件進(jìn)行定制化的分析,但由于分析RDB文件為離線工作,因此對(duì)線上服務(wù)不會(huì)有任何影響,這是它的最大優(yōu)點(diǎn)但同時(shí)也是它的最大缺點(diǎn):離線分析代表著分析結(jié)果的較差時(shí)效性。對(duì)于一個(gè)較大的RDB文件,它的分析可能會(huì)持續(xù)很久很久。

redis-rdb-tools的項(xiàng)目地址為:https://github.com/sripathikrishnan/redis-rdb-tools

五、如何解決Bigkey

5.1、提前預(yù)防

  • 設(shè)置過(guò)期時(shí)間,盡量過(guò)期時(shí)間分散,防止同一時(shí)間過(guò)期;
  • 存儲(chǔ)為String類(lèi)型的JSON,可以刪除不使用的Filed;

例如對(duì)象為{“userName”:“京東到家”,“ciyt”:“北京”},如果只需要用到userName屬性,那就定義新對(duì)象,只具有userName屬性,精簡(jiǎn)緩存中數(shù)據(jù)

  • 存儲(chǔ)為String類(lèi)型的JSON,利用@JsonProperty注解讓FiledName字符集縮小,代碼例子如下。但是存在緩存數(shù)據(jù)識(shí)別性低的缺點(diǎn);
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
import java.io.IOException;
public class JsonTest {
    @JsonProperty("u")
    private String userName;

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public static void main(String[] args) throws IOException {
        JsonTest output = new JsonTest();
        output.setUserName("京東到家");
        System.out.println(new ObjectMapper().writeValueAsString(output));

        String json = "{\"u\":\"京東到家\"}";
        JsonTest r1 = new ObjectMapper().readValue(json, JsonTest.class);
        System.out.println(r1.getUserName());
    }
}

{"u":"京東到家"}
京東到家


  • 采用壓縮算法,利用時(shí)間換空間,進(jìn)行序列化與反序列化。同時(shí)也存在緩存數(shù)據(jù)識(shí)別性低的缺點(diǎn);
  • 在業(yè)務(wù)上進(jìn)行干預(yù),設(shè)置閾值。比如用戶(hù)購(gòu)物車(chē)的商品數(shù)量,或者領(lǐng)券的數(shù)量,不能無(wú)限的增大;

5.2、如何優(yōu)雅刪除BigKey

5.2.1、DEL

此命令在Redis不同版本中刪除的機(jī)制并不相同,以下分別進(jìn)行分析:

redis_version < 4.0 版本:在主線程中同步刪除,刪除大Key會(huì)阻塞主線程,見(jiàn)如下源碼基于redis 3.0版本。那針對(duì)非String結(jié)構(gòu)數(shù)據(jù),可以先通過(guò)SCAN命令讀取部分?jǐn)?shù)據(jù),然后逐步進(jìn)行刪除,避免一次性刪除大key導(dǎo)致Redis阻塞。

// 從數(shù)據(jù)庫(kù)中刪除給定的鍵,鍵的值,以及鍵的過(guò)期時(shí)間。
// 刪除成功返回 1,因?yàn)殒I不存在而導(dǎo)致刪除失敗時(shí),返回 0 
int dbDelete(redisDb *db, robj *key) {
    // 刪除鍵的過(guò)期時(shí)間
    if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);

    // 刪除鍵值對(duì)
    if (dictDelete(db->dict,key->ptr) == DICT_OK) {
        // 如果開(kāi)啟了集群模式,那么從槽中刪除給定的鍵
        if (server.cluster_enabled) slotToKeyDel(key);
        return 1;
    } else {
        // 鍵不存在
        return 0;
    }
}


4.0 版本 < redis_version < 6.0 版本:引入lazy-free**,**手動(dòng)開(kāi)啟lazy-free時(shí),有4個(gè)選項(xiàng)可以控制,分別對(duì)應(yīng)不同場(chǎng)景下,是否開(kāi)啟異步釋放內(nèi)存機(jī)制:

  • lazyfree-lazy-expire:key在過(guò)期刪除時(shí)嘗試異步釋放內(nèi)存
  • lazyfree-lazy-eviction:內(nèi)存達(dá)到maxmemory并設(shè)置了淘汰策略時(shí)嘗試異步釋放內(nèi)存
  • lazyfree-lazy-server-del:執(zhí)行RENAME/MOVE等命令或需要覆蓋一個(gè)key時(shí),刪除舊key嘗試異步釋放內(nèi)存
  • replica-lazy-flush:主從全量同步,從庫(kù)清空數(shù)據(jù)庫(kù)時(shí)異步釋放內(nèi)存

開(kāi)啟lazy-free后,Redis在釋放一個(gè)key的內(nèi)存時(shí),首先會(huì)評(píng)估代價(jià),如果釋放內(nèi)存的代價(jià)很小,那么就直接在主線程中操作了,沒(méi)必要放到異步線程中執(zhí)行

redis_version >= 6.0 版本:引入lazyfree-lazy-user-del,只要開(kāi)啟了,del直接可以異步刪除key,不會(huì)阻塞主線程。具體是為什么呢,現(xiàn)在先賣(mài)個(gè)關(guān)子,在下面進(jìn)行解析。

5.2.2、SCAN

SCAN命令可以幫助在不阻塞主線程的情況下逐步遍歷大量的鍵,以及避免對(duì)數(shù)據(jù)庫(kù)的阻塞。以下代碼是利用scan來(lái)掃描集群中的Key。

public void scanRedis(String cursor,String endCursor) {
        ReloadableJimClientFactory factory = new ReloadableJimClientFactory();
        String jimUrl = "jim://xxx/546";
        factory.setJimUrl(jimUrl);
        Cluster client = factory.getClient();
        ScanOptions.ScanOptionsBuilder scanOptions = ScanOptions.scanOptions();
        scanOptions.count(100);
 
        Boolean end = false;
        int k = 0;
        while (!end) {
            KeyScanResult< String > result = client.scan(cursor, scanOptions.build());
            for (String key :result.getResult()){
                if (client.ttl(key) == -1){
                    logger.info("永久key為:{}" , key);
                }
            }
            k++;
            cursor = result.getCursor();
            if (endCursor.equals(cursor)){
                break;
            }
        }
    }


5.2.3、UNLINK

Redis 4.0 提供了 lazy delete (unlink命令) ,下面基于源碼(redis_version:7.2版本)分析下實(shí)現(xiàn)原理

  • del與unlink命令底層都調(diào)用了delGenericCommand()方法;
void delCommand(client *c) {
    delGenericCommand(c,server.lazyfree_lazy_user_del);
}
void unlinkCommand(client *c) {
    delGenericCommand(c,1);
}


  • lazyfree-lazy-user-del支持yes或者no。默認(rèn)是no;
  • 如果設(shè)置為yes,那么del命令就等價(jià)于unlink,也是異步刪除,這也同時(shí)解釋了之前咱們的問(wèn)題,為什么設(shè)置了lazyfree-lazy-user-del后,del命令就為異步刪除。
void delGenericCommand(client *c, int lazy) {
    int numdel = 0, j;
    // 遍歷所有輸入鍵
    for (j = 1; j < c->argc; j++) {
        // 先刪除過(guò)期的鍵
        expireIfNeeded(c->db,c->argv[j],0);
        int deleted  = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
                              dbSyncDelete(c->db,c->argv[j]);
        // 嘗試刪除鍵
        if (deleted) {
            // 刪除鍵成功,發(fā)送通知
            signalModifiedKey(c,c->db,c->argv[j]);
            notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[j],c->db->id);
            server.dirty++;
            // 成功刪除才增加 deleted 計(jì)數(shù)器的值
            numdel++;
        }
    }
    // 返回被刪除鍵的數(shù)量
    addReplyLongLong(c,numdel);
}


下面分析異步刪除dbAsyncDelete()與同步刪除dbSyncDelete(),底層同時(shí)也是調(diào)用dbGenericDelete()方法

int dbSyncDelete(redisDb *db, robj *key) {
    return dbGenericDelete(db, key, 0, DB_FLAG_KEY_DELETED);
}

int dbAsyncDelete(redisDb *db, robj *key) {
    return dbGenericDelete(db, key, 1, DB_FLAG_KEY_DELETED);
}

int dbGenericDelete(redisDb *db, robj *key, int async, int flags) {
    dictEntry **plink;
    int table;
    dictEntry *de = dictTwoPhaseUnlinkFind(db->dict,key->ptr,&plink,&table);
    if (de) {
        robj *val = dictGetVal(de);
        /* RM_StringDMA may call dbUnshareStringValue which may free val, so we need to incr to retain val */
        incrRefCount(val);
        /* Tells the module that the key has been unlinked from the database. */
        moduleNotifyKeyUnlink(key,val,db->id,flags);
        /* We want to try to unblock any module clients or clients using a blocking XREADGROUP */
        signalDeletedKeyAsReady(db,key,val->type);
        // 在調(diào)用用freeObjAsync之前,我們應(yīng)該先調(diào)用decrRefCount。否則,引用計(jì)數(shù)可能大于1,導(dǎo)致freeObjAsync無(wú)法正常工作。
        decrRefCount(val);
        // 如果是異步刪除,則會(huì)調(diào)用 freeObjAsync 異步釋放 value 占用的內(nèi)存。同時(shí),將 key 對(duì)應(yīng)的 value 設(shè)置為 NULL。
        if (async) {
            /* Because of dbUnshareStringValue, the val in de may change. */
            freeObjAsync(key, dictGetVal(de), db->id);
            dictSetVal(db->dict, de, NULL);
        }
        // 如果是集群模式,還會(huì)更新對(duì)應(yīng) slot 的相關(guān)信息
        if (server.cluster_enabled) slotToKeyDelEntry(de, db);

        /* Deleting an entry from the expires dict will not free the sds of the key, because it is shared with the main dictionary. */
        if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
        // 釋放內(nèi)存
        dictTwoPhaseUnlinkFree(db->dict,de,plink,table);
        return 1;
    } else {
        return 0;
    }
}


如果為異步刪除,調(diào)用freeObjAsync()方法,根據(jù)以下代碼分析:

#define LAZYFREE_THRESHOLD 64

/* Free an object, if the object is huge enough, free it in async way. */
void freeObjAsync(robj *key, robj *obj, int dbid) {
    size_t free_effort = lazyfreeGetFreeEffort(key,obj,dbid);
    if (free_effort > LAZYFREE_THRESHOLD && obj->refcount == 1) {
        atomicIncr(lazyfree_objects,1);
        bioCreateLazyFreeJob(lazyfreeFreeObject,1,obj);
    } else {
        decrRefCount(obj);
    }
}

size_t lazyfreeGetFreeEffort(robj *key, robj *obj, int dbid) {
    if (obj->type == OBJ_LIST && obj->encoding == OBJ_ENCODING_QUICKLIST) {
        quicklist *ql = obj->ptr;
        return ql->len;
    } else if (obj->type == OBJ_SET && obj->encoding == OBJ_ENCODING_HT) {
        dict *ht = obj->ptr;
        return dictSize(ht);
    } else if (obj->type == OBJ_ZSET && obj->encoding == OBJ_ENCODING_SKIPLIST){
        zset *zs = obj->ptr;
        return zs->zsl->length;
    } else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
        dict *ht = obj->ptr;
        return dictSize(ht);
    } else if (obj->type == OBJ_STREAM) {
        ...
        return effort;
    } else if (obj->type == OBJ_MODULE) {
        size_t effort = moduleGetFreeEffort(key, obj, dbid);
        /* If the module's free_effort returns 0, we will use asynchronous free
         * memory by default. */
        return effort == 0 ? ULONG_MAX : effort;
    } else {
        return 1; /* Everything else is a single allocation. */
    }
}


分析后咱們可以得出如下結(jié)論:

  • 當(dāng)Hash/Set底層采用哈希表存儲(chǔ)(非ziplist/int編碼存儲(chǔ))時(shí),并且元素?cái)?shù)量超過(guò)64個(gè)
  • 當(dāng)ZSet底層采用跳表存儲(chǔ)(非ziplist編碼存儲(chǔ))時(shí),并且元素?cái)?shù)量超過(guò)64個(gè)
  • 當(dāng)List鏈表節(jié)點(diǎn)數(shù)量超過(guò)64個(gè)(注意,不是元素?cái)?shù)量,而是鏈表節(jié)點(diǎn)的數(shù)量,List的實(shí)現(xiàn)是在每個(gè)節(jié)點(diǎn)包含了若干個(gè)元素的數(shù)據(jù),這些元素采用ziplist存儲(chǔ))
  • refcount == 1 就是在沒(méi)有引用這個(gè)Key時(shí)

只有以上這些情況,在刪除key釋放內(nèi)存時(shí),才會(huì)真正放到異步線程中執(zhí)行,其他情況一律還是在主線程操作。也就是說(shuō)String(不管內(nèi)存占用多大)、List(少量元素)、Set(int編碼存儲(chǔ))、Hash/ZSet(ziplist編碼存儲(chǔ))這些情況下的key在釋放內(nèi)存時(shí),依舊在主線程中操作。

5.3、分而治之

采用經(jīng)典算法“分治法”,將大而化小。針對(duì)String和集合類(lèi)型的Key,可以采用如下方式:

  • String類(lèi)型的大Key:可以嘗試將對(duì)象分拆成幾個(gè)Key-Value, 使用MGET或者多個(gè)GET組成的pipeline獲取值,分拆單次操作的壓力,對(duì)于集群來(lái)說(shuō)可以將操作壓力平攤到多個(gè)分片上,降低對(duì)單個(gè)分片的影響。
  • 集合類(lèi)型的大Key,并且需要整存整取要在設(shè)計(jì)上嚴(yán)格禁止這種場(chǎng)景的出現(xiàn),如無(wú)法拆分,有效的方法是將該大Key從JIMDB去除,單獨(dú)放到其他存儲(chǔ)介質(zhì)上。
  • 集合類(lèi)型的大Key,每次只需操作部分元素:將集合類(lèi)型中的元素分拆。以Hash類(lèi)型為例,可以在客戶(hù)端定義一個(gè)分拆Key的數(shù)量N,每次對(duì)HGET和HSET操作的field計(jì)算哈希值并取模N,確定該field落在哪個(gè)Key上。

如果線上服務(wù)強(qiáng)依賴(lài)Redis,需要考慮到如何做到“無(wú)感”,并保證數(shù)據(jù)一致性。咱們基本上可以采用三步走策略,如下圖所示。分別是進(jìn)行雙寫(xiě),雙讀校驗(yàn),最后讀新Key。在此基礎(chǔ)上可以設(shè)置開(kāi)關(guān),做到上線后的平穩(wěn)遷移。

淺析Redis大Key | 京東云技術(shù)團(tuán)隊(duì),云服務(wù),redis,京東云,bootstrap,key

六、總結(jié)

綜上所述,針對(duì)文章開(kāi)頭咱們購(gòu)物車(chē)大Key問(wèn)題,相信你已經(jīng)有了答案。咱們可以限制門(mén)店數(shù),限制門(mén)店中的商品數(shù)。如果不作限制,咱們也能進(jìn)行拆分,將大Key分散存儲(chǔ)。例如。將Redis中Key類(lèi)型改為L(zhǎng)ist,key為用戶(hù)與門(mén)店唯一鍵,Value為用戶(hù)在此門(mén)店下的商品。

存儲(chǔ)結(jié)構(gòu)拆分成兩種:
第一種:
    userPin:storeId的集合
第二種:
    userPin_storeId1:{門(mén)店下加車(chē)的所有商品基本信息};
    userPin_storeId2:{門(mén)店下加車(chē)的所有商品基本信息}     


以上介紹了大key的產(chǎn)生、識(shí)別、處理,以及如何使用合理策略和技術(shù)來(lái)應(yīng)對(duì)。在使用Redis過(guò)程中,防范大于治理,在治理過(guò)程中也要做到業(yè)務(wù)無(wú)感。

七、參考

https://github.com/redis/redis.git

http://redisbook.com/

https://github.com/huangz1990/redis-3.0-annotated.git

https://blog.csdn.net/ldw201510803006/article/details/124790121

https://blog.csdn.net/kuangd_1992/article/details/130451679

http://sd.jd.com/article/4930?shareId=119428&isHideShareButton=1

https://www.liujiajia.me/2023/3/28/redis-bigkeys

https://www.51cto.com/article/701990.html

https://help.aliyun.com/document_detail/353223.html

https://juejin.cn/post/7167015025154981895

https://www.jianshu.com/p/9e150d72ffc9

https://zhuanlan.zhihu.com/p/449648332

作者:京東零售?高凱

來(lái)源:京東云開(kāi)發(fā)者社區(qū) 轉(zhuǎn)載請(qǐng)注明來(lái)源文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-737860.html

到了這里,關(guān)于淺析Redis大Key | 京東云技術(shù)團(tuán)隊(duì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • SpringCloud-Hystrix服務(wù)熔斷與降級(jí)工作原理&源碼 | 京東物流技術(shù)團(tuán)隊(duì)

    SpringCloud-Hystrix服務(wù)熔斷與降級(jí)工作原理&源碼 | 京東物流技術(shù)團(tuán)隊(duì)

    在微服務(wù)架構(gòu)中,根據(jù)業(yè)務(wù)來(lái)拆分成一個(gè)個(gè)的服務(wù),服務(wù)與服務(wù)之間可以相互調(diào)用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign來(lái)調(diào)用。為了保證其高可用,單個(gè)服務(wù)通常會(huì)集群部署。由于網(wǎng)絡(luò)原因或者自身的原因,服務(wù)并不能保證100%可用,如果單個(gè)服務(wù)出現(xiàn)問(wèn)題,調(diào)用這

    2024年02月14日
    瀏覽(26)
  • 黃金眼PAAS化數(shù)據(jù)服務(wù)DIFF測(cè)試工具的建設(shè)實(shí)踐 | 京東云技術(shù)團(tuán)隊(duì)

    黃金眼PAAS化數(shù)據(jù)服務(wù)DIFF測(cè)試工具的建設(shè)實(shí)踐 | 京東云技術(shù)團(tuán)隊(duì)

    黃金眼PAAS化數(shù)據(jù)服務(wù)是一系列實(shí)現(xiàn)相同指標(biāo)服務(wù)協(xié)議的數(shù)據(jù)服務(wù),各個(gè)服務(wù)間按照所生產(chǎn)指標(biāo)的主題作劃分,比如交易實(shí)時(shí)服務(wù)提供實(shí)時(shí)交易指標(biāo)的查詢(xún),財(cái)務(wù)離線服務(wù)提供離線財(cái)務(wù)指標(biāo)的查詢(xún)。黃金眼PAAS化數(shù)據(jù)服務(wù)支撐了黃金眼APP、黃金眼PC和內(nèi)部各類(lèi)大屏的數(shù)據(jù)查詢(xún)需求

    2024年02月07日
    瀏覽(29)
  • K8S集群中使用JDOS KMS服務(wù)對(duì)敏感數(shù)據(jù)安全加密 | 京東云技術(shù)團(tuán)隊(duì)

    K8S集群中使用JDOS KMS服務(wù)對(duì)敏感數(shù)據(jù)安全加密 | 京東云技術(shù)團(tuán)隊(duì)

    KMS,Key Management Service,即密鑰管理服務(wù),在K8S集群中,以驅(qū)動(dòng)和插件的形式啟用對(duì)Secret,Configmap進(jìn)行加密。以保護(hù)敏感數(shù)據(jù), 驅(qū)動(dòng)和插件需要使用者按照需求進(jìn)行定制和實(shí)現(xiàn)自己的KMS插件,插件可以是gRPC服務(wù)器或者啟用一個(gè)云服務(wù)商提供的KMS插件。 本文中演示使用的KMS 服

    2024年02月11日
    瀏覽(16)
  • 京東搜索EE鏈路演進(jìn) | 京東云技術(shù)團(tuán)隊(duì)

    京東搜索EE鏈路演進(jìn) | 京東云技術(shù)團(tuán)隊(duì)

    搜索系統(tǒng)中容易存在頭部效應(yīng),中長(zhǎng)尾的優(yōu)質(zhì)商品較難獲得充分的展示機(jī)會(huì),如何破除系統(tǒng)的馬太效應(yīng),提升展示結(jié)果的豐富性與多樣性,助力中長(zhǎng)尾商品成長(zhǎng)是電商平臺(tái)搜索系統(tǒng)的一個(gè)重要課題。其中,搜索EE系統(tǒng)在保持排序結(jié)果基本穩(wěn)定的基礎(chǔ)上,通過(guò)將優(yōu)質(zhì)中長(zhǎng)尾商品

    2024年02月10日
    瀏覽(22)
  • 基于AIGC的京東購(gòu)物助手的技術(shù)方案設(shè)想 | 京東云技術(shù)團(tuán)隊(duì)

    基于AIGC的京東購(gòu)物助手的技術(shù)方案設(shè)想 | 京東云技術(shù)團(tuán)隊(duì)

    隨著AIGC的爆火,ChatGPT,GPT-4的發(fā)布,我作為一個(gè)算法工作者,深感AI發(fā)展的迅猛。最近,OpenAI的插件和聯(lián)網(wǎng)功能陸續(xù)向用戶(hù)公開(kāi),我也在第一時(shí)間試用了這些最新的功能。在OpenAI的插件市場(chǎng)上,我被一個(gè)可以幫助分析食譜,并生成購(gòu)物清單的功能所吸引。我開(kāi)始思考,如果我

    2024年02月12日
    瀏覽(27)
  • 技術(shù)賦能-混流編排功能,助力京東618直播重保 | 京東云技術(shù)團(tuán)隊(duì)

    技術(shù)賦能-混流編排功能,助力京東618直播重保 | 京東云技術(shù)團(tuán)隊(duì)

    每每到618、雙11這樣的大型活動(dòng)的時(shí)候,每天都有幾個(gè)重要的大v或者品牌直播需要保障。 以往的重點(diǎn)場(chǎng)次監(jiān)播方式是這么造的: 對(duì)每路直播的源流、各檔轉(zhuǎn)碼流分別起一個(gè)ffplay播放窗口,再手動(dòng)調(diào)整尺寸在顯示器桌面進(jìn)行布局,排到一屏里來(lái)監(jiān)播。 這樣做的缺點(diǎn): 操作復(fù)雜

    2024年02月08日
    瀏覽(23)
  • 初探webAssembly | 京東物流技術(shù)團(tuán)隊(duì)

    初探webAssembly | 京東物流技術(shù)團(tuán)隊(duì)

    一種運(yùn)行在現(xiàn)代網(wǎng)絡(luò)瀏覽器中的新型代碼,并且提供新的性能特性和效果 W3C WebAssembly Community Group開(kāi)發(fā)的一項(xiàng)網(wǎng)絡(luò)標(biāo)準(zhǔn),對(duì)于瀏覽器而言,WebAssembly 提供了一條途徑,讓各種語(yǔ)言編寫(xiě)的代碼以接近原生的速度在 Web 中運(yùn)行。在這種情況下,以前無(wú)法以此方式運(yùn)行的客戶(hù)端軟件等

    2024年02月15日
    瀏覽(19)
  • Spring源碼核心剖析 | 京東云技術(shù)團(tuán)隊(duì)

    Spring源碼核心剖析 | 京東云技術(shù)團(tuán)隊(duì)

    SpringAOP作為Spring最核心的能力之一,其重要性不言而喻。然后需要知道的是AOP并不只是Spring特有的功能,而是一種思想,一種通用的功能。而SpringAOP只是在AOP的基礎(chǔ)上將能力集成到SpringIOC中,使其作為bean的一種,從而我們能夠很方便的進(jìn)行使用。 1.1 使用場(chǎng)景 當(dāng)我們?cè)谌粘I(yè)

    2024年02月10日
    瀏覽(23)
  • 定時(shí)任務(wù)原理方案綜述 | 京東云技術(shù)團(tuán)隊(duì)

    定時(shí)任務(wù)原理方案綜述 | 京東云技術(shù)團(tuán)隊(duì)

    本文主要介紹目前存在的定時(shí)任務(wù)處理解決方案。業(yè)務(wù)系統(tǒng)中存在眾多的任務(wù)需要定時(shí)或定期執(zhí)行,并且針對(duì)不同的系統(tǒng)架構(gòu)也需要提供不同的解決方案。京東內(nèi)部也提供了眾多定時(shí)任務(wù)中間件來(lái)支持,總結(jié)當(dāng)前各種定時(shí)任務(wù)原理,從定時(shí)任務(wù)基礎(chǔ)原理、單機(jī)定時(shí)任務(wù)(單線

    2024年02月09日
    瀏覽(54)
  • 事務(wù),不只ACID | 京東物流技術(shù)團(tuán)隊(duì)

    事務(wù),不只ACID | 京東物流技術(shù)團(tuán)隊(duì)

    1. 什么是事務(wù)? 應(yīng)用在運(yùn)行時(shí)可能會(huì)發(fā)生數(shù)據(jù)庫(kù)、硬件的故障,應(yīng)用與數(shù)據(jù)庫(kù)的網(wǎng)絡(luò)連接斷開(kāi)或多個(gè)客戶(hù)端端并發(fā)修改數(shù)據(jù)導(dǎo)致預(yù)期之外的數(shù)據(jù)覆蓋問(wèn)題,為了提高應(yīng)用的可靠性和數(shù)據(jù)的一致性, 事務(wù) 應(yīng)運(yùn)而生。 從概念上講,事務(wù)是 應(yīng)用程序?qū)⒍鄠€(gè)讀寫(xiě)操作組合成一個(gè)邏

    2024年02月13日
    瀏覽(16)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包