redis深刻研究

 

Redis 設計思路學習與總結

https://cloud.tencent.com/developer/article/1004464前端

Redis 設計思路學習與總結redis

下半年利用空餘時間研究和分析了部分Redis源碼,本文從網絡模型、數據結構和內存管理、持久化和多機協做四個角度對redis的設計思路進行了分析,如有不正確之處,但願各路大神指出。算法

Redis是業界廣泛應用的緩存組件,研究一個組件框架,最直觀的辦法就是從應用方的角度出發,將每一個步驟的考慮一番,從這些步驟入手去研究每每可以最快的體會到一個組件框架的設計哲學。以Redis爲例,每當發起一條請求時,redis是如何管理管理網絡請求,收到請求後又是經過什麼樣的數據結構進行組織並操做內存,這些數據又是如何dump到磁盤實現持久化,再到多機環境下如何同步和保證一致性……本文就是從網絡模型、數據結構設計與內存管理、持久化方法和多機四個角度簡要描述了redis的設計和本身的一點體會。數據庫

一.網絡模型 
Redis是典型的基於Reactor的事件驅動模型,單進程單線程,高效的框架老是相似的。網絡模型與spp的異步模型幾乎一致。後端

Redis流程上總體分爲接受請求處理器、響應處理器和應答處理器三個同步模塊,每個請求都是要經歷這三個部分。緩存

Redis集成了libevent/epoll/kqueue/select等多種事件管理機制,能夠根據操做系統版本自由選擇合適的管理機制,其中libevent是最優選擇的機制。安全

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

二.數據結構和內存管理 
1.字符串 
1.1 結構 
Redis的字符串是對C語言原始字符串的二次封裝,結構以下:markdown

struct sdshdr { 
long len; 
long free; 
char buf[]; 
}; 
能夠看出,每當定義一個字符串時,除了保存字符的空間,Redis還分配了額外的空間用於管理屬性字段。網絡

1.2 內存管理方式 
動態內存管理方式,動態方式最大的好處就是可以較爲充分的利用內存空間,減小內存碎片化,與此同時帶來的劣勢就是容易引發頻繁的內存抖動,一般採用「空間預分配」和「惰性空間釋放」兩種優化策略來減小內存抖動,redis也不例外。

每次修改字符串內容時,首先檢查內存空間是否符合要求,不然就擴大2倍或者按M增加;減小字符串內容時,內存並不會馬上回收,而是按需回收。

關於內存管理的優化,最基本的出發點就是浪費一點空間仍是犧牲一些時間的權衡,像STL、tcmalloc、protobuf3的arena機制等採用的核心思路都是「預分配遲迴收」,Redis也是同樣的。

1.3 二進制安全 
判斷字符串結束與否的標識是len字段,而不是C語言的’\0’,所以是二進制安全的。

放心的將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系統,多臺主從上加了管理監控,以保證系統高可用性。

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

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

2.3 容災 
主從服務器,從用於備份主,一旦主故障,從代替主。

經過Redis的研究,深入體會到的一點就是:全部設計的過程都是權衡和割捨的過程。一樣放到平常的工做和開發中也是如此,一句代碼寫的好很差,一個模塊設計的是否科學,就從速度和內存的角度去衡量看是否須要優化,並去評估每一種優化會收益到什麼,同時會損失什麼,收益遠大於損失的就是好的優化,這樣每每對於開發和提高更有針對性,更能提升效率。

http://www.infoq.com/cn/articles/architecture-practice-01-redis 
Redis 重要特性 
如下特性請重點看管道和事務。

一、管道

Redis管道是指客戶端能夠將多個命令一次性發送到服務器,而後由服務器一次性返回全部結果。管道技術在批量執行命令的時候能夠大大減小網絡傳輸的開銷,提升性能。

二、事務

Redis事務是一組命令的集合。一個事務中的命令要麼都執行,要麼都不執行。若是命令在運行期間出現錯誤,不會自動回滾。

管道與事務的區別:管道主要是網絡上的優化,客戶端緩衝一組命令,一次性發送到服務器端執行,可是並不能保證命令是在同一個事務裏面執行;而事務是原子性的,能夠確保命令執行的時候不會有來自其餘客戶端的命令插入到命令序列中。

三、分佈式鎖

分佈式鎖是控制分佈式系統之間同步訪問共享資源的一種方式。在分佈式系統中,經常須要協調他們的動做,若是不一樣的系統或是同一個系統的不一樣主機之間共享了一個或一組資源,那麼訪問這些資源的時候,每每須要互斥來防止彼此干擾來保證一致性,在這種狀況下,便須要使用到分佈式鎖。

四、地理信息

從Redis 3.2版本開始,新增了地理信息相關的命令,能夠將用戶給定的地理位置信息(經緯度)存儲起來,並對這些信息進行操做。

常見應用問題

緩存穿透處理:什麼是緩存穿透?當根據Redis key在緩存中查詢後,不存在對應Value,就應該會在後端系統如DB中去查找,該Key的併發請求量一旦變大,那麼就會對DB形成很大的壓力。解決辦法有:a.前端風險控制,將惡意穿透狀況排除在外;b.對查詢結果爲空的狀況依然進行緩存,但緩存時間會設置得很短,通常是幾分鐘。 緩存雪崩處理:什麼是緩存雪崩?當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(好比DB)帶來很大壓力。解決辦法有:後端鏈接數限制,錯誤閾值限制,超時處理,緩存失效時間均勻分佈,前端永不失效及後端主動更新。 緩存時長:策略定位複雜,須要多維度的計算。 緩存失效:按時失效,事件失效,後端主動更新。 緩存Key:Hash、規則、前綴+Hash,異常狀況可人工干預。 Lua腳本:服務端批量處理及事務能力,有條件邏輯的可擴展腳本。使用它的好處有:減小網絡開銷、原子操做、可複用。 Limit:可滑動時間窗口,如應用於Session,Memcached需每次傳Key和Value。

相關文章
相關標籤/搜索