第一部分:Redis篇

一、什么是緩存穿透? 怎么解決?
緩存穿透
緩存穿透是指緩存和數(shù)據(jù)庫都沒有數(shù)據(jù),導(dǎo)致所有請(qǐng)求都到了數(shù)據(jù)庫,如果從數(shù)據(jù)庫查不到數(shù)據(jù)且不寫入緩存,造成數(shù)據(jù)庫短時(shí)間內(nèi)承受大量的請(qǐng)求而導(dǎo)致宕機(jī)。
解決
1、布隆過濾器
將查詢的參數(shù)存儲(chǔ)到一個(gè)bitmap中,在查詢緩存前,如果 bitmap 存在則進(jìn)行底層緩存的數(shù)據(jù)查詢,如果不存在則進(jìn)行攔截,不再進(jìn)行緩存的數(shù)據(jù)查詢。
優(yōu)點(diǎn):內(nèi)存占用較少,沒有多余key;
缺點(diǎn):實(shí)現(xiàn)復(fù)雜,存在誤判,元素不能刪除
2、緩存空對(duì)值
如果數(shù)據(jù)庫查詢的為空,則依然把這個(gè)數(shù)據(jù)緩存并設(shè)置過期時(shí)間,當(dāng)多次訪問的時(shí)候可以直接返回結(jié)果,避免造成多次訪問數(shù)據(jù)庫,但要保證當(dāng)數(shù)據(jù)庫有數(shù)據(jù)時(shí)及時(shí)更新緩存。
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單;
缺點(diǎn):消耗內(nèi)存,可能會(huì)發(fā)生不一致的問題
布隆過濾器
bitmap(位圖):相當(dāng)于是一個(gè)以bit為單位的數(shù)組,數(shù)組中每個(gè)單元只能存儲(chǔ)二進(jìn)制數(shù)0或1
布隆過濾器:一種節(jié)省空間的概率數(shù)據(jù)結(jié)構(gòu),它實(shí)際上是由一個(gè)很長的二進(jìn)制向量(位圖)和一系列隨機(jī)映射函數(shù)組成,用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間比一般算法要好很多,但也有一定概率的誤判性。它會(huì)告訴你某東西一定不存在或者可能存在。
實(shí)現(xiàn)原理:先初始化一個(gè)比較大數(shù)組,一開始都是0,當(dāng)一個(gè)key后經(jīng)過3次hash計(jì)算,模于數(shù)組長度得到數(shù)據(jù)的下標(biāo)然后把數(shù)組中原來的0改為1。這樣的話,三個(gè)數(shù)組的位置就能標(biāo)明一個(gè)key的存在。查找的過程也是一樣的。

二、什么是緩存擊穿 ? 怎么解決 ?
緩存擊穿
設(shè)置了過期時(shí)間且為熱點(diǎn)的key,在失效的瞬間,恰好對(duì)這個(gè)Key有大量的并發(fā)請(qǐng)求過來,擊穿了緩存,直接請(qǐng)求數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫崩潰。
解決
1、使用互斥鎖
請(qǐng)求查詢A,發(fā)現(xiàn)緩存中沒有。對(duì)A這個(gè)key加鎖,同時(shí)去數(shù)據(jù)庫查詢數(shù)據(jù),寫?緩存,再返回給?戶,這樣后?的請(qǐng)求就可以從緩存中拿到數(shù)據(jù)了。

2、設(shè)置邏輯過期
1、在設(shè)置key的時(shí)候,設(shè)置一個(gè)過期時(shí)間字段存入緩存中,不給當(dāng)前 key設(shè)置過期時(shí)間
2、當(dāng)查詢的時(shí)候,從redis取出數(shù)據(jù)后判斷時(shí)間是否過期
3、如果過期則開通另外一個(gè)線程進(jìn)行數(shù)據(jù)同步,當(dāng)前線程正常返回?cái)?shù)據(jù),這個(gè)數(shù)據(jù)不是最新。

