從源碼分析 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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...