中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

redis緩存中怎么實施數據淘汰策略

發布時間:2020-06-23 09:54:28 來源:億速云 閱讀:221 作者:Leah 欄目:關系型數據庫

這篇文章將為大家詳細講解有關redis緩存中怎么實施數據淘汰策略,文章內容質量較高,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

在 redis 中,允許用戶設置最大使用內存大小通過配置redis.conf中的maxmemory這個值來開啟內存淘汰功能,在內存限定的情況下是很有用的。

設置最大內存大小可以保證redis對外提供穩健服務。

redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。redis 提供 6種數據淘汰策略通過maxmemory-policy設置策略:

volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰

volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰

volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰

allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰

allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

no-enviction(驅逐):禁止驅逐數據

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

LRU 數據淘汰機制

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

另外,從 struct redisObject 中可以發現,每一個 redis 對象都會設置相應的 lru。可以想象的是,每一次訪問數據的時候,會更新 redisObject.lru。

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

// redisServer 保存了 lru 計數器
struct redisServer {
...
unsigned lruclock:22; /* Clock incrementing every minute, for 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 為了節省空間,提供多種方式來保存一個數據
// 譬如:“123456789” 會被存儲為整數 123456789
unsigned encoding:4;
unsigned lru:22; /* lru time (relative to server.lruclock) */
// 引用數
int refcount;
// 數據指針
void *ptr;

} robj;
// redis 定時執行程序。聯想: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();
......
}
// 更新服務器的 lru 計數器
void updateLRUClock(void) {
server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) &
REDIS_LRU_CLOCK_MAX;
}

TTL 數據淘汰機制

redis 數據集數據結構中保存了鍵值對過期時間的表,即 redisDb.expires。和 LRU 數據淘汰機制類似,TTL 數據淘汰機制是這樣的:從過期時間的表中隨機挑選幾個鍵值對,取出其中 ttl 最大的鍵值對淘汰。

同樣你會發現,redis 并不是保證取得所有過期時間的表中最快過期的鍵值對,而只是隨機挑選的幾個鍵值對中的。

總結

redis 每服務客戶端執行一個命令的時候,會檢測使用的內存是否超額。如果超額,即進行數據淘汰。

// 執行命令
int processCommand(redisClient *c) {
......
// 內存超額
/* 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;
}
}
......
}
// 如果需要,是否一些內存
int freeMemoryIfNeeded(void) {
size_t mem_used, mem_tofree, mem_freed;
int slaves = listLength(server.slaves);
// redis 從機回復空間和 AOF 內存大小不計算入 redis 內存大小
/* Remove the size of slaves output buffers and AOF buffer from the
* count of used memory. */
mem_used = zmalloc_used_memory();
// 從機回復空間大小
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();
}
// 內存是否超過設置大小
/* Check if we are over the memory limit. */
if (mem_used <= server.maxmemory) return REDIS_OK;
// redis 中可以設置內存超額策略
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;
// 遍歷所有數據集
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;
// 不同的策略,選擇的數據集不一樣
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;
}
// 數據集為空,繼續下一個數據集
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 策略:挑選最近最少使用的數據
/* 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 為隨機挑選鍵值對次數
// 隨機挑選 server.maxmemory_samples個鍵值對,驅逐最近最少使用的數據
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); 
// 計算數據的空閑時間
thisval = estimateObjectIdleTime(o);
// 當前鍵值空閑時間更長,則記錄
/* Higher idle time is better candidate for deletion */
if (bestkey == NULL || thisval > bestval) {
bestkey = thiskey;
bestval = thisval;
}
}
} 
// TTL 策略:挑選將要過期的數據
/* volatile-ttl */
else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {
// server.maxmemory_samples 為隨機挑選鍵值對次數
// 隨機挑選 server.maxmemory_samples個鍵值對,驅逐最快要過期的數據
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));
// 發布數據更新消息,主要是 AOF 持久化和從機
propagateExpire(db,keyobj);
// 注意, propagateExpire() 可能會導致內存的分配, propagateExpire()
提前執行就是因為 redis 只計算 dbDelete() 釋放的內存大小。倘若同時計算 dbDelete() 釋放的內存
和 propagateExpire() 分配空間的大小,與此同時假設分配空間大于釋放空間,就有可能永遠退不出這個循環。
// 下面的代碼會同時計算 dbDelete() 釋放的內存和 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() 釋放內存的大小
delta = (long long) zmalloc_used_memory();
dbDelete(db,keyobj);
delta -= (long long) zmalloc_used_memory();
mem_freed += delta;
server.stat_evictedkeys++;
// 將數據的刪除通知所有的訂閱客戶端
notifyKeyspaceEvent(REDIS_NOTIFY_EVICTED, "evicted",
keyobj, db->id);
decrRefCount(keyobj);
keys_freed++; 
// 將從機回復空間中的數據及時發送給從機
/* 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 使用的內存大小依舊超額,失敗返回
if (!keys_freed) return REDIS_ERR; /* nothing to free... */
}
return REDIS_OK;
}

適用場景

下面看看幾種策略的適用場景:

1、allkeys-lru: 如果我們的應用對緩存的訪問符合冪律分布(也就是存在相對熱點數據),或者我們不太清楚我們應用的緩存訪問分布狀況,我們可以選擇allkeys-lru策略。

2、allkeys-random: 如果我們的應用對于緩存key的訪問概率相等,則可以使用這個策略。

3、volatile-ttl: 這種策略使得我們可以向Redis提示哪些key更適合被eviction。

另外,volatile-lru策略和volatile-random策略適合我們將一個Redis實例既應用于緩存和又應用于持久化存儲的時候,然而我們也可以通過使用兩個Redis實例來達到相同的效果,值得一提的是將key設置過期時間實際上會消耗更多的內存,因此我們建議使用allkeys-lru策略從而更有效率的使用內存。

以上就是redis緩存中實施數據淘汰策略的方法,看完之后是否有所收獲呢?如果想了解更多相關內容,歡迎關注億速云行業資訊,感謝各位的閱讀。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

伊川县| 汶上县| 清远市| 丰宁| 汉阴县| 娱乐| 博野县| 东城区| 高雄市| 五指山市| 海伦市| 四子王旗| 石河子市| 田东县| 凌海市| 英吉沙县| 江陵县| 高尔夫| 津市市| 习水县| 辽宁省| 水城县| 正宁县| 嘉兴市| 东明县| 平南县| 天等县| 金堂县| 织金县| 当阳市| 应城市| 延津县| 定陶县| 苏州市| 桦甸市| 新闻| 泾阳县| 谢通门县| 陆良县| 确山县| 象山县|