【Redis扩展篇(一)】过期策略
Redis過期策略
0. 前言
Redis所有的數據結構都可以設置過期時間,時間一到就會被自動刪除。但是會不會因為統一時間太多的key過期,導致Redis執行執行出現卡頓。因為Redis是單線程的,收割的時間也會占用線程的處理時間,因此Redis需要采用一定的key的過期策略
1. 過期key集合
Redis會將每個設置了過期時間的key放入獨立字典中,以后會定時遍歷這個字典來刪除到期的key。除了定時遍歷外,他還會使用惰性策略來刪除過期的key。
所謂的惰性刪除就是在客戶端訪問這個key的時候,Redis對key的過期時間進行檢查,如果過期就立即刪除。
如果說定時刪除就是集中處理,那么惰性刪除就是零散處理
2. 定時掃描策略
Redis默認每秒進行10次過期掃描,過期掃描不會遍歷過期字典中的所有key,而是采用了一種簡單的貪心策略,步驟如下:
同時,為了保證過期掃描不會出現循環過度,導致線程卡死,該策略還加了掃描時間的上限,默認不會超過25ms。
💥假設一個大型的Redis實列中所有的key在同一時間過期了,會出現怎么樣的結果?
??Redis會持續掃描過期字典,直到過期字典中的key變得稀疏,才會停止。這樣就會導致線上讀寫請求出現明顯的卡頓現象。導致卡頓的另外一種原因是內存管理器需要頻繁回收內存頁,這也會產生一定的CPU消耗。
當客戶端請求到來時,服務器如果正好進入過期掃描狀態,客戶端請求將會至少等待25ms后才會進行處理,如果客戶端連接的超時時間設置的非常短(10ms),那么就會出現大量的連接因為超時而關閉,業務端就會出現很多異常。而且,這時還無法從Redis的slowlog中看到慢查詢記錄,因為慢查詢指的是邏輯處理過程慢,不包含等待時間
所以業務開發人員一定要注意過期時間,如果出現大批的key過期,要給過期時間設置一個隨機范圍,而不能全部設置在統一時間
# 在目標過期時間上增加一天的隨機時間 redis.expire_at(key, random.randomint(86400) + expire_ts)在一些活動系統中,因為活動是一期一會,下一期活動舉辦時,前面幾期活動的很多數據都可以丟棄了,所以需要給相關的活動設置一個過期時間,以減少不必要的Redis內存占用。如果不加注意,就可能會將過期時間設置為活動結束時間,導致大量的key同時過期。
從結點的過期策略
從節點不會進行過期掃描,從結點對過期的處理是被動的,主節點在key到期時,會在AOF文件中增加一條del指令,同步到所有從節點,從節點執行這個del指令來刪除過期key,因為指令同步是異步進行的,所以如果主節點過期的key的del沒有及時同步到從節點的化,就會出現主從數據不一致的情況,主結點沒有的數據在從節點中還會存在。
3. LRU
當Redis內存超出物理內存限制時,內存的數據會開始和磁盤產生頻繁的交換,交換會讓Redis的性能急劇下降。在生產環境中,是不允許Redis出現交換行為的,未來限制最大使用內存,Redis提供了配置參數maxmemory來限制內存超出期望的大小。
當實際內存超出了maxmemory時,Redis提供了幾種可選策略來讓用戶自己決定該如何騰出新的空間以繼續提供讀寫服務。
-  noeviction:不會繼續服務寫請求(del請求可以繼續服務),讀請求可以繼續進行,這樣可以保證不會丟失數據,但是會讓線上的業務不能持續進行。這個是默認的淘汰策略 
-  volatile-lru:嘗試淘汰已經設置了過期時間的key,最少使用的key優先被淘汰,沒有設置過期時間的key不會被淘汰,這樣可以保證 需要持久化的數據不會突然丟失 
-  volatile-ttl:和``volatile-lru`幾乎一樣,不過淘汰的策略不是LRU,而是比較key的剩余壽命ttl值,剩余壽命越小的 優先被淘汰 
-  volatile-random:和上面幾乎一樣,不過淘汰的策略是設置了過期key集合中隨機的key. 
-  allkeys-lru:區別于volatile-xxx,這個策略要淘汰的key對象是全體key集合,而不只是設置了過期的key集合,也就意味著一些沒有設置過期時間的key也會被淘汰。 
-  allkeys-random:淘汰的key是全體隨機的key。 
volatile-xxx策略只會針對帶過期時間的Key進行淘汰,allkeys-xxx策略會對所有的key進行淘汰。如果只是拿Redis做緩存,那么應該使用allkeys-xxx策略,客戶端寫緩存時不必攜帶過期時間,如果想同時使用Redis的持久化功能,那就使用volatile-xxx策略,這樣可以保證沒有設置過期時間的Key不會被淘汰,它們是永久的key。
LRU算法
實現LRU算法除了需要key/value字典外,還需要附加一個鏈表,鏈表中的元素按照一定的順序進行排列。當空間滿的時候,會移除鏈表尾部的元素。當字典的某個元素被訪問時,它在鏈表的位置會被移動到表頭,所以鏈表的元素排列順序就是元素最近被訪問的時間順序。
from collections import OrderedDictLRUDict(OrderedDict):def __init__(self, capacity):super(LRUDict, self).__init__()self.capacity = capacityself.items = OrderedDict()def __setitem__(self, key, value):old_value = self.items.get(key)if old_value is not None:self.items.pop(key)self.items[key] = valueelif len(self.items) < self.capacity:self.items[key] = valueelse:self.items.popitem(last=True)self.items[key] = valuedef __getitem__(self, key):value = self.items.get(key)if value is not None:self.items.pop(key)self.items[key] = valuereturn valuedef __repr__(self):return repr(self.items)if __name__ == '__main__':d = LRUDict(10)for i in range(15):d[i] = iprint(d)近似LRU算法
Redis使用的是一種近視LRU算法,它跟LRU算法不太一樣,之所以不適用LRU算法,是因為其需要消耗大量的額外內存,需要對現有的數據結構進行較大的改造。
近似LRU算法在現有數據結構的基礎上使用隨機采樣法來淘汰元素,能達到和LRU算法非常近似的效果。Redis為了實現近似LRU算法,給每個key增加了一個額外的小字段,這個字段的長度是24bit,也就是最后依次被訪問的時間戳。
之前提到處理key過期方式分為集中處理和懶惰處理,LRU淘汰的處理方式只有懶惰處理。當Redis執行寫操作時,發現內存超出maxmemory,就會執行依次LRU淘汰算法,該算法隨機采樣出5(數量可以自己設置)個key,然后淘汰掉最舊的key,如果淘汰后內存還是超出maxmemory,那就繼續采樣淘汰,直到內存低于maxmemory為止。
4. 懶惰刪除
刪除指令del會直接釋放對象的內存,大部分情況下,這個指令非常快,沒有明顯的延遲。不過如果被刪除的key是一個非常大的對象,比如一個包含了上千萬個元素的hash,那么刪除操作就會導致單線程卡頓。
Redis為了解決這個卡頓問題,在4.0版本中引入了unlink指令,它能對刪除操作進行懶處理,丟給后臺線程來異步回收內存
> unlink key OK?這樣刪除key會不會引起線程安全問題?
不會,打個比方,可以將整個Redis內存里面所有有效的數據想象成一棵大樹,當unlink指令發出時,他只是把大樹中的一個樹枝剪斷了,然后扔到異步線程池去處理,在樹枝離開大樹的一瞬間,它舊再也無法被主線程訪問,因此不會引起線程安全問題。
flush
Redis提供了flushdb和flushall指令,用來清空數據庫,這也是非常緩慢的操作,Redis4.0同樣給這兩個指令帶來了異步化,在指令后面增加async參數就可以將整整棵大樹連根拔起,然后丟給后臺線程慢慢處理
> flushall async OK異步隊列
主線程將對象的引用從"大樹"中摘除后,會將這個key的內存回收操作包裝成一個任務,放入異步任務隊列中,后臺線程會從這個異步隊列中獲取任務。任務隊列被主線程和異步線程同時操作,所以必須是一個線程安全的隊列。
不是所有的unlink操作都會延后處理,如果對應的key對象占用的內存很小,Redis會將對應key的內存立即回收。
AOF Sync 也很慢
Redis需要每秒執行1(該數量可以設置)次AOF同步日志到磁盤,確保消息盡量不丟失,需要調用sync(同步)函數,這個操作比較耗時,會導致主線程的效率下降,所以Redis也將這個操作移到異步線程來完成。執行AOF sync操作的線程是一個獨立的異步線程,和前面提到的懶惰刪除線程不是一個線程,同樣它也有一個屬于自己的任務隊列,隊列里存放的AOF sync任務。
更多異步刪除點
除了del指令和flush操作之外,Redis在key的過期,LRU淘汰,rename指令過程中,也會實施回收內存,此外,還有一種特殊的flush操作,其發生于正在進行全量同步的從節點中,在接受完整的rdb文件后,也需要將當前的內存中數據一次清空,以加載整個rdb文件的內容。
Redis4.0為刪除點也帶來了異步刪除機制,打開這些點需要額外的設置選項:
slave-lazy-flush # 從結點接受完rdb文件后的flush操作 lazyfree-lazy-eviction # 內存達到maxmemory時進行淘汰 lazyfree-lazy-expire key # 過期刪除 lazyfree-lazy-server-del rename # 指令刪除destKey總結
以上是生活随笔為你收集整理的【Redis扩展篇(一)】过期策略的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: python之旅六【第六篇】模块
- 下一篇: H5引入Web调试工具、VConsole