三、什么是緩存雪崩 ? 怎么解決 ?
緩存雪崩
某一時(shí)刻發(fā)生大規(guī)模的緩存失效的情況,例如緩存服務(wù)宕機(jī)、大量key在同一時(shí)間過期,這樣的后果就是大量的請(qǐng)求直接到數(shù)據(jù)庫,數(shù)據(jù)庫無響應(yīng),最后可能導(dǎo)致整個(gè)系統(tǒng)的崩潰。
解決
1、利用Redis集群提高服務(wù)的可用性
通過集群來提升緩存的可用性,可以利用Redis本身的Redis Cluster或者第三方集群方案如Codis等。
2、給業(yè)務(wù)添加多級(jí)緩存
設(shè)置多級(jí)緩存,設(shè)置一級(jí)緩存本地(Guava,Caffeine)緩存,第一級(jí)緩存失效的基礎(chǔ)上再訪問二級(jí)緩存redis,每一級(jí)緩存的失效時(shí)間都不同。
3、過期時(shí)間
1、為了避免大量的緩存在同一時(shí)間過期,可以把不同的 key 過期時(shí)間隨機(jī)生成,避免過期時(shí)間太過集中。
2、熱點(diǎn)數(shù)據(jù)設(shè)置邏輯過期。
4、熔斷降級(jí)
熔斷
當(dāng)緩存服務(wù)器宕機(jī)或超時(shí)響應(yīng)時(shí),為了防止整個(gè)系統(tǒng)出現(xiàn)雪崩,可以使用hystrix 類似 的熔斷,暫時(shí)停止業(yè)務(wù)服務(wù)訪問數(shù)據(jù)庫, 或者其他被依賴的服務(wù),避免 MySQL 被打死。
降級(jí)
當(dāng)出現(xiàn)大量緩存失效,而且處在高并發(fā)高的情況下,在業(yè)務(wù)系統(tǒng)內(nèi)部暫時(shí)舍棄對(duì)一些非核心的接口和數(shù)據(jù)的請(qǐng)求,而直接返回一個(gè)提前準(zhǔn)備好的 fallback(退路)錯(cuò)誤處理信息。
四、常用的緩存讀寫策略詳解
Cache aside
?????? 先更新完數(shù)據(jù)庫,再刪除緩存。這種策略發(fā)生數(shù)據(jù)庫和緩存數(shù)據(jù)不一致的概率很低,因?yàn)橥ǔ?shù)據(jù)庫更新操作比內(nèi)存操作耗時(shí)多出幾個(gè)數(shù)量級(jí),讀請(qǐng)求的回寫緩存速度非???,通常會(huì)在寫請(qǐng)求更新數(shù)據(jù)庫之前完成。
讀請(qǐng)求

寫請(qǐng)求

先更新數(shù)據(jù)庫,再更新緩存
1.?????? 線程A:更新了數(shù)據(jù)庫
2.?????? 線程B:更新了數(shù)據(jù)庫
3.?????? 線程B:更新了緩存
4.?????? 線程A:更新了緩存
數(shù)據(jù)庫數(shù)據(jù)與緩存數(shù)據(jù)的不一致
先刪緩存,再更新數(shù)據(jù)庫
1.?????? 線程A:刪除緩存
2.?????? 線程B:查詢緩存,發(fā)現(xiàn)緩存中沒有想要的數(shù)據(jù)
3.?????? 線程B:查詢數(shù)據(jù)庫中的舊數(shù)據(jù)
4.?????? 線程B:將查詢到的舊數(shù)據(jù)寫入緩存
5.?????? 線程A:將新數(shù)據(jù)寫入數(shù)據(jù)庫
數(shù)據(jù)庫數(shù)據(jù)與緩存數(shù)據(jù)的不一致
Read/Write Through
Read/Write Through中服務(wù)端把 cache 視為主要數(shù)據(jù)存儲(chǔ),從中讀取數(shù)據(jù)并將數(shù)據(jù)寫入其中。
寫(Write Through)
l? 先查 cache,cache 中不存在,直接更新 db。
l? cache 中存在,則先更新 cache,然后 cache 服務(wù)自己更新 db(同步更新 cache 和 db)。

讀(Read Through)
l? 從 cache 中讀取數(shù)據(jù),讀取到就直接返回 。
讀取不到的話,先從 db 加載,寫入到 cache 后返回響應(yīng)。

