Redis入门指南(第2版) Redis设计思路学习与总结
https://www.qcloud.com/community/article/222
宋增寬,騰訊工程師,16年畢業(yè)加入騰訊,從事海量服務(wù)后臺設(shè)計與研發(fā)工作,現(xiàn)在負責QQ群后臺等項目,喜歡研究技術(shù),并思考技術(shù)演變,專注于高并發(fā)業(yè)務(wù)架構(gòu)的設(shè)計與性能優(yōu)化。
下半年利用空余時間研究和分析了部分Redis源碼,本文從網(wǎng)絡(luò)模型、數(shù)據(jù)結(jié)構(gòu)和內(nèi)存管理、持久化和多機協(xié)作四個角度對redis的設(shè)計思路進行了分析,若有不正確之處,希望各路大神指出。
Redis是業(yè)界普遍應(yīng)用的緩存組件,研究一個組件框架,最直觀的辦法就是從應(yīng)用方的角度出發(fā),將每個步驟的考慮一番,從這些步驟入手去研究往往能夠最快的體會到一個組件框架的設(shè)計哲學。以Redis為例,每當發(fā)起一條請求時,redis是如何管理管理網(wǎng)絡(luò)請求,收到請求后又是通過什么樣的數(shù)據(jù)結(jié)構(gòu)進行組織并操作內(nèi)存,這些數(shù)據(jù)又是如何dump到磁盤實現(xiàn)持久化,再到多機環(huán)境下如何同步和保證一致性……本文就是從網(wǎng)絡(luò)模型、數(shù)據(jù)結(jié)構(gòu)設(shè)計與內(nèi)存管理、持久化方法和多機四個角度簡要描述了redis的設(shè)計和自己的一點體會。
一.網(wǎng)絡(luò)模型
Redis是典型的基于Reactor的事件驅(qū)動模型,單進程單線程,高效的框架總是類似的。網(wǎng)絡(luò)模型與spp的異步模型幾乎一致。
Redis流程上整體分為接受請求處理器、響應(yīng)處理器和應(yīng)答處理器三個同步模塊,每一個請求都是要經(jīng)歷這三個部分。
Redis集成了libevent/epoll/kqueue/select等多種事件管理機制,可以根據(jù)操作系統(tǒng)版本自由選擇合適的管理機制,其中l(wèi)ibevent是最優(yōu)選擇的機制。
{
安裝redis最好源碼編譯,而不是yum安裝或rpm安裝
因為Redis集成了libevent/epoll/kqueue/select等多種事件管理機制,編譯的時候可以根據(jù)操作系統(tǒng)版本自由選擇合適的管理機制,其中l(wèi)ibevent是最優(yōu)選擇的機制。
}
Redis的網(wǎng)絡(luò)模型有著所有事件驅(qū)動模型的優(yōu)點,高效低耗。但是面對耗時較長的操作的時候,同樣無法處理請求,只能等到事件處理完畢才能響應(yīng),之前在業(yè)務(wù)中也遇到過這樣的場景,刪除redis中全量的key-value,整個操作時間較長,操作期間所有的請求都無法響應(yīng)。所以了解清楚網(wǎng)絡(luò)模型有助于在業(yè)務(wù)中揚長避短,減少長耗時的請求,盡可能多一些簡單的短耗時請求發(fā)揮異步模型的最大的威力,事實上在Redis的設(shè)計中也多次體現(xiàn)這一點。
二.數(shù)據(jù)結(jié)構(gòu)和內(nèi)存管理
1.字符串
1.1 結(jié)構(gòu)
Redis的字符串是對C語言原始字符串的二次封裝,結(jié)構(gòu)如下:
struct sdshdr {
??? long len;
??? long free;
??? char buf[];
};
可以看出,每當定義一個字符串時,除了保存字符的空間,Redis還分配了額外的空間用于管理屬性字段。
1.2 內(nèi)存管理方式
動態(tài)內(nèi)存管理方式,動態(tài)方式最大的好處就是能夠較為充分的利用內(nèi)存空間,減少內(nèi)存碎片化,與此同時帶來的劣勢就是容易引起頻繁的內(nèi)存抖動,通常采用“空間預(yù)分配”和“惰性空間釋放”兩種優(yōu)化策略來減少內(nèi)存抖動,redis也不例外。
每次修改字符串內(nèi)容時,首先檢查內(nèi)存空間是否符合要求,否則就擴大2倍或者按M增長;減少字符串內(nèi)容時,內(nèi)存并不會立刻回收,而是按需回收。
關(guān)于內(nèi)存管理的優(yōu)化,最基本的出發(fā)點就是浪費一點空間還是犧牲一些時間的權(quán)衡,像STL、tcmalloc、protobuf3的arena機制等采用的核心思路都是“預(yù)分配遲回收”,Redis也是一樣的。
1.3 二進制安全
判斷字符串結(jié)束與否的標識是len字段,而不是C語言的'\0',因此是二進制安全的。
放心的將pb序列化后的二進制字符串存入redis。
簡而言之,通過redis的簡單封裝,redis的字符串的操作更加方便,性能更友好,并且屏蔽了C語言字符串的一些需要用戶關(guān)心的問題。
2.字典(哈希)
字典的底層一定是hash,涉及到hash一定會涉及到hash算法、沖突的解決方法和hash表擴容和縮容。
2.1 hash算法
Redis使用的就是常用的Murmurhash2,Murmurhash算法能夠給出在任意輸入序列下的散列分布性,并且計算速度很快。之前做共享內(nèi)存的Local-Cache的需求時也正是利用了Murmurhash的優(yōu)勢,解決了原有結(jié)構(gòu)的hash函數(shù)散列分布性差的問題。
2.2 hash沖突解決方法
鏈地址法解決hash沖突,通用解決方案沒什么特殊的。多說一句,如果選用鏈地址解決沖突,那么勢必要有一個散列性非常好的hash函數(shù),否則hash的性能將會大大折扣。Redis選用了Murmurhash,所以可以放心大膽的采用鏈地址方案。
2.3 hash擴容和縮容
維持hash表在一個合理的負載范圍之內(nèi),簡稱為rehash過程。
rehash的過程也是一個權(quán)衡的過程,在做評估之前首先明確一點,不管中間采用什么樣的rehash策略,rehash在宏觀上看一定是:分配一個新的內(nèi)存塊,老數(shù)據(jù)搬到新的內(nèi)存塊上,釋放舊內(nèi)存塊。
老數(shù)據(jù)何時搬?怎么搬?就變成了一個需要權(quán)衡的問題。
第一部分的網(wǎng)絡(luò)模型上明確的指出Redis的事件驅(qū)動模型特點,不適合玩長耗時操作。如果一個hashtable非常大,需要進行擴容就一次性把老數(shù)據(jù)copy過去,那就會非常耗時,違背事件驅(qū)動的特點。所以Redis依舊采用了一種惰性的方案:
新空間分配完畢后,啟動rehashidx標識符表明rehash過程的開始;之后所有增刪改查涉及的操作時都會將數(shù)據(jù)遷移到新空間,直到老空間數(shù)據(jù)大小為0表明數(shù)據(jù)已經(jīng)全部在新空間,將rehashidx禁用,表明rehash結(jié)束。
將一次性的集中問題分而治之,在Redis的設(shè)計哲學中體現(xiàn)的淋漓盡致,主要是為了避免大耗時操作,影響Redis響應(yīng)客戶請求。
3.整數(shù)集合
變長整數(shù)存儲,整數(shù)分為16/32/64三個變長尺度,根據(jù)存入的數(shù)據(jù)所屬的類型,進行規(guī)劃。
每次插入新元素都有可能導(dǎo)致尺度升級(例如由16位漲到32位),因此插入整數(shù)的時間復(fù)雜度為O(n)。這里也是一個權(quán)衡,內(nèi)存空間和時間的一個折中,盡可能節(jié)省內(nèi)存。
4.跳躍表
Redis的skilplist和普通的skiplist沒什么不同,都是冗余數(shù)據(jù)實現(xiàn)的從粗到細的多層次鏈表,Redis中應(yīng)用跳表的地方不多,常見的就是有序集合。
Redis的跳表和普通skiplist沒有什么特殊之處。
http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html
http://www.cppblog.com/mysileng/archive/2013/04/06/199159.html
http://www.cnblogs.com/huangxincheng/p/4979789.html
Skip List 介紹
Skip List是一種隨機化的數(shù)據(jù)結(jié)構(gòu),基于并聯(lián)的鏈表,其效率可比擬于二叉查找樹(對于大多數(shù)操作需要O(log n)平均時間)。基本上,跳躍列表是對有序的鏈表增加上附加的前進鏈接,增加是以隨機化的方式進行的,所以在列表中的查找可以快速的跳過部分列表(因此得名)。所有操作都以對數(shù)隨機化的時間進行。Skip List可以很好解決有序鏈表查找特定值的困難。
跳出平衡二叉樹的思維
聊一聊作者的其人其事
跳表是由William Pugh發(fā)明。他在 Communications of the ACM June 1990, 33(6) 668-676 發(fā)表了Skip lists: a probabilistic alternative to balanced trees,在該論文中詳細解釋了跳表的數(shù)據(jù)結(jié)構(gòu)和插入刪除操作。
5.鏈表
Redis的鏈表是雙向非循環(huán)鏈表,擁有表頭和表尾指針,對于首尾的操作時間復(fù)雜度是O(1),查找時間復(fù)雜度O(n),插入時間復(fù)雜度O(1)。
Redis的鏈表和普通鏈表沒有什么特殊之處。
三.AOF和RDB持久化
AOF持久化日志,RDB持久化實體數(shù)據(jù),AOF優(yōu)先級大于RDB。
1.AOF持久化
機制:通過定時事件將aof緩沖區(qū)內(nèi)的數(shù)據(jù)定時寫到磁盤上。
2.AOF重寫
為了減少AOF大小,Redis提供了AOF重寫功能,這個重寫功能做的工作就是創(chuàng)建一個新AOF文件代替老的AOF,并且這個新的AOF文件沒有一條冗余指令。(例如對list先插入A/B/C,后刪除B/C,再插入D共6條指令,最終狀態(tài)為A/D,只需1條指令就可以)
實現(xiàn)原理就是讀現(xiàn)有數(shù)據(jù)庫的狀態(tài),根據(jù)狀態(tài)反推指令,跟之前的AOF無關(guān)。同樣,為了避免長時間耗時,重寫工作放在子進程進行。
3.RDB持久化
SAVE和BGSAVE兩個命令都是用于生成RDB文件,區(qū)別在于BGSAVE會fork出一個子進程單獨進行,不影響Redis處理正常請求。
定時和定次數(shù)后進行持久化操作。
簡而言之,RDB的過程其實是比較簡單的,滿足條件后直接去寫RDB文件就結(jié)束了。
四.多機和集群
1.主從服務(wù)器
避免單點是所有服務(wù)的通用問題,Redis也不例外。解決單點就要有備機,有備機就要解決固有的數(shù)據(jù)同步問題。
1.1 sync——原始版主從同步
Redis最初的同步做法是sync指令,通過sync每次都會全量數(shù)據(jù),顯然每次都全量復(fù)制的設(shè)計比較消耗資源。改進思路也是常規(guī)邏輯,第一次全量,剩下的增量,這就是現(xiàn)在的psync指令的活。
1.2 psync
部分重同步實現(xiàn)的技術(shù)手段是“偏移序號+積壓緩沖區(qū)”,具體做法如下:
(1)主從分別維護一個seq,主每次完成一個請求便seq+1,從每同步完后更新自己seq;
(2)從每次打算同步時都是攜帶著自己的seq到主,主將自身的seq與從做差結(jié)果與積壓緩沖區(qū)大小比較,如果小于積壓緩沖區(qū)大小,直接從積壓緩沖區(qū)取相應(yīng)的操作進行部分重同步;
(3)否則說明積壓緩沖區(qū)不能夠cover掉主從不一致的數(shù)據(jù),進行全量同步。
本質(zhì)做法用空間換時間,顯然在這里犧牲部分空間換回高效的部分重同步,收益比很大。
2.Sentinel
本質(zhì):多主從服務(wù)器的Redis系統(tǒng),多臺主從上加了管理監(jiān)控,以保證系統(tǒng)高可用性。
3.集群
Redis的官方版集群尚未在工業(yè)界普及起來,下面主要介紹一下集群的管理體系和運轉(zhuǎn)體系。
2.1 slot-集群單位
集群的數(shù)據(jù)區(qū)由slot組成,每個節(jié)點負責的slot是在集群啟動時分配的。
2.2 客戶請求
客戶請求時如果相應(yīng)數(shù)據(jù)hash后不屬于請求節(jié)點所管理的slots,會給客戶返回MOVED錯誤,并給出正確的slots。
從這個層面看,redis的集群還不夠友好,集群內(nèi)部的狀態(tài)必須由客戶感知。
2.3 容災(zāi)
主從服務(wù)器,從用于備份主,一旦主故障,從代替主。
通過Redis的研究,深刻體會到的一點就是:所有設(shè)計的過程都是權(quán)衡和割舍的過程。同樣放到日常的工作和開發(fā)中也是如此,一句代碼寫的好不好,一個模塊設(shè)計的是否科學,就從速度和內(nèi)存的角度去衡量看是否需要優(yōu)化,并去評估每一種優(yōu)化會收益到什么,同時會損失什么,收益遠大于損失的就是好的優(yōu)化,這樣往往對于開發(fā)和提升更有針對性,更能提高效率。
?
?
?
?
?
?
?
?
f
?
轉(zhuǎn)載于:https://www.cnblogs.com/MYSQLZOUQI/p/6178009.html
總結(jié)
以上是生活随笔為你收集整理的Redis入门指南(第2版) Redis设计思路学习与总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机顶盒怎样配置服务器信息,网络机顶盒桌面
- 下一篇: SQL Server2008附加数据库失