在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ 大數(shù)據(jù)/ Redis 數(shù)據(jù)淘汰機制
Redis 數(shù)據(jù)淘汰機制
積分排行榜
小剖 Memcache
Redis 數(shù)據(jù)結(jié)構(gòu) intset
分布式鎖
從哪里開始讀起,怎么讀
Redis 數(shù)據(jù)結(jié)構(gòu) dict
不在浮沙筑高臺
Redis 集群(上)
Redis 監(jiān)視器
源碼閱讀工具
Redis 日志和斷言
內(nèi)存數(shù)據(jù)管理
Redis 數(shù)據(jù)結(jié)構(gòu)綜述
源碼日志
Web 服務(wù)器存儲 session
消息中間件
Redis 與 Lua 腳本
什么樣的源代碼適合閱讀
Redis 數(shù)據(jù)結(jié)構(gòu) sds
Memcached slab 分配策略
訂閱發(fā)布機制
Redis 是如何提供服務(wù)的
Redis 事務(wù)機制
Redis 集群(下)
主從復(fù)制
Redis 應(yīng)用
RDB 持久化策略
Redis 數(shù)據(jù)遷移
Redis 事件驅(qū)動詳解
初探 Redis
Redis 與 Memcache
AOF 持久化策略
Redis 數(shù)據(jù)結(jié)構(gòu) redisOb
作者簡介
Redis 數(shù)據(jù)結(jié)構(gòu) ziplist
Redis 數(shù)據(jù)結(jié)構(gòu) skiplist
Redis 哨兵機制

Redis 數(shù)據(jù)淘汰機制

概述

在 Redis 中,允許用戶設(shè)置最大使用內(nèi)存大小 server.maxmemory,在內(nèi)存限定的情況下是很有用的。譬如,在一臺 8G 機子上部署了 4 個 Redis 服務(wù)點,每一個服務(wù)點分配 1G 的內(nèi)存大小,減少內(nèi)存緊張的情況,由此獲取更為穩(wěn)健的服務(wù)。Redis 內(nèi)存數(shù)據(jù)集大小上升到一定大小的時候,就會施行數(shù)據(jù)淘汰策略。Redis 提供 6 種數(shù)據(jù)淘汰策略:

  1. volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用 的數(shù)據(jù)淘汰
  2. volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù) 據(jù)淘汰
  3. volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù) 淘汰
  4. allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰
  5. allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
  6. no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)

Redis 確定驅(qū)逐某個鍵值對后,會刪除這個數(shù)據(jù),并將這個數(shù)據(jù)變更消息發(fā)布到本地(AOF 持久化)和從機(主從連接)。

LRU 數(shù)據(jù)淘汰機制

在服務(wù)器配置中保存了 lru 計數(shù)器 server.lrulock,會定時(Redis 定時程序serverCorn())更新,server.lrulock 的值是根據(jù) server.unixtime 計算出來的。

// redisServer 保存了lru 計數(shù)器
struct redisServer {
...
unsigned lruclock:22; /* Clock incrementing every minute, for LRU */
...
};

另外,從 struct redisObject 中可以發(fā)現(xiàn),每一個 Redis 對象都會設(shè)置相應(yīng)的 lru,即最近訪問的時間??梢韵胂蟮氖?,每一次訪問數(shù)據(jù)的時候,會更新 redisObject.lru。

LRU 數(shù)據(jù)淘汰機制是這樣的:在數(shù)據(jù)集中隨機挑選幾個鍵值對,取出其中 lru 最大的鍵值對淘汰。所以,你會發(fā)現(xiàn),Redis 并不是保證取得所有數(shù)據(jù)集中最近最少使用(LRU)的鍵值對,而只是隨機挑選的幾個鍵值對中的。

// 每一個redis 對象都保存了lru
#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
typedef struct redisObject {
    // 剛剛好32 bits
    // 對象的類型,字符串/列表/集合/哈希表
    unsigned type:4;
    // 未使用的兩個位
    unsigned notused:2; /* Not used */
    // 編碼的方式,redis 為了節(jié)省空間,提供多種方式來保存一個數(shù)據(jù)
    // 譬如:“123456789” 會被存儲為整數(shù)123456789
    unsigned encoding:4;
    unsigned lru:22; /* lru time (relative to server.lruclock) */
    // 引用數(shù)
    int refcount;
    // 數(shù)據(jù)指針
    void *ptr;
} robj;

