最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis過期鍵的刪除策略分享

 更新時間:2024年11月07日 10:40:35   作者:XYY_CN  
redis是內(nèi)存型數(shù)據(jù)庫,可對鍵設置過期時間,當鍵過期時時怎么淘汰這些鍵的呢?我們先來想一想,如果讓我們設計,我們會想到哪些過期刪除策略呢?本文給大家詳細介紹了Redis過期鍵的刪除策略,需要的朋友可以參考下

Redis過期鍵刪除策略

redis是內(nèi)存型數(shù)據(jù)庫,可對鍵設置過期時間,當鍵過期時時怎么淘汰這些鍵的呢?
我們先來想一想,如果讓我們設計,我們會想到哪些過期刪除策略呢?

  1. 定時器,創(chuàng)建一個定時器,定時器經(jīng)過expire時間后開始執(zhí)行鍵的刪除操作。
  2. 周期刪除,啟動一個周期性的后臺任務,掃描鍵,發(fā)現(xiàn)過期鍵則進行刪除。
  3. 惰性刪除,當訪問一個鍵時,如果發(fā)現(xiàn)鍵過期,再執(zhí)行刪除。
  • 首先,定時器方式,資源消耗很大,不可能為每一個鍵創(chuàng)建一個定時器;另外當過期時間被重置時,定時器需要復位和重置,相當繁瑣。
  • 其次,周期刪除,過期鍵的刪除與周期執(zhí)行時間有關,可能會存在過期鍵很長時間沒有被刪除的情況,同時如果本周期內(nèi)過期鍵很多,刪除耗時會很長,增加服務負擔。
  • 最后,惰性刪除,只針對熱點數(shù)據(jù)有效,有些冷數(shù)據(jù)可能會常駐內(nèi)存。

而Redis采用了周期刪除和惰性刪除結(jié)合的方式,同時定期刪除會限制刪除耗時和鍵數(shù)來防止依次定期刪除執(zhí)行太久。

  • 惰性刪除(訪問到過期鍵時刪除),
  • 定期刪除(周期性的后臺刪除任務)。在源碼中兩種方式的入口如下:

源碼入口

定期刪除

redis在啟動初始化時(initServer)會注冊周期任務aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL), serverCron函數(shù)中包含了所有周期任務,其中databasesCron 是操作數(shù)據(jù)庫的函數(shù)。

  • 對于master節(jié)點執(zhí)行過期鍵刪除activeExpireCycle
  • 對于從節(jié)點執(zhí)行expireSlaveKeys, 這里注意如果從節(jié)點只讀,那么是不會有過期鍵淘汰的,此時依靠的是master節(jié)點同步刪除命令。但是對于可寫的從節(jié)點,會有一個dict(slaveKeysWithExpire)記錄從節(jié)點設置了過期鍵的節(jié)點,從這個dict中進行過期淘汰

在這里插入圖片描述

周期淘汰過期鍵的任務activeExpireCycle,該函數(shù)會一次遍歷db,檢查db->expires字典(存儲設置了過期時間鍵的ttl),對過期鍵進行刪除。

activeExpireCycle核心流程