Write Behind
?????? 與Read/Write Through Pattern 很相似,兩者都是由 cache 服務(wù)來負(fù)責(zé) cache 和 db 的讀寫。但是,兩個(gè)又有很大的不同:Read/Write是同步更新 cache 和 db,而 Write Behind 則是只更新緩存,不直接更新 db,而是改為異步批量的方式來更新 db。????
?????? 這種方式對(duì)數(shù)據(jù)一致性帶來了更大的挑戰(zhàn),比如 cache 數(shù)據(jù)可能還沒異步更新 db 的話,cache 服務(wù)可能就就掛掉了。
五、redis做為緩存,數(shù)據(jù)的持久化是怎么做的?
RDB
RDB全稱Redis Database Backup file,也被叫做Redis數(shù)據(jù)快照。簡(jiǎn)單來說就是把內(nèi)存中的所有數(shù)據(jù)都記錄到磁盤中。當(dāng)Redis實(shí)例故障重啟后,從磁盤讀取快照文件,恢復(fù)數(shù)據(jù)。
觸發(fā)方式
①save和bgsave
bgsave開始時(shí)會(huì)fork主進(jìn)程得到子進(jìn)程,子進(jìn)程共享主進(jìn)程的內(nèi)存數(shù)據(jù)。完成fork后讀取內(nèi)存數(shù)據(jù)并寫入 RDB 文件。fork采用的是copy-on-write技術(shù):
當(dāng)主進(jìn)程執(zhí)行讀操作時(shí),訪問共享內(nèi)存;
當(dāng)主進(jìn)程執(zhí)行寫操作時(shí),則會(huì)拷貝一份數(shù)據(jù),執(zhí)行寫操作。

②自動(dòng)
Redis會(huì)將數(shù)據(jù)集的快照dump到dump.rdb文件中。 此外,我們也可以通過配置文件來修改Redis服務(wù)器dump快照的頻率。
#在900秒(15分鐘)之后,如果至少有1個(gè)key發(fā)生變化,則dump內(nèi)存快照。
save 900 1
#在300秒(5分鐘)之后,如果至少有10個(gè)key發(fā)生變化,則dump內(nèi)存快照。
save 300 10
#在60秒(1分鐘)之后,如果至少有10000個(gè)key發(fā)生變化,則dump內(nèi)存快照。
save 60 10000
AOF
AOF 持久化全稱 append only file,以日志形式記錄每個(gè)寫操作,將 redis 執(zhí)行過的所有寫操作指令記錄下來(讀操作不記錄)。
AOF默認(rèn)是關(guān)閉的,需要修改redis.conf配置文件來開啟AOF:
# 是否開啟AOF功能,默認(rèn)是no
appendonly yes
# AOF文件的名稱
appendfilename "appendonly.aof"
AOF的命令記錄的頻率也可以通過redis.conf文件來配:
# 表示每執(zhí)行一次寫命令,立即記錄到AOF文件
appendfsync always
# 寫命令執(zhí)行完先放入AOF緩沖區(qū),然后表示每隔1秒將緩沖區(qū)數(shù)據(jù)寫到AOF文件,是默認(rèn)方案
appendfsync everysec
# 寫命令執(zhí)行完先放入AOF緩沖區(qū),由操作系統(tǒng)決定何時(shí)將緩沖區(qū)內(nèi)容寫回磁盤
appendfsync no
可以通過bgrewriteaof 命令,可以將 AOF 文件進(jìn)行壓縮,也可以選擇自動(dòng)觸發(fā),在配置文件中配置
# AOF文件比上次文件 增長超過多少百分比則觸發(fā)重寫
auto-aof-rewrite-percentage 100
# AOF文件體積最小多大以上才觸發(fā)重寫
auto-aof-rewrite-min-size 64mb
RDB vs AOF
RDB和AOF各有自己的優(yōu)缺點(diǎn),如果對(duì)數(shù)據(jù)安全性要求較高,在實(shí)際開發(fā)中往往會(huì)結(jié)合兩者來使用。

