Redis 知識總結(jié)
在公眾號后臺回復(fù):JGNB,可獲取杰哥原創(chuàng)的 PDF 手冊。
1. Redis 概覽
Redis 和 memcache 的區(qū)別,Redis 支持的數(shù)據(jù)類型應(yīng)用場景
redis 支持的數(shù)據(jù)結(jié)構(gòu)更豐富(string,hash,list,set,zset)。memcache 只支持 key-value 的存儲;
redis 原生支持集群,memcache 沒有原生的集群模式。
2. Redis 單線程模型
redis 單線程處理請求流程
redis 采用 IO 多路復(fù)用機制來處理請求,采用 reactor IO 模型, 處理流程如下:
首先接收到客戶端的 socket 請求,多路復(fù)用器將 socket 轉(zhuǎn)給連接應(yīng)答處理器;
連接應(yīng)答處理器將 AE_READABLE 事件與命令請求處理器關(guān)聯(lián)(這里是把 socket 事件放入一個隊列);
命令請求處理器從 socket 中讀到指令,再內(nèi)存中執(zhí)行,并將 AE_WRITEABLE 事件與命令回復(fù)處理器關(guān)聯(lián);
命令回復(fù)處理器將結(jié)果返回給 socket,并解除關(guān)聯(lián)。

redis 單線程效率高的原因
非阻塞 IO 復(fù)用(上圖流程), I/O 多路復(fù)用分派事件,事件處理器處理事件(這個可以理解為注冊的一段函數(shù),定義了事件發(fā)生的時候應(yīng)該執(zhí)行的動作), 這里分派事件和處理事件其實都是同一個線程;
純內(nèi)存操作效率高;
單線程反而避免了多線程切換。
3. Redis 過期策略
對 key 設(shè)置有效期,redis 的刪除策略: 定期刪除+惰性刪除。
定期刪除指的是 redis 默認每 100ms 就隨機抽取一些設(shè)置了過期事件的 key ,檢查是否過期,如果過期就刪除。如果 redis 設(shè)置了 10 萬個 key 都設(shè)置了過期時間,每隔幾百毫秒就要檢查 10 萬個 key 那 CPU 負載就很高了,所以 redis 并不會每隔 100ms 就檢查所有的 key,而是隨機抽取一些 key 來檢查。
但這樣會導(dǎo)致有些 key 過期了并沒有被刪除,所以采取了惰性刪除。意思是在獲取某個 key 的時候發(fā)現(xiàn)過期了,如果 key 過期了就刪除掉不會返回。
這兩個策略結(jié)合起來保證過期的 key 一定會被刪除。
最大內(nèi)存淘汰(maxmemory-policy)
如果 redis 內(nèi)存占用太多,就會進行內(nèi)存淘汰。有如下策略:
noeviction: 如果內(nèi)存不足以寫入數(shù)據(jù), 新寫入操作直接報錯;
allkeys-lru: 內(nèi)存不足以寫入數(shù)據(jù),移除最近最少使用的 key(最常用的策略);
allkeys-random: 內(nèi)存不足隨機移除幾個 key;
volatile-lru: 在設(shè)置了過期時間的 key 中,移除最近最少使用;
volatile-random: 設(shè)置了過期的時間的 key 中,隨機移除幾個。
4. Redis 主從模式保證高并發(fā)和高可用(哨兵模式)
讀寫分離
單機的 Redis 的 QPS 大概就在上萬到幾萬不等,無法承受更高的并發(fā)。
讀寫分離保證高并發(fā)(10W+ QPS):對于緩存來說一般都是支撐高并發(fā)讀,寫請求都是比較少的。采用讀寫分離的架構(gòu)(一主多從),master 負責接收寫請求,數(shù)據(jù)同步到 slave 上提供讀服務(wù),如果遇到瓶頸只需要增加 slave 機器就可以水平擴容
主從復(fù)制機制
redis replication 機制:
redis 采取異步復(fù)制到 slave 節(jié)點;
slave 節(jié)點做復(fù)制操作的時候是不會 block 自己的,它會使用舊的數(shù)據(jù)集來提供服務(wù),復(fù)制。完成后,刪除舊的數(shù)據(jù)集,加載新的數(shù)據(jù)集,這個時候會暫停服務(wù)(時間很短暫);
如果采用了主從架構(gòu),master 需要開啟持久化。如果 master 沒有開啟持久化(rdb 和 aof 都關(guān)閉了)。master 宕機重啟后數(shù)據(jù)是空的,然后經(jīng)過復(fù)制就把所有 slave 的數(shù)據(jù)也弄丟了。
即使采用高可用的的哨兵機制,可能 sentinal 還沒有檢測到 master failure,master 就自動重啟了,還是會導(dǎo)致 slave 清空故障。
主從同步流程
當 slave 啟動時會發(fā)送一個 psync 命令給 master;
如果是重新連接 master,則 master node 會復(fù)制給 slave 缺少的那部分數(shù)據(jù);
如果是 slave 第一次連接 master,則會觸發(fā)一次全量復(fù)制(full resynchronization)。開始 full resynchronization 的時候,master 會生成一份 rdb 快照,同時將客戶端命令緩存在內(nèi)存,rdb 生成完后,就發(fā)送給 slave,slave 先寫入磁盤在加載到內(nèi)存。然后 master 將緩存的命令發(fā)送給 slave。