// redis 定時執(zhí)行程序。聯(lián)想:linux cron
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ......
    /* We have just 22 bits per object for LRU information.
    * So we use an (eventually wrapping) LRU clock with 10 seconds resolution.
    * 2^22 bits with 10 seconds resolution is more or less 1.5 years.
    **
    Note that even if this will wrap after 1.5 years it's not a problem,
    * everything will still work but just some object will appear younger
    * to Redis. But for this to happen a given object should never be touched
    * for 1.5 years.
    **
    Note that you can change the resolution altering the
    * REDIS_LRU_CLOCK_RESOLUTION define.
    */
    updateLRUClock();
    ......
}
    // 更新服務(wù)器的lru 計數(shù)器
void updateLRUClock(void) {
    server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) &
    REDIS_LRU_CLOCK_MAX;
}

TTL 數(shù)據(jù)淘汰機制

Redis 數(shù)據(jù)集數(shù)據(jù)結(jié)構(gòu)中保存了鍵值對過期時間的表,即 redisDb.expires,在使用 SET 命令的時候,就有一個鍵值對超時時間的選項。和 LRU 數(shù)據(jù)淘汰機制類似,TTL 數(shù)據(jù)淘汰機制是這樣的:從過期時間 redisDB.expires 表中隨機挑選幾個鍵值對,取出其中 ttl 最大的鍵值對淘汰。同樣你會發(fā)現(xiàn),Redis 并不是保證取得所有過期時間的表中最快過期的鍵值對,而只是隨機挑選的幾個鍵值對中的。

無論是什么機制,都是從所有的鍵值對中挑選合適的淘汰。

在哪里開始淘汰數(shù)據(jù)

Redis 每服務(wù)客戶端執(zhí)行一個命令的時候,會檢測使用的內(nèi)存是否超額。如果超額,即進行數(shù)據(jù)淘汰。

// 執(zhí)行命令
int processCommand(redisClient *c) {
        ......
        // 內(nèi)存超額
        /* Handle the maxmemory directive.
        **
        First we try to free some memory if possible (if there are volatile
        * keys in the dataset). If there are not the only thing we can do
        * is returning an error. */
        if (server.maxmemory) {
                int retval = freeMemoryIfNeeded();
        if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {
                flagTransaction(c);
                addReply(c, shared.oomerr);
                return REDIS_OK;
        }
    }
    ......
}

這是我們之前講述過的命令處理函數(shù)。在處理命令處理函數(shù)的過程,會涉及到內(nèi)存使用量的檢測,如果檢測到內(nèi)存使用超額,會觸發(fā)數(shù)據(jù)淘汰機制。我們來看看淘汰機制觸發(fā)的函數(shù) freeMemoryIfNeeded() 里面發(fā)生了什么。