六、Redis的數(shù)據(jù)過期刪除策略有哪些?
Redis對(duì)數(shù)據(jù)設(shè)置有效時(shí)間,數(shù)據(jù)過期以后,就需要將數(shù)據(jù)從內(nèi)存中刪除掉??梢园凑詹煌囊?guī)則進(jìn)行刪除,這種刪除規(guī)則就被稱之為數(shù)據(jù)的刪除策略(數(shù)據(jù)過期策略)。
Redis的過期刪除策略:惰性刪除 + 定期刪除兩種策略進(jìn)行配合使用。
惰性刪除
惰性刪除指的是當(dāng)我們查詢key的時(shí)候????才對(duì)key進(jìn)行檢測(cè),如果已經(jīng)達(dá)到過期時(shí)間,則刪除。
優(yōu)點(diǎn):對(duì)CPU友好,只會(huì)在使用該key時(shí)才會(huì)進(jìn)行過期檢查,對(duì)于很多用不到的key不用浪費(fèi)時(shí)間進(jìn)行過期檢查。
缺點(diǎn):對(duì)內(nèi)存不友好,如果一個(gè)key已經(jīng)過期,但是一直沒有使用,那么該key就會(huì)一直存在內(nèi)存中,內(nèi)存永遠(yuǎn)不會(huì)釋放。
定期刪除
指的是redis默認(rèn)是每隔 100ms (10hz)就隨機(jī)抽取一些設(shè)置了過期時(shí)間的 key,檢查其是否過期,如果過期就刪除。
優(yōu)點(diǎn):可以通過限制刪除操作執(zhí)行的時(shí)長和頻率來減少刪除操作對(duì) CPU 的影響。另外定期刪除,也能有效釋放過期鍵占用的內(nèi)存。
缺點(diǎn):難以確定刪除操作執(zhí)行的時(shí)長和頻率。
七、Redis的數(shù)據(jù)淘汰策略有哪些?
如果某個(gè)key過期后,定期刪除沒刪除成功,然后也沒再次去請(qǐng)求key,也就是說惰性刪除也沒生效。 這時(shí),如果大量過期的key堆積在內(nèi)存中,redis的內(nèi)存會(huì)越來越高,導(dǎo)致redis的內(nèi)存塊耗盡。那么就應(yīng)該采用內(nèi)存淘汰機(jī)制。
Redis支持8種不同策略來選擇要?jiǎng)h除的key:
u? no-eviction:不淘汰任何key,但是內(nèi)存滿時(shí)不允許寫入新數(shù)據(jù),默認(rèn)就是這種策略。
u? volatile-ttl:對(duì)設(shè)置了TTL的key,比較key的剩余TTL值,TTL越小越先被淘汰。
u? allkeys-random:對(duì)全體key,隨機(jī)進(jìn)行淘汰。
u? volatile-random:對(duì)設(shè)置了TTL的key,隨機(jī)進(jìn)行淘汰。
u? allkeys-lru:對(duì)全體key,基于LRU(Least Recently Used,最近最久未使用)算法進(jìn)行淘汰。
u? volatile-lru:對(duì)設(shè)置了TTL的key,基于LRU算法進(jìn)行淘汰。
u? allkeys-lfu:對(duì)全體key,基于LFU(Least Frequently Used ,最近最少使用)算法進(jìn)行淘汰。
u? volatile-lfu:對(duì)設(shè)置了TTL的key,基于LFU算法進(jìn)行淘汰。
使用建議:
優(yōu)先使用 allkeys-lru 策略。充分利用 LRU 算法的優(yōu)勢(shì),把最近最常訪問的數(shù)據(jù)留在緩存中。如果業(yè)務(wù)有明顯的冷熱數(shù)據(jù)區(qū)分,建議使用。
如果業(yè)務(wù)中數(shù)據(jù)訪問頻率差別不大,沒有明顯冷熱數(shù)據(jù)區(qū)分,建議使用 allkeys-random,隨機(jī)選擇淘汰。
如果業(yè)務(wù)中有置頂?shù)男枨?,可以使?volatile-lru 策略,同時(shí)置頂數(shù)據(jù)不設(shè)置過期時(shí)間,這些數(shù)據(jù)就一直不被刪除,會(huì)淘汰其他設(shè)置過期時(shí)間的數(shù)據(jù)。
如果業(yè)務(wù)中有短時(shí)高頻訪問的數(shù)據(jù),可以使用 allkeys-lfu 或 volatile-lfu 策略。
八、Redis分布式鎖如何實(shí)現(xiàn)?
redis分布式鎖
redis是一個(gè)單獨(dú)的非業(yè)務(wù)服務(wù),不會(huì)受到其他業(yè)務(wù)服務(wù)的限制,所有的業(yè)務(wù)服務(wù)都可以向redis發(fā)送寫入命令, 且只有一個(gè)業(yè)務(wù)服務(wù)可以寫入命令成功,那么這個(gè)寫入命令成功的服務(wù)即獲得了鎖,可以進(jìn)行后續(xù)對(duì)資源的操作,其他未寫入成功的服務(wù),則進(jìn)行其他處理。
# 添加鎖,NX是互斥、EX是設(shè)置超時(shí)時(shí)間(設(shè)置超時(shí)時(shí)間避免死鎖,用一條命令保證原子性)
SET lock value NX EX 10
# 釋放鎖,刪除即可
DEL key
redisson實(shí)現(xiàn)的分布式鎖
①執(zhí)行流程
在redisson中需要手動(dòng)加鎖,并且可以控制鎖的失效時(shí)間和等待時(shí)間,當(dāng)鎖住的一個(gè)業(yè)務(wù)還沒有執(zhí)行完成的時(shí)候,在redisson中引入了一個(gè)看門狗機(jī)制,就是說每隔一段時(shí)間就檢查當(dāng)前業(yè)務(wù)是否還持有鎖,如果持有就增加加鎖的持有時(shí)間,當(dāng)業(yè)務(wù)執(zhí)行完成之后釋放鎖就可以了。
在高并發(fā)下,一個(gè)業(yè)務(wù)有可能會(huì)執(zhí)行很快,先客戶1持有鎖的時(shí)候,客戶2來了以后并不會(huì)馬上拒絕,它會(huì)自旋不斷嘗試獲取鎖,如果客戶1釋放之后,客戶2就可以馬上持有鎖,性能也得到了提升。


