最近又復(fù)習(xí)了一下redis中比較重要的幾個(gè)知識(shí)點(diǎn),知識(shí)點(diǎn)多且碎,在這里做一個(gè)簡(jiǎn)單的總結(jié),便于以后復(fù)習(xí)。
主流應(yīng)用架構(gòu)
我們都知道多數(shù)情況下redis是作為緩存應(yīng)用來(lái)使用的,下面則顯示出當(dāng)前主流的應(yīng)用架構(gòu)(客戶端、緩存、存儲(chǔ)層).

對(duì)比緩存中間件 Memcache和Redis的區(qū)別
??Memcache: 在代碼層次上比較類似于Hash
- 支持簡(jiǎn)單的數(shù)據(jù)類型
- 不支持?jǐn)?shù)據(jù)持久化存儲(chǔ)
- 不支持主從
- 不支持分片
??Redis
- 數(shù)據(jù)類型豐富
- 支持?jǐn)?shù)據(jù)磁盤持久化存儲(chǔ)(RDB、AOF)
- 支持主從
- 支持分片
我們知道,Redis是內(nèi)存級(jí)數(shù)據(jù)庫(kù),它的QPS(Query Per Second)可以達(dá)到100000+,那它為啥那么快呢?
原因如下:
- 完全基于內(nèi)存,絕大多數(shù)是存粹的內(nèi)存操作
- 數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單,對(duì)數(shù)據(jù)操作簡(jiǎn)單(如利用了Hash的查找為O(1)的特性)
- 采用單線程(在處理網(wǎng)絡(luò)請(qǐng)求時(shí)是單線程),單線程也能處理高并發(fā)的需求,如果想要多核的話可以啟動(dòng)多個(gè)實(shí)例
- 使用多路I/O復(fù)用模型,非阻塞I/O
下面我們便開始聊一聊Redis的多路I/O復(fù)用模型,在聊這個(gè)之前,我們首先應(yīng)該清楚一個(gè)概念:
FD 文件描述符(File Descripto):一個(gè)打開的文件通過(guò)唯一的描述符進(jìn)行引用,該描述符是打開文件元數(shù)據(jù)到文件本身的映射.
I/O模型有:1.傳統(tǒng)的I/O阻塞模型 2.多路I/O復(fù)用模型(可以同時(shí)對(duì)多個(gè)fd的狀態(tài)進(jìn)行監(jiān)控)

