接下來咱們來一塊兒研究下Redis工程架構相關的問題,這部份內容出現的機率相對大一些,由於並非全部人都會去研究源碼,若是面試一味問源碼那麼可能註定是一場尬聊。git
面試時在不要求候選人對Redis很是熟練的前提下,工程問題將是不二之選。github
經過本文你將瞭解到如下內容:
1.Redis的內存回收詳解
2.Redis的持久化機制面試
1.1 爲何要回收內存?redis
Redis做爲內存型數據庫,若是單純的只進不出遲早就撐爆了,事實上不少把Redis當作主存儲DB用的傢伙們遲早會嚐到這個苦果,固然除非你家廠子確實不差錢,數T級別的內存都毛毛雨,或者數據增加必定程度以後再也不增加的場景,就另當別論了。算法
對於咱們這種把節約成本當作KPI的普通廠子,仍是把Redis當緩存用比較符合家裏的經濟條件,因此這麼看面試官的問題還算是比較貼合實際,比起那些手撕RBTree好一些,若是問題恰好在你知識射程範圍內,先給面試官點個贊再說!數據庫
爲了讓Redis服務安全穩定的運行,讓使用內存保持在必定的閾值內是很是有必要的,所以咱們就須要刪除該刪除的,清理該清理的,把內存留給須要的鍵值對,試想一條大河須要設置幾個警惕水位來確保不決堤不枯竭,Redis也是同樣的,只不過Redis只關心決堤便可,來一張圖:緩存
圖中設定機器內存爲128GB,佔用64GB算是比較安全的水平,若是內存接近80%也就是100GB左右,那麼認爲Redis目前承載能力已經比較大了,具體的比例能夠根據公司和我的的業務經驗來肯定。安全
筆者只是想表達出於安全和穩定的考慮,不要以爲128GB的內存就意味着存儲128GB的數據,都是要打折的。架構
1.2 內存從哪裏回收?dom
Redis佔用的內存是分爲兩部分:存儲鍵值對消耗和自己運行消耗。顯而後者咱們沒法回收,所以只能從鍵值對下手了,鍵值對能夠分爲幾種:帶過時的、不帶過時的、熱點數據、冷數據。對於帶過時的鍵值是須要刪除的,若是刪除了全部的過時鍵值對以後內存仍然不足怎麼辦?那隻能把部分數據給踢掉了。
人生無處不取捨,這個讓筆者腦海浮現了《泰坦尼克》,郵輪撞到了冰山頃刻間海水涌入,面臨數量不足的救生艇,人們作出了抉擇:讓女士和孩童先走,紳士們選擇留下,海上逃生場景如圖:
要實施對鍵值對的刪除咱們須要明白以下幾點:
老規矩來到github看下源碼,src/server.h中給的redisDb結構體給出了答案:
typedef struct redisDb { dict *dict; /* The keyspace for this DB */ dict *expires; /* Timeout of keys with a timeout set */ dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ int id; /* Database ID */ long long avg_ttl; /* Average TTL, just for stats */ unsigned long expires_cursor; /* Cursor of the active expire cycle. */ list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */ } redisDb;
Redis本質上就是一個大的key-value,key就是字符串,value有是幾種對象:字符串、列表、有序列表、集合、哈希等,這些key-value都是存儲在redisDb的dict中的,來看下黃健宏畫的一張很是讚的圖:
看到這裏,對於刪除機制又清晰了一步,咱們只要把redisDb中dict中的目標key-value刪掉就行,不過貌似沒有這麼簡單,Redis對於過時鍵值對確定有本身的組織規則,讓咱們繼續研究吧!
redisDb的expires成員的類型也是dict,和鍵值對是同樣的,本質上expires是dict的子集,expires保存的是全部帶過時的鍵值對,稱之爲過時字典吧,它纔是咱們研究的重點。
對於鍵,咱們能夠設置絕對和相對過時時間、以及查看剩餘時間:
上述三組命令在設計緩存時用處比較大,有心的讀者能夠留意。
過時字典expires和鍵值對空間dict存儲的內容並不徹底同樣,過時字典expires的key是指向Redis對應對象的指針,其value是long long型的unix時間戳,前面的EXPIRE和PEXPIRE相對時長最終也會轉換爲時間戳,來看下過時字典expires的結構,筆者畫了個圖:
判斷鍵是否過時可刪除,須要先查過時字典是否存在該值,若是存在則進一步判斷過時時間戳和當前時間戳的相對大小,作出刪除判斷,簡單的流程如圖:
通過前面的幾個環節,咱們知道了Redis的兩種存儲位置:鍵空間和過時字典,以及過時字典expires的結構、判斷是否過時的方法,那麼該如何實施刪除呢?
先拋開Redis來想一下可能的幾種刪除策略:
在上述的三種策略中定時刪除和按期刪除屬於不一樣時間粒度的主動刪除,惰性刪除屬於被動刪除。
三種策略都有各自的優缺點:
定時刪除對內存使用率有優點,可是對CPU不友好,惰性刪除對內存不友好,若是某些鍵值對一直不被使用,那麼會形成必定量的內存浪費,按期刪除是定時刪除和惰性刪除的折中。
Reids採用的是惰性刪除和定時刪除的結合,通常來講能夠藉助最小堆來實現定時器,不過Redis的設計考慮到時間事件的有限種類和數量,使用了無序鏈表存儲時間事件,這樣若是在此基礎上實現定時刪除,就意味着O(N)遍歷獲取最近須要刪除的數據。
可是我以爲antirez若是非要使用定時刪除,那麼他確定不會使用原來的無序鏈表機制,因此我的認爲已存在的無序鏈表不能做爲Redis不使用定時刪除的根本理由,冒昧猜想惟一可能的是antirez以爲沒有必要使用定時刪除。
按期刪除聽着很簡單,可是如何控制執行的頻率和時長呢?
試想一下若是執行頻率太少就退化爲惰性刪除了,若是執行時間太長又和定時刪除相似了,想一想還確實是個難題!而且執行按期刪除的時機也須要考慮,因此咱們繼續來看看Redis是如何實現按期刪除的吧!筆者在src/expire.c文件中找到了activeExpireCycle函數,按期刪除就是由此函數實現的,在代碼中antirez作了比較詳盡的註釋,不過都是英文的,試着讀了一下模模糊糊弄個大概,因此學習英文並閱讀外文資料是很重要的學習途徑。
因爲筆者對Redis源碼瞭解很少,只能作個模糊版本的解讀,因此不免有問題,仍是建議有條件的讀者自行前往源碼區閱讀,拋磚引玉看下筆者的模糊版本:
主體意思:按期刪除是個自適應的閉環而且機率化的抽樣掃描過程,過程當中都有執行時間和cpu時間的限制,若是觸發閾值就中止,能夠說是儘可能在不影響對客戶端的響應下潤物細無聲地進行的。
1.3.5 DEL刪除鍵值對
在Redis4.0以前執行del操做時若是key-value很大,那麼可能致使阻塞,在新版本中引入了BIO線程以及一些新的命令,實現了del的延時懶刪除,最後會有BIO線程來實現內存的清理回收。
爲了保證Redis的安全穩定運行,設置了一個max-memory的閾值,那麼當內存用量到達閾值,新寫入的鍵值對沒法寫入,此時就須要內存淘汰機制,在Redis的配置中有幾種淘汰策略能夠選擇,詳細以下:
後三種策略都是針對過時字典的處理,可是在過時字典爲空時會noeviction同樣返回寫入失敗,毫無策略地隨機刪除也不太可取,因此通常選擇第二種allkeys-lru基於LRU策略進行淘汰。
我的認爲antirez一貫都是工程化思惟,善於使用機率化設計來作近似實現,LRU算法也不例外,Redis中實現了近似LRU算法,而且通過幾個版本的迭代效果已經比較接近理論LRU算法的效果了,這個也是個不錯的內容,因爲篇幅限制,本文計劃後續單獨講LRU算法時再進行詳細討論。
過時健刪除策略強調的是對過時健的操做,若是有健過時而內存足夠,Redis不會使用內存淘汰機制來騰退空間,這時會優先使用過時健刪除策略刪除過時健。
內存淘汰機制強調的是對內存數據的淘汰操做,當內存不足時,即便有的健沒有到達過時時間或者根本沒有設置過時也要根據必定的策略來刪除一部分,騰退空間保證新數據的寫入。
Q2:講講你對Redis持久化機制的理解。
我的認爲Redis持久化既是數據庫自己的亮點,也是面試的熱點,主要考察的方向包括:RDB機制原理、AOF機制原理、各自的優缺點、工程上的對於RDB和AOF的取捨、新版本Redis混合持久化策略等,如能把握要點,持久化問題就過關了。