②可重入
在內(nèi)部就是判斷是否是當(dāng)前線程持有的鎖,如果是當(dāng)前線程持有的鎖就會(huì)計(jì)數(shù),如果釋放鎖就會(huì)在計(jì)算上減1。在存儲(chǔ)數(shù)據(jù)的時(shí)候采用的hash結(jié)構(gòu),大key可以按照自己的業(yè)務(wù)進(jìn)行定制,其中field是當(dāng)前線程的唯一標(biāo)識(shí),小value是當(dāng)前線程重入的次數(shù)

?③主從一致性
redis集群中主從數(shù)據(jù)并不是強(qiáng)一致性。 當(dāng)主節(jié)點(diǎn)宕機(jī)后,主節(jié)點(diǎn)的數(shù)據(jù)還未來得及同步到從節(jié)點(diǎn),進(jìn)行主從切換后,新的主節(jié)點(diǎn)并沒有舊的主節(jié)點(diǎn)的全部數(shù)據(jù),這就會(huì)導(dǎo)致剛寫入到舊的主節(jié)點(diǎn)的鎖在新的主節(jié)點(diǎn)并沒有,其他服務(wù)來獲取鎖時(shí)還是會(huì)加鎖成功。此時(shí)則會(huì)有2個(gè)服務(wù)都可以操作公共資源,此時(shí)的分布式鎖是不安全的。
RedLock:要實(shí)現(xiàn)高可用的RedLock,不能只在一個(gè)redis實(shí)例上創(chuàng)建鎖,應(yīng)該是在多個(gè)redis實(shí)例(n / 2 + 1)上創(chuàng)建鎖。高可用的RedLock會(huì)導(dǎo)致性能降低,不太建議使用RedLock。
使用redis分布式鎖,是追求高性能,在CAP理論中,追求的是AP而不是CP。所以,如果追求高可用,建議使用 zookeeper分布式鎖。
九、Redis集群方案之主從同步
全量同步
一般用于初次同步場(chǎng)景,Redis早期支持的復(fù)制功能只有全量復(fù)制,它會(huì)把主節(jié)點(diǎn)全部數(shù)據(jù)一次性發(fā)送給從節(jié)點(diǎn),當(dāng)數(shù)據(jù)量較大時(shí),會(huì)對(duì)主從節(jié)點(diǎn)和網(wǎng)絡(luò)造成很大的開銷。
Replication Id:簡(jiǎn)稱replid,是數(shù)據(jù)集的標(biāo)記,id一致則說明是同一數(shù)據(jù)集。每一個(gè)master都有唯一的replid,slave則會(huì)繼承master節(jié)點(diǎn)的replid。
offset:偏移量,隨著記錄在repl_baklog中的數(shù)據(jù)增多而逐漸增大。slave完成同步時(shí)也會(huì)記錄當(dāng)前同步的offset。如果slave的offset小于master的offset,說明slave數(shù)據(jù)落后于master,需要更新。
因此slave做數(shù)據(jù)同步,必須向master聲明自己的replication id 和offset,master才可以判斷到底需要同步哪些數(shù)據(jù)。

