從源碼分析 Redis 非同步刪除各個參數的具體作用

来源:https://www.cnblogs.com/ivictor/archive/2023/11/27/17858619.html
-Advertisement-
Play Games

以前對非同步刪除幾個參數的作用比較模糊,包括網上的很多資料都是一筆帶過,語焉不詳。 所以這次從源碼(基於 Redis 7.0.5)的角度來深入分析下這幾個參數的具體作用: lazyfree-lazy-user-del lazyfree-lazy-user-flush lazyfree-lazy-ser ...


以前對非同步刪除幾個參數的作用比較模糊,包括網上的很多資料都是一筆帶過,語焉不詳。

所以這次從源碼(基於 Redis 7.0.5)的角度來深入分析下這幾個參數的具體作用:

  • lazyfree-lazy-user-del
  • lazyfree-lazy-user-flush
  • lazyfree-lazy-server-del
  • lazyfree-lazy-expire
  • lazyfree-lazy-eviction
  • slave-lazy-flush

lazyfree-lazy-user-del

在 Redis 4.0 之前,通常不建議直接使用 DEL 命令刪除一個 KEY。這是因為,如果這個 KEY 是一個包含大量數據的大 KEY,那麼這個刪除操作就會阻塞主線程,導致 Redis 無法處理其他請求。這種情況下,一般是建議分而治之,即批量刪除 KEY 中的元素。

在 Redis 4.0 中,引入了非同步刪除機制,包括一個新的命令 -UNLINK。該命令的作用同DEL一樣,都用來刪除 KEY。只不過DEL命令是在主線程中同步執行刪除操作。而UNLINK命令則是通過後臺線程非同步執行刪除操作,即使碰到一個大 KEY,也不會導致主線程被阻塞。

如果應用之前用的是DEL,要使用UNLINK,就意味著代碼需要改造,而代碼改造顯然是個費時費力的事情。

為瞭解決這個痛點,在 Redis 6.0 中,引入了參數 lazyfree-lazy-user-del。將該參數設置為 yes(預設為 no),則通過DEL命令刪除 KEY,效果同UNLINK一樣,都是執行非同步刪除操作。

以下是DEL命令和UNLINK命令的實現代碼。

// DEL 命令調用的函數
void delCommand(client *c) {
    delGenericCommand(c,server.lazyfree_lazy_user_del);
}

// UNLINK 命令調用的函數
void unlinkCommand(client *c) {
    delGenericCommand(c,1);
}

可以看到,當 server.lazyfree_lazy_user_del 設置為 yes 時,DEL命令實際上調用的就是 delGenericCommand(c,1),與UNLINK命令一樣。

lazyfree-lazy-user-flush

在 Redis 中,如果要清除整個資料庫的數據,可使用FLUSHALL(清除所有資料庫的數據)或 FLUSHDB(清除當前資料庫的數據)。

在 Redis 4.0 之前,這兩個命令都是在主線程中執行的。如果要清除的 KEY 比較多,同樣會導致主線程被阻塞。

如果使用的是 Redis Cluster,在執行此類操作時,很容易會觸發主從切換。

主要原因是在刪除期間,主節點無法響應集群其它節點的心跳請求。如果沒有響應持續的時間超過 cluster-node-timeout(預設15 秒),主節點就會被集群其它節點判定為故障,進而觸發故障切換流程,將從節點提升為主節點。

這個時候,原主節點會降級為從節點,降級後,原主節點又會重新從新的主節點上同步數據。所以,雖然原主節點上執行了 FLUSH 操作,但發生故障切換後,數據又同步過來了。如果再對新的主節點執行 FLUSH 操作,同樣會觸發主從切換。

所以,在這種情況下,建議將參數 cluster-node-timeout 調整為一個比較大的值(預設是 15 秒),這樣就可以確保主節點有充足的時間來執行 FLUSH 操作而不會觸發切換流程。

在 Redis 4.0 中,FLUSHALL 和 FLUSHDB 命令新增了一個 ASYNC 修飾符,可用來進行非同步刪除操作。如果不加 ASYNC,則還是主線程同步刪除。

FLUSHALL ASYNC
FLUSHDB ASYNC