// 如果需要,是否一些內(nèi)存
int freeMemoryIfNeeded(void) {
    size_t mem_used, mem_tofree, mem_freed;
    int slaves = listLength(server.slaves);
    // redis 從機回復(fù)空間和AOF 內(nèi)存大小不計算入redis 內(nèi)存大小
    // 關(guān)于已使用內(nèi)存大小是如何統(tǒng)計的,我們會其他章節(jié)講解,這里先忽略這個細節(jié)
    /* Remove the size of slaves output buffers and AOF buffer from the
    * count of used memory. */
    mem_used = zmalloc_used_memory();
    // 從機回復(fù)空間大小
    if (slaves) {
        listIter li;
        listNode *ln;
        listRewind(server.slaves,&li);
    while((ln = listNext(&li))) {
        redisClient *slave = listNodeValue(ln);
    unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave);
    if (obuf_bytes > mem_used)
        mem_used = 0;
    else
        mem_used -= obuf_bytes;
    }
}
    // server.aof_buf && server.aof_rewrite_buf_blocks
    if (server.aof_state != REDIS_AOF_OFF) {
        mem_used -= sdslen(server.aof_buf);
        mem_used -= aofRewriteBufferSize();
    }
    // 內(nèi)存是否超過設(shè)置大小
    /* Check if we are over the memory limit. */
    if (mem_used <= server.maxmemory) return REDIS_OK;
    // redis 中可以設(shè)置內(nèi)存超額策略
    if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)
        return REDIS_ERR; /* We need to free memory, but policy forbids. */
    /* Compute how much memory we need to free. */
        mem_tofree = mem_used - server.maxmemory;
        mem_freed = 0;
    while (mem_freed < mem_tofree) {
        int j, k, keys_freed = 0;
        // 遍歷所有數(shù)據(jù)集
        for (j = 0; j < server.dbnum; j++) {
            long bestval = 0; /* just to prevent warning */
            sds bestkey = NULL;
            struct dictEntry *de;
            redisDb *db = server.db+j;
            dict *dict;
        // 不同的策略,選擇的數(shù)據(jù)集不一樣
        if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
            server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)
        {
            dict = server.db[j].dict;
        } else {
            dict = server.db[j].expires;
        }
        // 數(shù)據(jù)集為空,繼續(xù)下一個數(shù)據(jù)集
        if (dictSize(dict) == 0) continue;
        // 隨機淘汰隨機策略:隨機挑選
        /* volatile-random and allkeys-random policy */
        if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||
            server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)
        {
            de = dictGetRandomKey(dict);
            bestkey = dictGetKey(de);
        }
    // LRU 策略:挑選最近最少使用的數(shù)據(jù)
    /* volatile-lru and allkeys-lru policy */
        else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
            server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
        {
        // server.maxmemory_samples 為隨機挑選鍵值對次數(shù)
        // 隨機挑選server.maxmemory_samples 個鍵值對,驅(qū)逐最近最少使用的數(shù)據(jù)
        for (k = 0; k < server.maxmemory_samples; k++) {
            sds thiskey;
            long thisval;
            robj *o;
        // 隨機挑選鍵值對
            de = dictGetRandomKey(dict);
        // 獲取鍵
            thiskey = dictGetKey(de);
        /* When policy is volatile-lru we need an additional lookup
        * to locate the real key, as dict is set to db->expires. */
        if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
            de = dictFind(db->dict, thiskey);
            o = dictGetVal(de);
            // 計算數(shù)據(jù)的空閑時間
            thisval = estimateObjectIdleTime(o);
            // 當前鍵值空閑時間更長,則記錄
            /* Higher idle time is better candidate for deletion */
        if (bestkey == NULL || thisval > bestval) {
            bestkey = thiskey;
            bestval = thisval;
            }
        }
    }
    // TTL 策略:挑選將要過期的數(shù)據(jù)
    /* volatile-ttl */
    else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {
    // server.maxmemory_samples 為隨機挑選鍵值對次數(shù)
    // 隨機挑選server.maxmemory_samples 個鍵值對,驅(qū)逐最快要過期的數(shù)據(jù)
    for (k = 0; k < server.maxmemory_samples; k++) {
    sds thiskey;
    long thisval;
    de = dictGetRandomKey(dict);
    thiskey = dictGetKey(de);
    thisval = (long) dictGetVal(de);
    /* Expire sooner (minor expire unix timestamp) is better
    * candidate for deletion */
    if (bestkey == NULL || thisval < bestval) {
        bestkey = thiskey;
        bestval = thisval;
        }
    }
}
    // 刪除選定的鍵值對
    /* Finally remove the selected key. */
    if (bestkey) {
        long long delta;
    robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
    // 發(fā)布數(shù)據(jù)更新消息,主要是AOF 持久化和從機
    propagateExpire(db,keyobj);
    // 注意, propagateExpire() 可能會導(dǎo)致內(nèi)存的分配,
    // propagateExpire() 提前執(zhí)行就是因為redis 只計算
    // dbDelete() 釋放的內(nèi)存大小。倘若同時計算dbDelete()
    // 釋放的內(nèi)存和propagateExpire() 分配空間的大小,與此
    // 同時假設(shè)分配空間大于釋放空間,就有可能永遠退不出這個循環(huán)。
    // 下面的代碼會同時計算dbDelete() 釋放的內(nèi)存和propagateExpire() 分配空間的大小// propagateExpire(db,keyobj);
    // delta = (long long) zmalloc_used_memory();
    // dbDelete(db,keyobj);
    // delta -= (long long) zmalloc_used_memory();
    // mem_freed += delta;
    /////////////////////////////////////////
/* We compute the amount of memory freed by dbDelete() alone.
* It is possible that actually the memory needed to propagate
* the DEL in AOF and replication link is greater than the one
* we are freeing removing the key, but we can't account for
* that otherwise we would never exit the loop.
**
AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space. */
// 只計算dbDelete() 釋放內(nèi)存的大小
    delta = (long long) zmalloc_used_memory();
    dbDelete(db,keyobj);
    delta -= (long long) zmalloc_used_memory();
    mem_freed += delta;
    server.stat_evictedkeys++;
    // 將數(shù)據(jù)的刪除通知所有的訂閱客戶端
    notifyKeyspaceEvent(REDIS_NOTIFY_EVICTED, "evicted",
    keyobj, db->id);
    decrRefCount(keyobj);
    keys_freed++;
    // 將從機回復(fù)空間中的數(shù)據(jù)及時發(fā)送給從機
/* When the memory to free starts to be big enough, we may
* start spending so much time here that is impossible to
* deliver data to the slaves fast enough, so we force the
* transmission here inside the loop. */
    if (slaves) flushSlavesOutputBuffers();
    }
}
    // 未能釋放空間,且此時redis 使用的內(nèi)存大小依舊超額,失敗返回
    if (!keys_freed) return REDIS_ERR; /* nothing to free... */
    }
    return REDIS_OK;
}