下面是activeExpireCycle函數(shù)的主要邏輯,我們省略很多代碼,來看下淘汰的主要流程是什么。

  1. 首先是根據(jù)本次淘汰需要遍歷的db數(shù)(dbs_per_call ), 接著上一次遍歷的db開始繼續(xù)遍歷。timelimit_exit 表示一次定期淘汰策略執(zhí)行是有時間限制的。
  2. 對每個db來說,在do{}while()中進行鍵的淘汰,我們來看一下退出條件:直到過期比例(expired*100/sampled)小于配置參數(shù)(config_cycle_acceptable_stale),退出循環(huán),認為該db淘汰任務執(zhí)行完畢。
  3. 然后對每個db來說,需要依次遍歷兩個dict(0:前臺字典,1:rehash用的字典)。此時的退出條件是sampled >= num || checked_buckets >= max_buckets,也就是說采樣鍵數(shù)量(sampled )達到設定值或者hash槽數(shù)量(checked_buckets )達到設定值。這里說明,在淘汰每個db中的鍵時,采樣和檢查的鍵數(shù)時有限的。
  4. 對于兩個字典來說,依賴db->expires_cursor的自增來訪問每個hash槽,驗證該槽上的鍵是否過期。
    // 依次遍歷db
    for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
        unsigned long expired, sampled;
        redisDb *db = server.db+(current_db % server.dbnum);
        current_db++;
        // 針對每個db
        do {
            while (sampled < num && checked_buckets < max_buckets) {
                // 針對每個字典
                for (int table = 0; table < 2; table++) {
                    if (table == 1 && !dictIsRehashing(db->expires)) break;
                    unsigned long idx = db->expires_cursor;
                    idx &= DICTHT_SIZE_MASK(db->expires->ht_size_exp[table]);
                    dictEntry *de = db->expires->ht_table[table][idx];
                    checked_buckets++;
                    while(de) {
                        dictEntry *e = de;
                        de = de->next;
                        if (activeExpireCycleTryExpire(db,e,now)) expired++;
                        sampled++;
                    }
                }
                db->expires_cursor++;
            }
        } while (sampled == 0 || (expired*100/sampled) > config_cycle_acceptable_stale);
    }

淘汰相關的關鍵參數(shù)

有一些關鍵參數(shù)用于控制周期任務執(zhí)行的時間在一個可接受的范圍。

  • 采樣鍵數(shù)num的設置。num是控制每個db的采樣鍵數(shù)。num取的是過期字典db->expire的鍵數(shù)量和config_keys_per_loop中的最小值, config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP + ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort, ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP=20, effort是配置參數(shù)屬于[0,9]區(qū)間,則config_keys_per_loop 屬于[20, 65]區(qū)間。
  • 采樣槽數(shù)max_buckets, 控制的是最大hash槽數(shù)。而訪問hash槽采用的是自增index的方式,所以可能存在大量的hash槽對應的空節(jié)點,因此最大hash槽的設置是大于采樣鍵數(shù)的,取得是20倍采樣鍵數(shù)。

在這里插入圖片描述

  • 單次執(zhí)行時間限制 timelimit、timelimit_exit。timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;, 執(zhí)行時間是根據(jù)CPU占比(ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +2*effort)估算出來的一個時間。每執(zhí)行16次do{}while()中的內(nèi)容會計算一次耗時。
  • ACTIVE_EXPIRE_CYCLE_FAST,快速淘汰模式。在快速淘汰模式下,會增加單詞執(zhí)行的時間限制和遍歷的db數(shù)。

activeExpireCycleTryExpire

該函數(shù)會判斷鍵是否過期,過期的話執(zhí)行刪除。首先計算節(jié)點de是否過期, now > t(改鍵的過期時間戳) 時表明該鍵已經(jīng)過期,然后生成一個key對象,執(zhí)行從db中刪除指定key的操作。

int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
    long long t = dictGetSignedIntegerVal(de);
    if (now > t) {
        sds key = dictGetKey(de);
        robj *keyobj = createStringObject(key,sdslen(key));
        deleteExpiredKeyAndPropagate(db,keyobj);
        decrRefCount(keyobj);
        return 1;
    } else {
        return 0;
    }
}

惰性刪除

expireIfNeeded 該函數(shù)會在查找指定鍵時被調(diào)用,用于檢查該鍵是否過期,過期的話執(zhí)行刪除(deleteExpiredKeyAndPropagate)。

void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
    mstime_t expire_latency;
    latencyStartMonitor(expire_latency);
    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);
    propagateDeletion(db,keyobj,server.lazyfree_lazy_expire);
    server.stat_expiredkeys++;
}

刪除接口

下圖對redis的刪除接口進行了總結(jié),方便進行理解和源碼閱讀。

redis刪除接口調(diào)用圖

從字典中刪除key接口

1. 真正從字典中刪除對應key(dictFreeUnlinkedEntry)