在 Redis 6.2.0 中,FLUSHALLFLUSHDB命令又新增了一個 SYNC 修飾符,它的效果與之前的FLUSHALLFLUSHDB命令一樣,都是用來進行同步刪除操作。

既然效果一樣,為什麼要引入這個修飾符呢?這實際上與 Redis 6.2.0 中引入的 lazyfree-lazy-user-flush 參數有關。該參數控制了沒有加修飾符的FLUSHALLFLUSHDB命令的行為。

預設情況下,lazyfree-lazy-user-flush 的值為 no,這意味著 FLUSHALL/FLUSHDB 將執行同步刪除操作。如果將 lazyfree-lazy-user-flush 設置為 yes,即使不加 ASYNC 修飾符,FLUSHALL/FLUSHDB 也會進行非同步刪除。

以下是 lazyfree-lazy-user-flush 參數的相關代碼:

/* Return the set of flags to use for the emptyDb() call for FLUSHALL
 * and FLUSHDB commands.
 *
 * sync: flushes the database in an sync manner.
 * async: flushes the database in an async manner.
 * no option: determine sync or async according to the value of lazyfree-lazy-user-flush.
 *
 * On success C_OK is returned and the flags are stored in *flags, otherwise
 * C_ERR is returned and the function sends an error to the client. */
int getFlushCommandFlags(client *c, int *flags) {
    /* Parse the optional ASYNC option. */
    if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"sync")) {
        *flags = EMPTYDB_NO_FLAGS;
    } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"async")) {
        *flags = EMPTYDB_ASYNC;
    } else if (c->argc == 1) {
        *flags = server.lazyfree_lazy_user_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS;
    } else {
        addReplyErrorObject(c,shared.syntaxerr);
        return C_ERR;
    }
    return C_OK;
}

可以看到,在不指定任何修飾符的情況下(c->argc == 1),修飾符的取值由 server.lazyfree_lazy_user_flush 決定。

lazyfree-lazy-server-del

lazyfree-lazy-server-del 主要用在兩個函數中:dbDeletedbOverwrite。這兩個函數的實現代碼如下:

/* This is a wrapper whose behavior depends on the Redis lazy free
 * configuration. Deletes the key synchronously or asynchronously. */
int dbDelete(redisDb *db, robj *key) {
    return dbGenericDelete(db, key, server.lazyfree_lazy_server_del);
}

/* Overwrite an existing key with a new value. Incrementing the reference
 * count of the new value is up to the caller.
 * This function does not modify the expire time of the existing key.
 *
 * The program is aborted if the key was not already present. */
void dbOverwrite(redisDb *db, robj *key, robj *val) {
    dictEntry *de = dictFind(db->dict,key->ptr);
    ...
    if (server.lazyfree_lazy_server_del) {
        freeObjAsync(key,old,db->id);
        dictSetVal(db->dict, &auxentry, NULL);
    }

    dictFreeVal(db->dict, &auxentry);
}

下麵我們分別看看這兩個函數的使用場景。

dbDelete

dbDelete 函數主要用於 Server 端的一些內部刪除操作,常用於以下場景:

  1. 執行MIGRATE命令時,刪除源端實例的 KEY。

  2. RESTORE命令中,如果指定了 REPLACE 選項,當指定的 KEY 存在時,會調用 dbDelete 刪除這個 KEY。

  3. 通過POPTRIM之類的命令從列表(List),集合(Set),有序集合(Sorted Set)中彈出或者移除元素時,當 KEY 為空時,會調用 dbDelete 刪除這個 KEY。

  4. SINTERSTOREZINTERSTORE等 STORE 命令中。這些命令會計算多個集合(有序集合)的交集、並集、差集,並將結果存儲在一個新的 KEY 中。如果交集、並集、差集的結果為空,當用來存儲的 KEY 存在時,會調用 dbDelete 刪除這個 KEY。

dbOverwrite

dbOverwrite 主要是用於 KEY 存在的場景,新值覆蓋舊值。主要用於以下場景:

  1. SET 相關的命令。如 SETSETNXSETEXHSETMSET
  2. SINTERSTOREZINTERSTORE 等 STORE 命令中。如果交集、並集、差集的結果不為空,且用來存儲的 KEY 存在,則該 KEY 的值會通過 dbOverwrite 覆蓋。