增量同步
使用psync命令實(shí)現(xiàn)。當(dāng)從節(jié)點(diǎn)正在復(fù)制主節(jié)點(diǎn)時(shí),如果出現(xiàn)網(wǎng)絡(luò)閃斷或者命令丟失等異常情況時(shí),從節(jié)點(diǎn)會(huì)向主節(jié)點(diǎn)要求補(bǔ)發(fā)丟失的命令數(shù)據(jù),如果主節(jié)點(diǎn)的復(fù)制積壓緩沖區(qū)內(nèi)存在這部分?jǐn)?shù)據(jù)則直接發(fā)送給從節(jié)點(diǎn),這樣就可以保持主從節(jié)點(diǎn)復(fù)制的一致性。

十、Redis集群方案之哨兵模式
哨兵模式
Redis提供了哨兵(Sentinel)機(jī)制來實(shí)現(xiàn)主從集群的自動(dòng)故障恢復(fù)。哨兵的結(jié)構(gòu)和作用如下:

監(jiān)控:Sentinel 會(huì)不斷檢查您的master和slave是否按預(yù)期工作。
1、每隔1秒發(fā)送一次ping命令,如果超過一定時(shí)間沒有相向則認(rèn)為是主觀下線。
2、如果大多數(shù)sentinel都認(rèn)為實(shí)例主觀下線,則判定服務(wù)下線。
自動(dòng)故障恢復(fù):如果master故障,Sentinel會(huì)將一個(gè)slave提升為master。當(dāng)故障實(shí)例恢復(fù)后也以新的master為主。
1、首先選定一個(gè)slave作為新的master,執(zhí)行slaveof no one
2、然后讓所有節(jié)點(diǎn)都執(zhí)行slaveof 新master
3、修改故障節(jié)點(diǎn),執(zhí)行slaveof 新master
通知:Sentinel充當(dāng)Redis客戶端的服務(wù)發(fā)現(xiàn)來源,當(dāng)集群發(fā)生故障轉(zhuǎn)移時(shí),會(huì)將最新信息推送給Redis的客戶端。
腦裂
在一個(gè)高可用集群中,當(dāng)多個(gè)服務(wù)器在指定的時(shí)間內(nèi),由于網(wǎng)絡(luò)的原因無法互相檢測(cè)到對(duì)方,而各自形成一個(gè)新的小規(guī)模集群,并且各小集群當(dāng)中,會(huì)選舉新的master節(jié)點(diǎn),都對(duì)外提供獨(dú)立的服務(wù),由于網(wǎng)絡(luò)斷裂的原因,一個(gè)高可用集群中,實(shí)際上分裂為多個(gè)小的集群,這種情況就稱為腦裂。


十一、Redis集群方案之分片集群
主從和哨兵可以解決高可用、高并發(fā)讀的問題。但是依然有兩個(gè)問題沒有解決:
???????? 海量數(shù)據(jù)存儲(chǔ)問題
???????? 高并發(fā)寫的問題
使用分片集群可以解決上述問題,分片集群特征:
???????? 集群中有多個(gè)master,每個(gè)master保存不同數(shù)據(jù)
???????? 每個(gè)master都可以有多個(gè)slave節(jié)點(diǎn)
???????? master之間通過ping監(jiān)測(cè)彼此健康狀態(tài)
???????? 客戶端請(qǐng)求可以訪問集群任意節(jié)點(diǎn),最終都會(huì)被轉(zhuǎn)發(fā)到正確節(jié)點(diǎn)

