Redis是業界普遍應用的快取元件,研究一個元件框架,最直觀的辦法就是從應用方的角度出發,將每個步驟的考慮一番,從這些步驟入手去研究往往能夠最快的體會到一個元件框架的設計哲學。以Redis為例,每當發起一條請求時,redis是如何管理管理網路請求,收到請求後又是透過什麼樣的資料結構進行組織並操作記憶體,這些資料又是如何dump到磁碟實現持久化,再到多機環境下如何同步和保證一致性。

這裡也給大家推薦一個課,主要是幫助在職Java開發工程師晉升提高,由來自一線大型網際網路公司架構師、技術總監全職授課,內容涵蓋: 高併發、高可用、分散式、高效能、架構設計、原始碼分析。

一丶網路模型

Redis是典型的基於Reactor的事件驅動模型,單程序單執行緒,高效的框架總是類似的。網路模型與spp的非同步模型幾乎一致。

Redis流程上整體分為接受請求處理器、響應處理器和應答處理器三個同步模組,每一個請求都是要經歷這三個部分。

Redis集成了libevent/epoll/kqueue/select等多種事件管理機制,可以根據作業系統版本自由選擇合適的管理機制,其中libevent是最優選擇的機制。

Redis的網路模型有著所有事件驅動模型的優點,高效低耗。但是面對耗時較長的操作的時候,同樣無法處理請求,只能等到事件處理完畢才能響應,之前在業務中也遇到過這樣的場景,刪除redis中全量的key-value,整個操作時間較長,操作期間所有的請求都無法響應。所以瞭解清楚網路模型有助於在業務中揚長避短,減少長耗時的請求,儘可能多一些簡單的短耗時請求發揮非同步模型的最大的威力,事實上在Redis的設計中也多次體現這一點。

二丶資料結構和記憶體管理

1。字串

1。1 結構

Redis的字串是對C語言原始字串的二次封裝,結構如下:

structsdshdr {longlen;longfree;charbuf[];};

可以看出,每當定義一個字串時,除了儲存字元的空間,Redis還分配了額外的空間用於管理屬性欄位。

1。2 記憶體管理方式

動態記憶體管理方式,動態方式最大的好處就是能夠較為充分的利用記憶體空間,減少記憶體碎片化,與此同時帶來的劣勢就是容易引起頻繁的記憶體抖動,通常採用“空間預分配”和“惰性空間釋放”兩種最佳化策略來減少記憶體抖動,redis也不例外。

每次修改字串內容時,首先檢查記憶體空間是否符合要求,否則就擴大2倍或者按M增長;減少字串內容時,記憶體並不會立刻回收,而是按需回收。

關於記憶體管理的最佳化,最基本的出發點就是浪費一點空間還是犧牲一些時間的權衡,像STL、tcmalloc、protobuf3的arena機制等採用的核心思路都是“預分配遲迴收”,Redis也是一樣的。

1。3 二進位制安全

判斷字串結束與否的標識是len欄位,而不是C語言的‘’,因此是二進位制安全的。

放心的將pb序列化後的二進位制字串存入redis。

簡而言之,透過redis的簡單封裝,redis的字串的操作更加方便,效能更友好,並且遮蔽了C語言字串的一些需要使用者關心的問題。

2。字典(雜湊)

字典的底層一定是hash,涉及到hash一定會涉及到hash演算法、衝突的解決方法和hash表擴容和縮容。

2。1 hash演算法

Redis使用的就是常用的Murmurhash2,Murmurhash演算法能夠給出在任意輸入序列下的雜湊分佈性,並且計算速度很快。之前做共享記憶體的Local-Cache的需求時也正是利用了Murmurhash的優勢,解決了原有結構的hash函式雜湊分佈性差的問題。

2。2 hash衝突解決方法

鏈地址法解決hash衝突,通用解決方案沒什麼特殊的。多說一句,如果選用鏈地址解決衝突,那麼勢必要有一個雜湊性非常好的hash函式,否則hash的效能將會大大折扣。Redis選用了Murmurhash,所以可以放心大膽的採用鏈地址方案。

2。3 hash擴容和縮容

維持hash表在一個合理的負載範圍之內,簡稱為rehash過程。

rehash的過程也是一個權衡的過程,在做評估之前首先明確一點,不管中間採用什麼樣的rehash策略,rehash在宏觀上看一定是:分配一個新的記憶體塊,老資料搬到新的記憶體塊上,釋放舊記憶體塊。

老資料何時搬?怎麼搬?就變成了一個需要權衡的問題。