lazyfree-lazy-expire

lazyfree-lazy-expire 主要用於以下四種場景:

1. 刪除過期 KEY,包括主動訪問時刪除和 Redis 定期刪除。

不僅如此,該參數還決定了刪除操作傳播給從庫及寫到 AOF 文件中是用DEL還是UNLINK

/* Delete the specified expired key and propagate expire. */
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
    mstime_t expire_latency;
    latencyStartMonitor(expire_latency);
    // 刪除過期 KEY
    if (server.lazyfree_lazy_expire)
        dbAsyncDelete(db,keyobj);
    else
        dbSyncDelete(db,keyobj);
    latencyEndMonitor(expire_latency);
    latencyAddSampleIfNeeded("expire-del",expire_latency);
    notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",keyobj,db->id);
    signalModifiedKey(NULL, db, keyobj);
    // 將刪除操作傳播給從庫及寫到 AOF 文件中
    propagateDeletion(db,keyobj,server.lazyfree_lazy_expire);
    server.stat_expiredkeys++;
}

2. 主庫啟動,載入 RDB 的時候,當碰到過期 KEY 時,該參數決定了刪除操作傳播給從庫是用DEL還是UNLINK

if (iAmMaster() &&
    !(rdbflags & RDBFLAGS_AOF_PREAMBLE) &&
    expiretime != -1 && expiretime < now)
{
    if (rdbflags & RDBFLAGS_FEED_REPL) {
        /* Caller should have created replication backlog,
         * and now this path only works when rebooting,
         * so we don't have replicas yet. */
        serverAssert(server.repl_backlog != NULL && listLength(server.slaves) == 0);
        robj keyobj;
        initStaticStringObject(keyobj, key);
        robj *argv[2];
        argv[0] = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
        argv[1] = &keyobj;
        replicationFeedSlaves(server.slaves, dbid, argv, 2);
    }
    sdsfree(key);
    decrRefCount(val);
    server.rdb_last_load_keys_expired++;
}

3. EXPIRE,PEXPIREEXPIREATPEXPIREAT命令中,當設置的時間過期時(譬如 EXPIRE/PEXPIRE 中指定了負值或者 EXPIREAT/PEXPIREAT指定了過去的時間戳),將導致 KEY 被刪除而不是過期。

4. GETEX命令中,如果通過 EXAT unix-time-seconds 或者 PXAT unix-time-milliseconds 指定了過期時間,當指定的時間戳過期時,將導致 KEY 被刪除而不是過期。

lazyfree-lazy-eviction

當 Redis 記憶體不足時,會刪除部分 KEY 來釋放記憶體。

lazyfree-lazy-eviction 決定了KEY 刪除的方式及刪除操作傳播給從庫和寫到 AOF 文件中是用DEL還是UNLINK

/* Finally remove the selected key. */
if (bestkey) {
    db = server.db + bestdbid;
    robj *keyobj = createStringObject(bestkey, sdslen(bestkey));
    delta = (long long) zmalloc_used_memory();
    latencyStartMonitor(eviction_latency);
    // 刪除 KEY 
    if (server.lazyfree_lazy_eviction)
        dbAsyncDelete(db, keyobj);
    else
        dbSyncDelete(db, keyobj);
    latencyEndMonitor(eviction_latency);
    latencyAddSampleIfNeeded("eviction-del", eviction_latency);
    delta -= (long long) zmalloc_used_memory();
    mem_freed += delta;
    server.stat_evictedkeys++;
    signalModifiedKey(NULL, db, keyobj);
    notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted", keyobj, db->id);
    // 將刪除操作傳播給從庫並寫到 AOF 文件中
    propagateDeletion(db, keyobj, server.lazyfree_lazy_eviction);
    decrRefCount(keyobj);
    keys_freed++;
    ...
}

slave-lazy-flush

Redis 主從複製中,從節點在載入主節點的 RDB 文件之前,首先會清除自身的數據,slave-lazy-flush 決定了數據清除的方式。

/* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(connection *conn) {
    char buf[PROTO_IOBUF_LEN];
    ssize_t nread, readlen, nwritten;
    int use_diskless_load = useDisklessLoad();
    redisDb *diskless_load_tempDb = NULL;
    functionsLibCtx* temp_functions_lib_ctx = NULL;
    int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
                                                        EMPTYDB_NO_FLAGS;
    ...
    if (use_diskless_load && server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
        /* Initialize empty tempDb dictionaries. */
        diskless_load_tempDb = disklessLoadInitTempDb();
        temp_functions_lib_ctx = functionsLibCtxCreate();

        moduleFireServerEvent(REDISMODULE_EVENT_REPL_ASYNC_LOAD,
                              REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED,
                              NULL);
    } else {
        replicationAttachToNewMaster();

        serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
        emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
    }
    ...
}

總結

綜合上面的分析,非同步刪除各參數的作用如下,

圖片

註意,這幾個參數的預設值都是 no。

另外,在通過POPTRIM之類的命令從列表(List),集合(Set),有序集合(Sorted Set)中彈出或者移除元素時,對於這些元素的刪除都是同步的,並不會非同步刪除。如果元素的值過大(最大值由 proto-max-bulk-len 決定,預設是 512MB),依然會阻塞主線程。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Proj4:改進LiteOS中物理記憶體分配演算法 實驗目的 掌握LiteOS系統調用的自定義方法 實驗環境 Ubantu和IMX6ULL mini 實驗內容 (從代碼角度詳細描述實驗的步驟和過程) 原先代碼: 1 /* 2 3 * Description : find suitable free bl ...
  • Shell使用 在圖標和視窗占據電腦屏幕之前。需要輸入命令與大多數電腦進行交互。在UNIX系統(Linux系統派生自該系統)中,用來解釋和管理命令的程式稱為shell。 Shell類型: Bash shell,Bourne Again Shell,預設shell。 BSD UNIX用戶中流行的C ...
  • GMAC網卡Fixed-Link模式GMACfixed-link固定鏈接模式,mac與對端的連接方式是寫死的,通常用於mac to mac(不排除mac to phy的情況)。內核要支持fixed-link模式,需要打開CONFIG_FIXED_PHY配置。 社區版linux的gmac網卡platf ...
  • SQL RIGHT JOIN關鍵字 SQL RIGHT JOIN關鍵字返回右表(table2)中的所有記錄以及左表(table1)中的匹配記錄。如果沒有匹配,則左側的結果為0條記錄。 RIGHT JOIN語法 SELECT column_name(s) FROM table1 RIGHT JOIN ...
  • 本文分享自華為雲社區《DTSE Tech Talk | 3招解決時序數據高基數難題,性能多維度提升!》,作者:華為雲開源。 本期《openGemini全新列存引擎,為您解決時序數據高基數難題》的主題直播中,華為雲開源DTSE技術佈道師&資料庫創新Lab技術專家黃飛騰,與開發者朋友們分享了時序資料庫的 ...
  • 負載均衡 此處的負載均衡指的是FE層的負載均衡. 當部署多個 FE 節點時,用戶可以在多個 FE 之上部署負載均衡層來實現 Doris 的高可用。官方文檔描述: 負載均衡 。 實現方式 實現方式有多種,如下列舉。 開發者在應用層自己進行重試與負載均衡。 JDBC Connector 發現一個連接掛掉 ...
  • 閱讀 Doris SQL 原理解析,總結下Doris中SQL解析流程: 詞法識別:解析原始SQL文本,拆分token 語法識別:將token轉換成AST 單機邏輯查詢計劃:將AST經過一系列的優化(比如,謂詞下推等)成查詢計劃,提高執行性能與效率。 分散式邏輯查詢計劃:根據分散式環境(數據分佈信息、 ...
  • 本文分享自華為雲社區《多模歸一,一生萬物——華為雲多模資料庫GeminiDB架構與應用實踐》,作者: GaussDB 資料庫 。 在這個信息爆炸的時代,數據的管理和應用變得越來越重要。互聯網用戶的規模化使得業務數據也呈現出多樣性,包括社交關係、系統日誌、Json、KV等。同時,越來越多的軟體開發團隊 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...