哨兵(sentinal)模式介紹
哨兵是 redis 集群架構(gòu)的一個重要組件,主要提供如下功能:
集群監(jiān)控:負責監(jiān)控 master 和 slave 是否正常工作;
消息通知:如果某個 redis 實例有故障, 哨兵負責發(fā)消息通知管理員;
故障轉(zhuǎn)移: 如果 master node 發(fā)生故障,會自動切換到 slave;
配置中心:如果故障轉(zhuǎn)移發(fā)生了,通知客戶端新的 master 地址。
哨兵的核心知識:
哨兵至少三個,保證自己的高可用;
哨兵+主從的部署架構(gòu)是用來保證 redis 集群高可用的,并非保證數(shù)據(jù)不丟失;
哨兵(Sentinel)需要通過不斷的測試和觀察才能保證高可用。
為什么哨兵只有兩個節(jié)點無法正常工作

假設(shè)哨兵集群只部署了 2 個哨兵實例,quorum=1。
master 宕機的時候,s1 和 s2 只要有一個哨兵認為 master 宕機 j 就可以進行切換,并且會從 s1 和 s2 中選取一個來進行故障轉(zhuǎn)移。這個時候是需要滿足 majority,也就是大多數(shù)哨兵是運行的,2 個哨兵的 majority 是 2,如果 2 個哨兵都運行著就允許執(zhí)行故障轉(zhuǎn)移。如果 M1 所在的機器宕機了,那么 s1 哨兵也就掛了,只剩 s2 一個,沒有 majorityl 來允許執(zhí)行故障轉(zhuǎn)移,雖然集群還有一臺機器 R1,但是故障轉(zhuǎn)移也不會執(zhí)行。
如果是經(jīng)典的三哨兵集群,如下:

此時 majority 也是 2,就算 M1 所在的機器宕機了,哨兵還是剩下兩個 s2 和 s3,它們滿足 majority 就可以允許故障轉(zhuǎn)移執(zhí)行。
哨兵核心底層原理
sdown 和 odown 兩種失敗狀態(tài);
sdown 是主觀宕機,就是一個哨兵覺得 master 宕機了,達成條件是如果一個哨兵 ping master 超過了 is-master-down-after-milliseconds 指定的毫秒數(shù)后就認為主觀宕機;
odown 是客觀宕機,如果一個哨兵在指定時間內(nèi)收到了 majority(大多數(shù)) 數(shù)量的哨兵也認為那個 master 宕機了,就是客觀宕機。
哨兵之間的互相發(fā)現(xiàn):哨兵是通過 redis 的 pub/sub 實現(xiàn)的。
5. Redis 數(shù)據(jù)的恢復(fù)(Redis 的持久化)
RDB
RDB 原理
RDB(Redis DataBase)是將某一個時刻的內(nèi)存快照(Snapshot),以二進制的方式寫入磁盤的過程。
RDB 有兩種方式 save 和 bgsave:
save: 執(zhí)行就會觸發(fā) Redis 的持久化,但同時也是使 Redis 處于阻塞狀態(tài),直到 RDB 持久化完成,才會響應(yīng)其他客戶端發(fā)來的命令;
bgsave: bgsave 會 fork() 一個子進程來執(zhí)行持久化,整個過程中只有在 fork() 子進程時有短暫的阻塞,當子進程被創(chuàng)建之后,Redis 的主進程就可以響應(yīng)其他客戶端的請求了。
RDB 配置
除了使用 save 和 bgsave 命令觸發(fā)之外, RDB 支持自動觸發(fā)。
自動觸發(fā)策略可配置 Redis 在指定的時間內(nèi),數(shù)據(jù)發(fā)生了多少次變化時,會自動執(zhí)行 bgsave 命令。在 redis 配置文件中配置:
在時間 m 秒內(nèi),如果 Redis 數(shù)據(jù)至少發(fā)生了 n 次變化,那么就自動執(zhí)行BGSAVE命令。
save m n
RDB 優(yōu)缺點
RDB 的優(yōu)點:
RDB 會定時生成多個數(shù)據(jù)文件,每個數(shù)據(jù)文件都代表了某個時刻的 redis 全量數(shù)據(jù),適合做冷備,可以將這個文件上傳到一個遠程的安全存儲中,以預(yù)定好的策略來定期備份 redis 中的數(shù)據(jù);
RDB 對 redis 對外提供讀寫服務(wù)的影響非常小,redis 是通過 fork 主進程的一個子進程操作磁盤 IO 來進行持久化的;
相對于 AOF,直接基于 RDB 來恢復(fù) reids 數(shù)據(jù)更快。
RDB 的缺點:
如果使用 RDB 來恢復(fù)數(shù)據(jù),會丟失一部分數(shù)據(jù),因為 RDB 是定時生成的快照文件;
RDB 每次來 fork 出子進程的時候,如果數(shù)據(jù)文件特別大,可能會影響對外提供服務(wù),暫停數(shù)秒(主進程需要拷貝自己的內(nèi)存表給子進程, 實例很大的時候這個拷貝過程會很長)。latest_fork_usec 代表 fork 導(dǎo)致的延時;Redis 上執(zhí)行 INFO 命令查看 latest_fork_usec;當 RDB 比較大的時候, 應(yīng)該在 slave 節(jié)點執(zhí)行備份, 并在低峰期執(zhí)行。
AOF
AOF 原理
redis 對每條寫入命令進行日志記錄,以 append-only 的方式寫入一個日志文件,redis 重啟的時候通過重放日志文件來恢復(fù)數(shù)據(jù)集。(由于運行久了 AOF 文件會越來越大,redis 提供一種 rewrite 機制,基于當前內(nèi)存中的數(shù)據(jù)集,來構(gòu)建一個更小的 AOF 文件,將舊的龐大的 AOF 文件刪除)。rewrite 即把日志文件壓縮, 通過 bgrewriteaof 觸發(fā)重寫。AOF rewrite 后臺執(zhí)行的方式和 RDB 有類似的地方,fork 一個子進程,主進程仍進行服務(wù),子進程執(zhí)行 AOF 持久化,數(shù)據(jù)被 dump 到磁盤上。與 RDB 不同的是,后臺子進程持久化過程中,主進程會記錄期間的所有數(shù)據(jù)變更(主進程還在服務(wù)),并存儲在 server.aof_rewrite_buf_blocks 中;后臺子進程結(jié)束后,Redis 更新緩存追加到 AOF 文件中,是 RDB 持久化所不具備的。
AOF 的工作流程如下:
Redis 執(zhí)行寫命令后,把這個命令寫入到 AOF 文件內(nèi)存中(write 系統(tǒng)調(diào)用);
Redis 根據(jù)配置的 AOF 刷盤策略,把 AOF 內(nèi)存數(shù)據(jù)刷到磁盤上(fsync 系統(tǒng)調(diào)用);
根據(jù) rewrite 相關(guān)的配置觸發(fā) rewrite 流程。
AOF 配置
appendonly: 是否啟用 AOF(yes | no);
appendfsync: 刷盤的機制:
always:主線程每次執(zhí)行寫操作后立即刷盤,此方案會占用比較大的磁盤 IO 資源,但數(shù)據(jù)安全性最高;
everysec:主線程每次寫操作只寫內(nèi)存就返回,然后由后臺線程每隔 1 秒執(zhí)行一次刷盤操作(觸發(fā) fsync 系統(tǒng)調(diào)用),此方案對性能影響相對較小,但當 Redis 宕機時會丟失 1 秒的數(shù)據(jù);
no:主線程每次寫操作只寫內(nèi)存就返回,內(nèi)存數(shù)據(jù)什么時候刷到磁盤,交由操作系統(tǒng)決定,此方案對性能影響最小,但數(shù)據(jù)安全性也最低,Redis 宕機時丟失的數(shù)據(jù)取決于操作系統(tǒng)刷盤時機。
auto-aof-rewrite-percentage: 當 aof 文件相較于上一版本的 aof 文件大小的百分比達到多少時觸發(fā) AOF 重寫。舉個例子,auto-aof-rewrite-percentage 選項配置為 100,上一版本的 aof 文件大小為 100M,那么當我們的 aof 文件達到 200M 的時候,觸發(fā) AOF 重寫;
auto-aof-rewite-min-size:最小能容忍 aof 文件大小,超過這個大小必須進行 AOF 重寫;
no-appendfsync-on-rewrite: 設(shè)置為 yes 表示 rewrite 期間對新寫操作不 fsync,暫時存在內(nèi)存中,等 rewrite 完成后再寫入,默認為 no。
AOF 優(yōu)缺點
AOF 的優(yōu)點:
可以更好的保證數(shù)據(jù)不丟失,一般 AOF 每隔 1s 通過一個后臺線程來執(zhí)行 fsync(強制刷新磁盤頁緩存),最多丟失 1s 的數(shù)據(jù);
AOF 以 append-only 的方式寫入(順序追加),沒有磁盤尋址開銷,性能很高;
AOF 即使文件很大, 觸發(fā)后臺 rewrite 的操作的時候一般也不會影響客戶端的讀寫,(rewrite 的時候會對其中指令進行壓縮,創(chuàng)建出一份恢復(fù)需要的最小日志出來)。
在創(chuàng)建新的日志文件的時候,老的文件還是照常寫入,當新的文件創(chuàng)建完成后再交換新老日志。但是還是有可能會影響到主線程的寫入, 如:

當磁盤的 IO 負載很高,那這個后臺線程在執(zhí)行 AOF fsync 刷盤操作(fsync 系統(tǒng)調(diào)用)時就會被阻塞住, ,緊接著,主線程又需要把數(shù)據(jù)寫到文件內(nèi)存中(write 系統(tǒng)調(diào)用),但此時的后臺子線程由于磁盤負載過高,導(dǎo)致 fsync 發(fā)生阻塞,遲遲不能返回,那主線程在執(zhí)行 write 系統(tǒng)調(diào)用時,也會被阻塞住,直到后臺線程 fsync 執(zhí)行完成后,主線程執(zhí)行 write 才能成功返回。這時候主線程就無法響應(yīng)客戶端的請求, 可能會導(dǎo)致客戶端請求 redis 超時。具體類似: https://blog.csdn.net/mmgithub123/article/details/124507846。
AOF 日志文件通過非常可讀的方式進行記錄,這個特性適合做災(zāi)難性的誤操作的緊急恢復(fù),比如不小心使用 flushall 清空了所有數(shù)據(jù),只要 rewrite 沒有發(fā)生,就可以立即拷貝 AOF,將最后一條 flushall 命令刪除,再回放 AOF 恢復(fù)數(shù)據(jù)。
AOF 的缺點:
同一份數(shù)據(jù),因為 AOF 記錄的命令會比 RDB 快照文件更大;
AOF 開啟后,支持寫的 QPS 會比 RDB 支持寫的 QPS 要低,畢竟 AOF 有寫磁盤的操作。
總結(jié)
總結(jié) AOF 和 RDB 該如何選擇:兩者綜合使用,將 AOF 配置成每秒 fsync 一次。RDB 作為冷備,AOF 用來保證數(shù)據(jù)不丟失的恢復(fù)第一選擇,當 AOF 文件損壞或不可用的時候還可以使用 RDB 來快速恢復(fù)。
6. Redis 集群模式(redis cluster)
在主從部署模式上,雖然實現(xiàn)了一定程度的高并發(fā),并保證了高可用,但是有如下限制:
master 數(shù)據(jù)和 slave 數(shù)據(jù)一模一樣,master 的數(shù)據(jù)量就是集群的限制瓶頸;
redis 集群的寫能力也受到了 master 節(jié)點的單機限制。
在高版本的 Redis 已經(jīng)原生支持集群(cluster)模式,可以多 master 多 slave 部署,橫向擴展 Redis 集群的能力。Redis Cluster 支持 N 個 master node ,每個 master node 可以掛載多個 slave node。
redis cluster 介紹
自動將數(shù)據(jù)切片,每個 master 上放一部分數(shù)據(jù);
提供內(nèi)置的高可用支持,部分 master 不可用時還是能夠工作;
redis cluster 模式下,每個 redis 要開放兩個端口:6379 和 10000+以后的端口(如 16379)。16379 是用來節(jié)點之間通信的,使用的是 cluster bus 集群總線。cluster bus 用來做故障檢測,配置更新,故障轉(zhuǎn)移授權(quán)。
redis cluster 負載均衡
redis cluster 采用 一致性 hash+虛擬節(jié)點 來負載均衡。redis cluster 有固定的 16384 個 slot (2^14),對每個 key 做 CRC16 值計算,然后對 16384 mod??梢垣@取每個 key 的 slot。redis cluster 每個 master 都會持有部分 slot,比如 三個 master 那么 每個 master 就會持有 5000 多個 slot。hash slot 讓 node 的添加和刪除變得很簡單,增加一個 master,就將其他 master 的 slot 移動部分過去,減少一個就分給其他 master,這樣讓集群擴容的成本變得很低。
cluster 基礎(chǔ)通信原理(gossip 協(xié)議)
與集中式不同(如使用 zookeeper 進行分布式協(xié)調(diào)注冊),redis cluster 使用的是 gossip 協(xié)議進行通信。并不是將集群元數(shù)據(jù)存儲在某個節(jié)點上,而是不斷的互相通信,保持整個集群的元數(shù)據(jù)是完整的。gossip 協(xié)議所有節(jié)點都持有一份元數(shù)據(jù),不同節(jié)點的元數(shù)據(jù)發(fā)生了變更,就不斷的將元數(shù)據(jù)發(fā)送給其他節(jié)點,讓其他節(jié)點也進行元數(shù)據(jù)的變更。
集中式的好處:元數(shù)據(jù)的讀取和更新時效性很好,一旦元數(shù)據(jù)變化就更新到集中式存儲,缺點就是元數(shù)據(jù)都在一個地方,可能導(dǎo)致元數(shù)據(jù)的存儲壓力。
對于 gossip 來說:元數(shù)據(jù)的更新會有延時,會降低元數(shù)據(jù)的壓力,缺點是操作是元數(shù)據(jù)更新可能會導(dǎo)致集群的操作有一些滯后。
redis cluster 主備切換與高可用
判斷節(jié)點宕機:如果有一個節(jié)點認為另外一個節(jié)點宕機,那就是 pfail,主觀宕機。如果多個節(jié)點認為一個節(jié)點宕機,那就是 fail,客觀宕機。跟哨兵的原理一樣;
對宕機的 master,從其所有的 slave 中選取一個切換成 master node,再次之前會進行一次過濾,檢查每個 slave 與 master 的斷開時間,如果超過了 cluster-node-timeout * cluster-slave-validity-factor 就沒有資格切換成 master;
從節(jié)點選取:每個從節(jié)點都會根據(jù)從 master 復(fù)制數(shù)據(jù)的 offset,來設(shè)置一個選舉時間,offset 越大的從節(jié)點,選舉時間越靠前,master node 開始給 slave 選舉投票,如果大部分 master(n/2+1)都投給了某個 slave,那么選舉通過(與 zk 有點像,選舉時間類似于 epochid);
整個流程與哨兵類似,可以說 redis cluster 集成了哨兵的功能,更加的強大;
Redis 集群部署相關(guān)問題 redis 機器的配置,多少臺機器,能達到多少 qps?
機器標準:8 核+32G
集群: 5 主+5 從(每個 master 都掛一個 slave)
效果: 每臺機器最高峰每秒大概 5W,5 臺機器最多就是 25W,每個 master 都有一個從節(jié)點,任何一個節(jié)點掛了都有備份可切換成主節(jié)點進行故障轉(zhuǎn)移
腦裂問題哨兵模式下:
master 下 掛載了 3 個 slave,如果 master 由于網(wǎng)絡(luò)抖動被哨兵認為宕機了,執(zhí)行了故障轉(zhuǎn)移,從 slave 里面選取了一個作為新的 master,這個時候老的 master 又恢復(fù)了,剛好又有 client 連的還是老的 master,就會產(chǎn)生腦裂,數(shù)據(jù)也會不一致,比如 incr 全局 id 也會重復(fù)。
redis 對此的解決方案是:min-slaves-to-write 1 至少有一個 slave 連接 min-slaves-max-lag 10 slave 與 master 主從復(fù)制延遲時間如果連接到 master 的 slave 數(shù)小于最少 slave 的數(shù)量,并且主從復(fù)制延遲時間超過配置時間,master 就拒絕寫入 12。client 連接 redis 多 tcp 連接的考量首先 redis server 雖然是單線程來處理請求, 但是他是多路復(fù)用的, 單 tcp 連接肯定是沒有多 tcp 連接性能好, 多路復(fù)用一個 io 周期得到的就緒 io 事件越多, 處理的就越多。這也不是絕對的, 如果使用 pipeline 的方式傳輸, 單連接會比多連接性能好, 因為每一個 pipeline 的單次請求過多也會導(dǎo)致單周期到的命令太多, 性能下降多少個連接比較合適這個問題, redis cluser 控制在每個節(jié)點 100 個連接以內(nèi)。
推薦閱讀
50 個 Redis 必備知識:基礎(chǔ)知識,架構(gòu)、調(diào)優(yōu)和監(jiān)控知識及難點解決
學(xué)會這幾個 Redis 技巧,讓你的程序快如閃電!