散列插槽
Redis會(huì)把每一個(gè)master節(jié)點(diǎn)映射到0~16383共16384個(gè)插槽上。每個(gè)key的有效部分通過CRC16校驗(yàn)后對(duì)16384取模來決定放置哪個(gè)槽,集群的每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分hash 槽。


集群伸縮
Redis集群提供了靈活的節(jié)點(diǎn)擴(kuò)容和收縮方案,可以在不影響集群對(duì)外服務(wù)的情況下,為集群添加節(jié)點(diǎn)進(jìn)行擴(kuò)容也可以下線部分節(jié)點(diǎn)進(jìn)行縮容。

其實(shí),集群擴(kuò)容和縮容的關(guān)鍵點(diǎn),就在于槽和節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系,擴(kuò)容和縮容就是將一部分槽和數(shù)據(jù)遷移給新節(jié)點(diǎn)。
故障轉(zhuǎn)移
Redis集群的故障轉(zhuǎn)移和哨兵的故障轉(zhuǎn)移類似,但是Redis集群中所有的節(jié)點(diǎn)都要承擔(dān)狀態(tài)維護(hù)的任務(wù)。
故障發(fā)現(xiàn)
Redis集群內(nèi)節(jié)點(diǎn)通過ping/pong消息實(shí)現(xiàn)節(jié)點(diǎn)通信,集群中每個(gè)節(jié)點(diǎn)都會(huì)定期向其他節(jié)點(diǎn)發(fā)送ping消息,接收節(jié)點(diǎn)回復(fù)pong 消息作為響應(yīng)。如果在cluster-node-timeout時(shí)間內(nèi)通信一直失敗,則發(fā)送節(jié)點(diǎn)會(huì)認(rèn)為接收節(jié)點(diǎn)存在故障,把接收節(jié)點(diǎn)標(biāo)記為主觀下線(pfail)狀態(tài)。

當(dāng)某個(gè)節(jié)點(diǎn)判斷另一個(gè)節(jié)點(diǎn)主觀下線后,相應(yīng)的節(jié)點(diǎn)狀態(tài)會(huì)跟隨消息在集群內(nèi)傳播。通過Gossip消息傳播,集群內(nèi)節(jié)點(diǎn)不斷收集到故障節(jié)點(diǎn)的下線報(bào)告。當(dāng)半數(shù)以上持有槽的主節(jié)點(diǎn)都標(biāo)記某個(gè)節(jié)點(diǎn)是主觀下線時(shí)。觸發(fā)客觀下線流程。

故障恢復(fù)
故障節(jié)點(diǎn)變?yōu)榭陀^下線后,如果下線節(jié)點(diǎn)是持有槽的主節(jié)點(diǎn)則需要在它的從節(jié)點(diǎn)中選出一個(gè)替換它,從而保證集群的高可用。
故障恢復(fù)流程
1. 資格檢查:每個(gè)從節(jié)點(diǎn)都要檢查最后與主節(jié)點(diǎn)斷線時(shí)間,判斷是否有資格替換故障的主節(jié)點(diǎn)。
2. 準(zhǔn)備選舉時(shí)間:當(dāng)從節(jié)點(diǎn)符合故障轉(zhuǎn)移資格后,更新觸發(fā)故障選舉的時(shí)間,只有到達(dá)該時(shí)間后才能執(zhí)行后續(xù)流程。
3. 發(fā)起選舉:當(dāng)從節(jié)點(diǎn)定時(shí)任務(wù)檢測(cè)到達(dá)故障選舉時(shí)間(failover_auth_time)到達(dá)后,發(fā)起選舉流程。
4. 選舉投票:持有槽的主節(jié)點(diǎn)處理故障選舉消息。投票過程其實(shí)是一個(gè)領(lǐng)導(dǎo)者選舉的過程,如集群內(nèi) 有N個(gè)持有槽的主節(jié)點(diǎn)代表有N張選票。由于在每個(gè)配置紀(jì)元內(nèi)持有槽的主節(jié)點(diǎn)只能投票給一個(gè)從節(jié)點(diǎn),因此只能有一個(gè)從節(jié)點(diǎn)獲得N/2+1的選票,保證能夠找出唯一的從節(jié)點(diǎn)。