Redis采用的I/O多路復(fù)用函數(shù): epoll/kqueue/evport/select
優(yōu)先選擇O(1)的I/O多路復(fù)用函數(shù)作為底層實(shí)現(xiàn),以時(shí)間復(fù)雜度為O(n)的來(lái)保底,基于react設(shè)計(jì)監(jiān)聽(tīng)I(yíng)/O事件(監(jiān)聽(tīng)多個(gè)fd)
簡(jiǎn)單聊一下Redis的數(shù)據(jù)類型
1.String k,v 最基本的類型,二進(jìn)制安全(可存圖片) [incr 可用作網(wǎng)站用戶量統(tǒng)計(jì)]
2.Hash String元素組成的字典,適合存儲(chǔ)對(duì)象。hmset lilei name "lilei" age 26 title "senior"
3.List列表 按照插入順序排序 lpush rpush. 查詢0-10的記錄 orange mylist 0 10
4.Set String元素的無(wú)序集合,不重復(fù)??梢赃M(jìn)行交、差、并等操作。sadd myset 111。smembers myset —>遍歷所有元素
5.Sorted Set 通過(guò)分?jǐn)?shù)為元素大小排序,不重復(fù) zadd myzset 3 abc ; zrangebyscore myset 0 10
還有一些比較高級(jí)的如用于計(jì)數(shù)的HyperLogLog和用于支持存儲(chǔ)地理位置的Geo
此外可以看一下<<Redis的設(shè)計(jì)與實(shí)現(xiàn)>>這本書中對(duì)于Redis底層數(shù)據(jù)類型基礎(chǔ)的介紹,以上的所有對(duì)象都是基于更加底層的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的
如何從海量數(shù)據(jù)里(2000w+)查詢出某一個(gè)固定前綴的key
遇到這種問(wèn)題時(shí),應(yīng)該首先問(wèn)清楚數(shù)據(jù)量的范圍規(guī)模,問(wèn)清楚邊界,再進(jìn)行思考和回答
KEYS pattern : 查找所有符合給定模式pattern 的key, 例如 keys k1 查找所有以k1開頭的key*
使用keys 對(duì)線上業(yè)務(wù)有什么影響?
keys指令會(huì)一次性返回所有匹配的key,鍵的數(shù)量太大會(huì)導(dǎo)致服務(wù)卡頓
為了解決這個(gè)問(wèn)題 引入了SCAN cursor
基于游標(biāo)cursor的迭代器,需要基于上一次游標(biāo)延續(xù)之前的迭代過(guò)程,以0作為游標(biāo)作為一次新的迭代,直到返回游標(biāo)0完成一次遍歷。
不保證每次執(zhí)行都返回某個(gè)給定數(shù)量的元素,支持模糊查詢。
-
一次返回的數(shù)量不可控,只能是大概率符合count參數(shù)
Scan 0 match k1* count 10
可能獲取的數(shù)據(jù)數(shù)量不是10條,可能獲得重復(fù)元素,要在程序中去重
如何通過(guò)Redis實(shí)現(xiàn)分布式鎖?
要想實(shí)現(xiàn)分布式鎖,我們需要考慮到以下需求:
- 互斥性
- 安全性
- 注意死鎖問(wèn)題
- 容錯(cuò)
可以利用Redis如下命令的特性實(shí)現(xiàn)一個(gè)分布式鎖
SETNX key value :如果key不存在,創(chuàng)建并且賦值(用key作為鎖)
但這樣是占有了一個(gè)鎖,如何釋放掉它呢?也就是說(shuō)如何解決SETNX長(zhǎng)期有效的問(wèn)題
EXPIRE key seconds :設(shè)置過(guò)期時(shí)間刪除模擬鎖的釋放
下面看一個(gè)偽代碼的實(shí)現(xiàn)
long status = redisService.setnx(key,"1");
//注釋1
if(status==1){
redisService.expire(key,expire);
doSomething();//......
}
上面的代碼看上去似乎沒(méi)有什么問(wèn)題,但是仔細(xì)想想,如果執(zhí)行到注釋1的地方的時(shí)候,redis服務(wù)器發(fā)生了宕機(jī)怎么辦? 所以我們要保證setnx操作和expire的設(shè)置是原子的
所以redis引入了以下命令
set locktarget 12345(可以寫線程的標(biāo)識(shí)) ex 10 nx //該操作是個(gè)原子操作,成功返回OK,失敗返回nil
偽代碼實(shí)現(xiàn):
String result = redisService.set(lockkey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expiretime);
if("OK".equals(result)){
doSomething();//....
}
大量的key同時(shí)過(guò)期怎么處理?
key集中過(guò)期,清除大量的key很耗時(shí),會(huì)出現(xiàn)短暫的卡頓現(xiàn)象
解決方案: 在設(shè)置key的過(guò)期時(shí)間的時(shí)候,給每個(gè)key加上隨機(jī)的值
如何用Redis做異步隊(duì)列?
使用list做異步隊(duì)列, rpush生產(chǎn)消息,lpop消費(fèi)消息
缺點(diǎn):沒(méi)有等待隊(duì)列里有值就進(jìn)行直接消費(fèi)
彌補(bǔ):可以在應(yīng)用層引入sleep機(jī)制去調(diào)用lpop重試
也可以用以下命令
BLPOP key[key...] timeout : 阻塞直到隊(duì)列有消息或者超時(shí)
缺點(diǎn):只能供一個(gè)消費(fèi)者消費(fèi)
Pub/Sub 主題訂閱模式

