redis做爲緩存場景使用,內存耗盡時,忽然出現大量的逐出,在這個逐出的過程當中阻塞正常的讀寫請求,致使 redis 短期不可用;git
redis 中的LRU是如何實現的?github
逐出qps突增很是大的緣由:一次須要逐出釋放太多的空間會致使阻塞;具體的緣由是 mem_tofree 的計算邏輯有問題;
mem_tofree 統計的是:實際已分配的內存總量 - AOF 緩衝區相關的內存;
若是這時候有rehash,會臨時分配一個桶來作rehash,這部份內存未排除,因此在rehash階段,算出來的mem_tofree 就會很大,形成一個時刻須要逐出大量的key,逐出的loop是阻塞的,這個階段會block redis的請求;redis
逐出qps的計算:算法
freeMemoryIfNeeded(...) // 計算出 Redis 目前佔用的內存總數,但有兩個方面的內存不會計算在內: // 1)從服務器的輸出緩衝區的內存 // 2)AOF 緩衝區的內存 // 3)AOF 重寫緩衝區中的內存 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; } } if (server.aof_state != REDIS_AOF_OFF) { mem_used -= sdslen(server.aof_buf); mem_used -= aofRewriteBufferSize(); } // 計算須要釋放多少字節的內存 mem_tofree = mem_used - server.maxmemory; propagateExpire(db,keyobj); // 計算刪除鍵所釋放的內存數量 delta = (long long) zmalloc_used_memory(); dbDelete(db,keyobj); delta -= (long long) zmalloc_used_memory(); mem_freed += delta; // 對淘汰鍵的計數器增一 server.stat_evictedkeys++;
github上 @Rosanta 給出的解決方案:釋放內存的循環邏輯中最多執行必定次數,達到閾值了就再也不逐出,到下個請求來時再釋放一點空間;這個方案的好處是不會 block 整個進程,正常的業務讀寫請求無影響;潛在問題是可能單次寫入的數據比釋放的空間還大,致使總的內存是一直上升,而不是降低;緩存
@antirez 給的方案:一樣是迭代刪除,但會加個標誌,保證在迭代刪除的邏輯下內存是逐漸降低的,而若是是上升的,仍是會block住正常的請求(要控制主總的內存大小);
詳見:
https://github.com/antirez/redis/pull/4583服務器
關於 redis 4.0的逐出算法優化
http://antirez.com/news/109函數