void dictFreeUnlinkedEntry(dict *d, dictEntry *he) {
    if (he == NULL) return;
    dictFreeKey(d, he);
    dictFreeVal(d, he);
    zfree(he);
}

dictFreeUnlinkedEntry 函數(shù)會調(diào)用字典d的key,value銷毀函數(shù)先銷毀he的key,value,然后釋放he。

2. 從字典中搜索并刪除對應key(dictGenericDelete)

static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
    uint64_t h, idx;
    dictEntry *he, *prevHe;
    int table;

    /* dict is empty */
    if (dictSize(d) == 0) return NULL;

    if (dictIsRehashing(d)) _dictRehashStep(d);
    h = dictHashKey(d, key);

    for (table = 0; table <= 1; table++) {
        idx = h & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
        he = d->ht_table[table][idx];
        prevHe = NULL;
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                /* Unlink the element from the list */
                if (prevHe)
                    prevHe->next = he->next;
                else
                    d->ht_table[table][idx] = he->next;
                if (!nofree) {
                    dictFreeUnlinkedEntry(d, he);
                }
                d->ht_used[table]--;
                return he;
            }
            prevHe = he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) break;
    }
    return NULL; /* not found */
}
  1. 檢查字典是否正在進行rehash,由于redis采用的是漸進式rehash,對于處在rehash狀態(tài)的字典這里會執(zhí)行一步rehash操作
  2. 計算key的hash值
  3. 遍歷字典。我們知道redis的字典中實際存在兩個hashtable, 依次遍歷這兩個hashtable,查找對應的key是否存在
  4. 找到對應key后首先改變鏈表鏈接關系,若nofree為0則執(zhí)行真實刪除操作,從字典中刪除key
  5. 最終返回找到的節(jié)點地址

3. 真實刪除(dictDelete)

int dictDelete(dict *ht, const void *key) {
    return dictGenericDelete(ht,key,0) ? DICT_OK : DICT_ERR;
}

從字典中刪除key對應節(jié)點并釋放對應內(nèi)存。

4. Unlink(dictUnlink)

dictEntry *dictUnlink(dict *d, const void *key) {
    return dictGenericDelete(d,key,1);
}

從字典中移除key對應的節(jié)點并返回,但并不釋放節(jié)點內(nèi)存。

從db中刪除key接口

1. dbGenericDelete

static int dbGenericDelete(redisDb *db, robj *key, int async) {
    /* 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);
    dictEntry *de = dictUnlink(db->dict,key->ptr);
    if (de) {
        robj *val = dictGetVal(de);
        /* Tells the module that the key has been unlinked from the database. */
        moduleNotifyKeyUnlink(key,val,db->id);
        /* We want to try to unblock any client using a blocking XREADGROUP */
        if (val->type == OBJ_STREAM)
            signalKeyAsReady(db,key,val->type);
        if (async) {
            freeObjAsync(key, val, db->id);
            dictSetVal(db->dict, de, NULL);
        }
        if (server.cluster_enabled) slotToKeyDelEntry(de, db);
        dictFreeUnlinkedEntry(db->dict,de);
        return 1;
    } else {
        return 0;
    }
}
  • 首先從過期字典中刪除對應key
  • 從字典中unlink掉key對應節(jié)點

總結(jié)

redis對于設置了過期時間的鍵,會在db->expires中存儲該鍵對應的過期時間戳。過期鍵刪除策略是定期刪除和惰性刪除相結(jié)合的策略。惰性刪除是指訪問到該鍵時校驗是否過期,過期執(zhí)行刪除。
對于周期刪除,策略較復雜,為了保證定期刪除可控,嚴格控制了每次定期刪除時遍歷db數(shù)量、采樣鍵數(shù)量、執(zhí)行時間等指標。定期刪除提供了fast模式,該模式下會放寬執(zhí)行時間和遍歷的db數(shù)。
通過這兩種方式,來處理redis的過期鍵,保證過期邏輯和內(nèi)存可控。

