咱們都知道redis是常駐在內存當中的,所以他的效率比MySQL要快不少不少。但又引起了另一個問題,內存從本質上講,它是昂貴的,不能用於大量的長時間的存儲,他是「不安全不穩定的「,而且有可能存在內存泄露,不能與磁盤相比。redis
那麼若是解決這種問題呢?所以咱們使用redis的時候,強制的應該給每一個Key加上過時時間。咱們來看看redis對過時的Key是怎麼處理的。數據庫
第一個問題,redis如何知道他是一個過時鍵呢?又該如何斷定他過時了呢?安全
在數據庫中, 全部鍵的過時時間都被保存在 redisDb
結構的 expires
字典裏:性能
1 typedef struct redisDb { 2 3 // ... 4 5 dict *expires; 6 7 // ... 8 9 } redisDb;
expires
字典的鍵是一個指向 dict
字典(鍵空間)裏某個鍵的指針, 而字典的值則是鍵所指向的數據庫鍵的到期時間, 這個值以 long long
類型表示。spa
下圖展現了一個含有三個鍵的數據庫,其中 number
和 book
兩個鍵帶有過時時間unix
咱們能夠看到number和book是有一個過時時間的,他是long long類型。實則他是一個unix的時間戳,所以判斷他是否過時就十分的簡單了。指針
經過 expires
字典, 能夠用如下步驟檢查某個鍵是否過時:日誌
expires
字典:若是存在,那麼取出鍵的過時時間;能夠用僞代碼來描述這一過程:code
1 def is_expired(key): 2 3 # 取出鍵的過時時間 4 key_expire_time = expires.get(key) 5 6 # 若是過時時間不爲空,而且當前時間戳大於過時時間,那麼鍵已通過期 7 if expire_time is not None and current_timestamp() > key_expire_time: 8 return True 9 10 # 不然,鍵未過時或沒有設置過時時間 11 return False
當咱們知道這個鍵過時了,咱們該如何清除呢?基本上有如下三種策略:xml
定時刪除策略對內存是最友好的: 由於它保證過時鍵會在第一時間被刪除, 過時鍵所消耗的內存會當即被釋放。
這種策略的缺點是, 它對 CPU 時間是最不友好的: 由於刪除操做可能會佔用大量的 CPU 時間 —— 在內存不緊張、可是 CPU 時間很是緊張的時候 (好比說,進行交集計算或排序的時候), 將 CPU 時間花在刪除那些和當前任務無關的過時鍵上, 這種作法毫無疑問會是低效的。
除此以外, 目前 Redis 事件處理器對時間事件的實現方式 —— 無序鏈表, 查找一個時間複雜度爲 O(N) —— 並不適合用來處理大量時間事件。
惰性刪除對 CPU 時間來講是最友好的: 它只會在取出鍵時進行檢查, 這能夠保證刪除操做只會在非作不可的狀況下進行 —— 而且刪除的目標僅限於當前處理的鍵, 這個策略不會在刪除其餘無關的過時鍵上花費任何 CPU 時間。
惰性刪除的缺點是, 它對內存是最不友好的: 若是一個鍵已通過期, 而這個鍵又仍然保留在數據庫中, 那麼 dict
字典和 expires
字典都須要繼續保存這個鍵的信息, 只要這個過時鍵不被刪除, 它佔用的內存就不會被釋放。
在使用惰性刪除策略時, 若是數據庫中有很是多的過時鍵, 但這些過時鍵又正好沒有被訪問的話, 那麼它們就永遠也不會被刪除(除非用戶手動執行), 這對於性能很是依賴於內存大小的 Redis 來講, 確定不是一個好消息。
舉個例子, 對於一些按時間點來更新的數據, 好比日誌(log), 在某個時間點以後, 對它們的訪問就會大大減小, 若是大量的這些過時數據積壓在數據庫裏面, 用戶覺得它們已通過期了(已經被刪除了), 但實際上這些鍵卻沒有真正的被刪除(內存也沒有被釋放), 那結果確定是很是糟糕。
從上面對定時刪除和惰性刪除的討論來看, 這兩種刪除方式在單一使用時都有明顯的缺陷: 定時刪除佔用太多 CPU 時間, 惰性刪除浪費太多內存。
按期刪除是這兩種策略的一種折中:
所以最終redis使用的過時鍵刪除策略是惰性刪除加上按期刪除, 這兩個策略相互配合,能夠很好地在合理利用 CPU 時間和節約內存空間之間取得平衡。
所以redis大體流程以下:獲取key以前,會檢查key是否過時,如過時,直接刪除,返回null。
而且會按期的隨機的檢查大約25%的key是否過時,若是超過必定比例的key被過時。那麼繼續循環,直至低於這個數值。
這個按期的時間,以及數值均可以在conf文件裏面配置。