5. 替換主節(jié)點(diǎn) 當(dāng)從節(jié)點(diǎn)收集到足夠的選票之后,觸發(fā)替換主節(jié)點(diǎn)操作。
十二、Redis是單線程的,但是為什么還那么快?

基于內(nèi)存
Redis是基于內(nèi)存的緩存數(shù)據(jù)庫,用于存儲(chǔ)使用頻繁的數(shù)據(jù),這樣減少訪問數(shù)據(jù)庫的次數(shù),提高運(yùn)行效率。
單線程避免線程切換
多線程在執(zhí)行過程中需要進(jìn)行CPU的上下文切換,需要完成一系列工作,這是非常消耗資源的操作,Redis又是基于內(nèi)存實(shí)現(xiàn)的,對(duì)于內(nèi)存來說,沒有上下文切換效率就是最高的。
IO多路復(fù)?
Redis采用網(wǎng)絡(luò)IO多路復(fù)用技術(shù),來保證在多連接的時(shí)候系統(tǒng)的高吞吐量。
C語?實(shí)現(xiàn)高效的數(shù)據(jù)結(jié)構(gòu)
Redis底層數(shù)據(jù)結(jié)構(gòu)一共有6種:簡(jiǎn)單動(dòng)態(tài)字符串,雙向鏈表,壓縮列表,哈希表,跳表和整數(shù)數(shù)組。它們和數(shù)據(jù)類型的對(duì)應(yīng)關(guān)系如下圖所示:

十三、能解釋一下I/O多路復(fù)用模型?
多路指的是多個(gè)socket網(wǎng)絡(luò)連接,復(fù)用指的是復(fù)用一個(gè)線程。多路復(fù)用主要有三種技術(shù):select,poll,epoll。epoll是最新的、也是目前最好的多路復(fù)用技術(shù)。采用多路I/O復(fù)用技術(shù),可以讓單個(gè)線程高效處理多個(gè)連接請(qǐng)求。
Redis內(nèi)部使用文件事件處理器(File Event Handler),這個(gè)文件事件處理器是單線程的,類似于Netty的Reactor反應(yīng)器模型。 它采用 IO 多路復(fù)用機(jī)制同時(shí)監(jiān)聽多個(gè) socket,將產(chǎn)生事件的 socket 壓入內(nèi)存隊(duì)列中,事件分派器根據(jù) socket 上的事件類型來選擇對(duì)應(yīng)的事件處理器進(jìn)行處理。 文件事件處理器的結(jié)構(gòu)包含4個(gè)部分:
l? 多個(gè)socket
l? IO多路復(fù)用程序
l? 文件事件分派器
l? 事件處理器(連接應(yīng)答處理器、命令請(qǐng)求處理器、命令回復(fù)處理器)
多個(gè)socket可能會(huì)并發(fā)產(chǎn)生不同的操作,每個(gè)操作對(duì)應(yīng)不同的文件事件,但是 IO 多路復(fù)用程序會(huì)監(jiān)聽多個(gè)socket,會(huì)將產(chǎn)生事件的 socket 放入隊(duì)列中排隊(duì),事件分派器每次從隊(duì)列中取出一個(gè) socket,根據(jù)socket的事件類型交給對(duì)應(yīng)的事件處理器進(jìn)行處理。

在Redis6.0之后,為了提升更好的性能,在命令回復(fù)處理器(請(qǐng)求后的數(shù)據(jù)通過網(wǎng)絡(luò)返回)使用了多線程來處理回復(fù)事件,在命令請(qǐng)求處理器中(網(wǎng)絡(luò)請(qǐng)求的接收和解析),將命令的轉(zhuǎn)換使用了多線程,增加命令轉(zhuǎn)換速度,在命令執(zhí)行的時(shí)候,依然是單線程。