以上就是Redis過期鍵的刪除策略分享的詳細內(nèi)容,更多關于Redis過期鍵刪除的資料請關注腳本之家其它相關文章!

相關文章

  • redis發(fā)布訂閱模式的實現(xiàn)

    redis發(fā)布訂閱模式的實現(xiàn)

    本文主要介紹了redis發(fā)布訂閱模式,Redis?的?SUBSCRIBE?命令可以讓客戶端訂閱任意數(shù)量的頻道,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧
    2024-04-04
  • php結(jié)合redis實現(xiàn)高并發(fā)下的搶購、秒殺功能的實例

    php結(jié)合redis實現(xiàn)高并發(fā)下的搶購、秒殺功能的實例

    下面小編就為大家?guī)硪黄猵hp結(jié)合redis實現(xiàn)高并發(fā)下的搶購、秒殺功能的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • Redis中大key處理的問題解決

    Redis中大key處理的問題解決

    本文主要介紹了Redis中大key處理的問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2026-03-03
  • Redis過期監(jiān)聽機制,訂單超時自動取消方式

    Redis過期監(jiān)聽機制,訂單超時自動取消方式

    這篇文章主要介紹了Redis過期監(jiān)聽機制,訂單超時自動取消方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Redis教程(十四):內(nèi)存優(yōu)化介紹

    Redis教程(十四):內(nèi)存優(yōu)化介紹

    這篇文章主要介紹了Redis教程(十四):內(nèi)存優(yōu)化介紹,本文講解了特殊編碼、BIT和Byte級別的操作、盡可能使用Hash等內(nèi)容,需要的朋友可以參考下
    2015-05-05
  • Redis本地鎖和分布式鎖的區(qū)別小結(jié)

    Redis本地鎖和分布式鎖的區(qū)別小結(jié)

    Redis本地鎖和分布式鎖在設計目的、實現(xiàn)方式和應用場景上都有顯著區(qū)別,下面就來介紹一下,具有一定的參考價值,感興趣的可以了解一下
    2025-04-04
  • Redis通用命令介紹以及key的層級結(jié)構(gòu)講解

    Redis通用命令介紹以及key的層級結(jié)構(gòu)講解

    這篇文章主要介紹了Redis通用命令以及key的層級結(jié)構(gòu),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-12-12
  • Redis數(shù)據(jù)編碼詳解

    Redis數(shù)據(jù)編碼詳解

    這篇文章主要介紹了Redis數(shù)據(jù)編碼的相關知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2026-03-03
  • 詳解Redis終端操作和Redis-plus-plus接口使用教程

    詳解Redis終端操作和Redis-plus-plus接口使用教程

    這篇文章主要介紹了詳解Redis終端操作和Redis-plus-plus接口使用教程,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2026-03-03
  • 基于Redis實現(xiàn)短信驗證碼登錄功能

    基于Redis實現(xiàn)短信驗證碼登錄功能

    對于我們用戶來講,我們在登陸一個APP的時候,有很多種登陸方式,比如"微信掃碼"、"手機號登陸"、"支付寶掃碼"、"賬號密碼登錄",現(xiàn)在大多都會要求微信掃碼登錄或者是手機號驗證碼登錄,所以本文給大家介紹了基于Redis實現(xiàn)短信驗證碼登錄功能,需要的朋友可以參考下
    2025-01-01

最新評論

宁城县| 宽城| 南丹县| SHOW| 绥滨县| 苏尼特左旗| 永嘉县| 迁安市| 德格县| 安化县| 平安县| 卓尼县| 荥经县| 萨嘎县| 乌海市| 延吉市| 邻水| 惠东县| 陇川县| 景宁| 灵璧县| 雅安市| 贵港市| 榆林市| 渭南市| 陈巴尔虎旗| 清丰县| 云南省| 澄江县| 九江县| 东海县| 铁岭市| 卢氏县| 东丽区| 凤山市| 文安县| 堆龙德庆县| 青神县| 新巴尔虎右旗| 原平市| 开化县|