這里有三個(gè)客戶端連接 分別為cli-1、cli-2、cli-3
cli1:6379-> subscribe myTopic
cli2:6379-> subscribe myTopic
cli3:6379-> publish myTopic "Hello"
然后cli1 和 cli3便會(huì)接收到hello消息
這里請(qǐng)注意: 消息的發(fā)布是無(wú)狀態(tài)的,無(wú)法保證可達(dá)
Redis如何做持久化?
1.RDB(快照)持久化:保存某個(gè)時(shí)間點(diǎn)的全量數(shù)據(jù)快照
在redis.conf 中可以配置 RDB持久化方式的持久化策略
- save 900 1 //在900s內(nèi)進(jìn)行一次寫操作觸發(fā)持久化
- save 300 10
- Save 60 10000
stop-writes-on-bgsave-error yes
當(dāng)備份進(jìn)程出錯(cuò)誤時(shí),主進(jìn)程停止接受新的寫入操作(保證持久化的數(shù)據(jù)一致性問(wèn)題)
rdbcompression no
建議設(shè)置為no,關(guān)閉壓縮,降低cpu損耗(因?yàn)镽edis本身就是CPU密集型)
手動(dòng)持久化的命令
1.SAVE : 阻塞Redis的服務(wù)器進(jìn)程直到RDB文件被創(chuàng)建完畢
2.BGSAVE: fork出一個(gè)子進(jìn)程來(lái)創(chuàng)建RDB文件,不阻塞服務(wù)器進(jìn)程
自動(dòng)觸發(fā)RDB持久化的方式
- 根據(jù)redis.conf配置的save m n 定時(shí)觸發(fā)(用的bgsave)
- 主從復(fù)制時(shí),主節(jié)點(diǎn)自動(dòng)觸發(fā)
- 執(zhí)行debug reload
- 執(zhí)行shutdown 且沒(méi)有開啟ROF持久化
什么是 Copy-On-Write 寫時(shí)復(fù)制?
如果有多個(gè)調(diào)用者同時(shí)獲取相同的資源的時(shí)候,他們會(huì)獲取相同的指針指向相同的資源,直到某個(gè)調(diào)用者試圖修改資源的內(nèi)容的時(shí)候,系統(tǒng)才會(huì)真正復(fù)制一份專有副本給調(diào)用者,而其他調(diào)用者見(jiàn)到的最初資源保持不變。
當(dāng)redis做持久化時(shí),redis會(huì)fork一個(gè)子進(jìn)程,將數(shù)據(jù)寫入磁盤中的一個(gè)臨時(shí)的rdb文件中,當(dāng)子進(jìn)程完成寫臨時(shí)文件之后,將原來(lái)的rdb替換掉,這樣的好處是可以實(shí)現(xiàn)copy-on-write,子進(jìn)程繼續(xù)可以接受其他請(qǐng)求,確保了redis性能。
缺點(diǎn):內(nèi)存數(shù)據(jù)的全量同步,當(dāng)數(shù)據(jù)量大的時(shí)候會(huì)由于I/O而嚴(yán)重影響性能,可能會(huì)因?yàn)閞edis掛掉而丟失從當(dāng)前至最近一次快照期間的數(shù)據(jù)。
2.AOF(Append-Only-File)持久化:保存寫狀態(tài)
- 記錄下除了查詢以外所有變更數(shù)據(jù)庫(kù)狀態(tài)的指令
- 以append的形式追加保存到AOF文件中(增量)
AOF的持久化默認(rèn)是關(guān)閉的. vim redis.conf
appendonlyno. —>修改為 appendonly yes 生效
appendfilename "append only.aof"
appendfsync:可以指定AOF寫入方式 :
1.always 2.everysec 3.no
AOF日志重寫 bgrewrite aof
日志重寫解決AOF文件大小不斷增大的問(wèn)題,原理如下:
- 調(diào)用fork(),創(chuàng)建一個(gè)子進(jìn)程
- 子進(jìn)程把新的AOF寫到一個(gè)臨時(shí)文件里,不依賴原來(lái)的AOF文件
- 主進(jìn)程持續(xù)把新的變動(dòng)同時(shí)寫入內(nèi)存和原來(lái)的AOF里
- 主進(jìn)程獲取子進(jìn)程的重寫AOF的完成信號(hào),往新的AOF同步增量變動(dòng)
- 使用新的AOF文件替換掉舊的AOF文件
Redis數(shù)據(jù)的恢復(fù)
RDB和AOF文件共存情況下的恢復(fù)流程

RDB-AOF混合持久化的方式
BGSAVE做鏡像全量持久化,AOF做增量持久化
總結(jié)RDB和AOF的優(yōu)缺點(diǎn)
- RDB優(yōu)點(diǎn):全量數(shù)據(jù)快照,文件小,恢復(fù)快
- RDB缺點(diǎn):無(wú)法保存最近一次快照后的數(shù)據(jù)
- AOF優(yōu)點(diǎn):可讀性高,適合保存增量數(shù)據(jù),數(shù)據(jù)不易丟失
- AOF缺點(diǎn):文件體積大,恢復(fù)時(shí)間長(zhǎng)
Redis主從復(fù)制同步
1.全同步過(guò)程
- Slave發(fā)送sync命令到master
- master啟動(dòng)一個(gè)后臺(tái)進(jìn)程,將redis中的數(shù)據(jù)快照保存到文件中
- master將保存快照期間收到的命令緩存起來(lái)
- master完成寫文件操作后,將該文件發(fā)送給salve
- 使用新的AOF文件替換掉舊的AOF文件
- master將這期間收到的寫命令發(fā)送給salve,進(jìn)行回放
2.增量同步的過(guò)程
- master接受用戶的操作指令,判斷是否需要傳播到salve
- 將操作記錄追加到aof文件
- 將操作傳播到其他slave:1.對(duì)齊主從庫(kù) 2.往響應(yīng)緩存中寫入指令
- 將緩存中的數(shù)據(jù)發(fā)送給slave
主從模式不具備高可用性,當(dāng)master掛掉以后,slave將無(wú)法對(duì)外提供寫入操作,為解決該問(wèn)題,引入redis sentinel(哨兵)解決主從同步宕機(jī)后的主從切換問(wèn)題
- 監(jiān)控:檢查主從服務(wù)器是否運(yùn)行正常
- 提醒:通過(guò)API向管理員或者其他應(yīng)用程序發(fā)送故障通知
- 自動(dòng)故障轉(zhuǎn)移:主從切換
流言協(xié)議 Gossip 在雜亂無(wú)章中尋求一致
每個(gè)節(jié)點(diǎn)都隨機(jī)與對(duì)方通信,最終所有節(jié)點(diǎn)的狀態(tài)達(dá)成一致。
種子節(jié)點(diǎn)定期隨機(jī)向其他節(jié)點(diǎn)發(fā)送節(jié)點(diǎn)列表以及需要傳播的信息
不保證信息一定會(huì)傳遞給所有的節(jié)點(diǎn),但最終會(huì)趨于一致性。
關(guān)于redis集群的更多總結(jié)我會(huì)放到下一篇文章里,今天的總結(jié)就到這里了!