第一部分的網路模型上明確的指出Redis的事件驅動模型特點,不適合玩長耗時操作。如果一個hashtable非常大,需要進行擴容就一次性把老資料copy過去,那就會非常耗時,違背事件驅動的特點。所以Redis依舊採用了一種惰性的方案:

新空間分配完畢後,啟動rehashidx識別符號表明rehash過程的開始;之後所有增刪改查涉及的操作時都會將資料遷移到新空間,直到老空間資料大小為0表明資料已經全部在新空間,將rehashidx禁用,表明rehash結束。

將一次性的集中問題分而治之,在Redis的設計哲學中體現的淋漓盡致,主要是為了避免大耗時操作,影響Redis響應客戶請求。

3。整數集合

變長整數儲存,整數分為16/32/64三個變長尺度,根據存入的資料所屬的型別,進行規劃。

每次插入新元素都有可能導致尺度升級(例如由16位漲到32位),因此插入整數的時間複雜度為O(n)。這裡也是一個權衡,記憶體空間和時間的一個折中,儘可能節省記憶體。

4。跳躍表

Redis的skilplist和普通的skiplist沒什麼不同,都是冗餘資料實現的從粗到細的多層次連結串列,Redis中應用跳錶的地方不多,常見的就是有序集合。

Redis的跳錶和普通skiplist沒有什麼特殊之處。

5。連結串列

Redis的連結串列是雙向非迴圈連結串列,擁有表頭和表尾指標,對於首尾的操作時間複雜度是O(1),查詢時間複雜度O(n),插入時間複雜度O(1)。

Redis的連結串列和普通連結串列沒有什麼特殊之處。

三丶AOF和RDB持久化

AOF持久化日誌,RDB持久化實體資料,AOF優先順序大於RDB。

1。AOF持久化

機制:透過定時事件將aof緩衝區內的資料定時寫到磁碟上。

2。AOF重寫

為了減少AOF大小,Redis提供了AOF重寫功能,這個重寫功能做的工作就是建立一個新AOF檔案代替老的AOF,並且這個新的AOF檔案沒有一條冗餘指令。(例如對list先插入A/B/C,後刪除B/C,再插入D共6條指令,最終狀態為A/D,只需1條指令就可以)

實現原理就是讀現有資料庫的狀態,根據狀態反推指令,跟之前的AOF無關。同樣,為了避免長時間耗時,重寫工作放在子程序進行。

3。RDB持久化

SAVE和BGSAVE兩個命令都是用於生成RDB檔案,區別在於BGSAVE會fork出一個子程序單獨進行,不影響Redis處理正常請求。

定時和定次數後進行持久化操作。

簡而言之,RDB的過程其實是比較簡單的,滿足條件後直接去寫RDB檔案就結束了。

四丶多機和叢集

1。主從伺服器

避免單點是所有服務的通用問題,Redis也不例外。解決單點就要有備機,有備機就要解決固有的資料同步問題。

1。1 sync——原始版主從同步

Redis最初的同步做法是sync指令,透過sync每次都會全量資料,顯然每次都全量複製的設計比較消耗資源。改進思路也是常規邏輯,第一次全量,剩下的增量,這就是現在的psync指令的活。

1。2 psync

部分重同步實現的技術手段是“偏移序號+積壓緩衝區”,具體做法如下:

(1)主從分別維護一個seq,主每次完成一個請求便seq+1,從每同步完後更新自己seq;

(2)從每次打算同步時都是攜帶著自己的seq到主,主將自身的seq與從做差結果與積壓緩衝區大小比較,如果小於積壓緩衝區大小,直接從積壓緩衝區取相應的操作進行部分重同步;

(3)否則說明積壓緩衝區不能夠cover掉主從不一致的資料,進行全量同步。

本質做法用空間換時間,顯然在這裡犧牲部分空間換回高效的部分重同步,收益比很大。

2。Sentinel

本質:多主從伺服器的Redis系統,多臺主從上加了管理監控,以保證系統高可用性。

3。叢集

Redis的官方版叢集尚未在工業界普及起來,下面主要介紹一下叢集的管理體系和運轉體系。

2。1 slot-叢集單位

叢集的資料區由slot組成,每個節點負責的slot是在叢集啟動時分配的。

2。2 客戶請求

客戶請求時如果相應資料hash後不屬於請求節點所管理的slots,會給客戶返回MOVED錯誤,並給出正確的slots。

從這個層面看,redis的叢集還不夠友好,叢集內部的狀態必須由客戶感知。

2。3 容災

主從伺服器,從用於備份主,一旦主故障,從代替主。

好了,以上就給大家分享的架構師整理的Redis設計理念與學習筆記,如果想要了解更多大牛的開發經驗,可以來公開課學習,希望能夠給大家更多學習上的幫助