柚子快報激活碼778899分享:面試 緩存 最全Redis原理
柚子快報激活碼778899分享:面試 緩存 最全Redis原理
什么是 Redis?
Redis 是完全開源免費的,遵守 BSD 協(xié)議,是一個高性能的 key-value 數(shù)據(jù)庫。
Redis 與其他 key - value 緩存產(chǎn)品相比有以下三個特點:
Redis 支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保存在磁盤中,重啟的時候可以再次加載進行使用。
Redis 不僅僅支持簡單的 key-value 類型的數(shù)據(jù),同時還提供 list,set,zset,hash 等數(shù)據(jù)結(jié)構(gòu)的存儲。
Redis 支持?jǐn)?shù)據(jù)的備份,即 master-slave 模式的數(shù)據(jù)備份。
Redis 優(yōu)勢:
性能極高:Redis 能讀的速度是 110000 次/s,寫的速度是 81000 次/s。
豐富的數(shù)據(jù)類型:Redis 支持二進制案例的 Strings,Lists,Hashes,Sets 及 Ordered Sets 數(shù)據(jù)類型操作。
原子:Redis 的所有操作都是原子性的,意思就是要么成功執(zhí)行要么失敗完全不執(zhí)行。單個操作是原子性的。多個操作也支持事務(wù),即原子性,通過 MULTI 和 EXEC 指令包起來。
豐富的特性:Redis 還支持 publish/subscribe,通知,key 過期等等特性。
Redis 與其他 key-value 存儲有什么不同?
Redis 有著更為復(fù)雜的數(shù)據(jù)結(jié)構(gòu)并且提供對他們的原子性操作,這是一個不同于其他數(shù)據(jù)庫的進化路徑。Redis 的數(shù)據(jù)類型都是基于基本數(shù)據(jù)結(jié)構(gòu)的同時對程序員透明,無需進行額外的抽象。
Redis 運行在內(nèi)存中但是可以持久化到磁盤,所以在對不同數(shù)據(jù)集進行高速讀寫時需要權(quán)衡內(nèi)存,因為數(shù)據(jù)量不能大于硬件內(nèi)存。在內(nèi)存數(shù)據(jù)庫方面的另一個優(yōu)點是,相比在磁盤上相同的復(fù)雜的數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中操作起來非常簡單,這樣 Redis 可以做很多內(nèi)部復(fù)雜性很強的事情。同時,在磁盤格式方面他們是緊湊的以追加的方式產(chǎn)生的,因為他們并不需要進行隨機訪問。
Redis 的數(shù)據(jù)類型?
1.Redis 支持五種數(shù)據(jù)類型:
string(字符串),
hash(哈希),
list(列表),
set(集合),
zsetsorted set(有序集合)
2.有哪些應(yīng)用場景?
1)String:緩存、限流、分布式鎖、計數(shù)器、分布式 Session 等。
2)Hash:用戶信息、用戶主頁訪問量、組合查詢等。
3)List:簡單隊列、關(guān)注列表時間軸。
4)Set:贊、踩、標(biāo)簽等。
ZSet:排行榜、好友關(guān)系鏈表。
3.常用命令
終端連接
`redis-cli -h 127.0.0.1 -p 6379`
key
keys * # 獲取所有的key
select 0 # 選擇第一個庫
move myString 1 # 將當(dāng)前的數(shù)據(jù)庫key移動到某個數(shù)據(jù)庫,目標(biāo)庫有,則不能移動
flush db # 清除指定庫
randomkey # 隨機key
type key # 類型
set key1 value1 # 設(shè)置key
get key1 # 獲取key
mset key1 value1 key2 value2 key3 value3
mget key1 key2 key3
del key1 # 刪除key
exists key # 判斷是否存在key
expire key 10 # 10s 過期
pexpire key # 1000 毫秒
persist key # 刪除過期時間
訂閱發(fā)布
subscribe chat1 # 訂閱頻道
publish chat1 "hell0 ni hao" # 發(fā)布消息
pubsub channels # 查看頻道
pubsub numsub chat1 # 查看某個頻道的訂閱者數(shù)量
unsubscrible chat1 # 退訂指定頻道 或 punsubscribe java.*
psubscribe java.* # 訂閱一組頻道
ref redis常用命令大全
使用 Redis 有哪些好處?
速度快,因為數(shù)據(jù)存在內(nèi)存中,類似于 HashMap,HashMap 的優(yōu)勢就是查找和操作的時間復(fù)雜度都是 O1)
支持豐富數(shù)據(jù)類型,支持 string,list,set,Zset,hash 等
支持事務(wù),操作都是原子性,所謂的原子性就是對數(shù)據(jù)的更改要么全部執(zhí)行,要么全部不執(zhí)行
豐富的特性,可用于緩存,消息,按 key 設(shè)置過期時間,過期后將會自動刪除
Redis 與Memcached?
存儲方式 Memecache 把數(shù)據(jù)全部存在內(nèi)存之中,斷電后會掛掉,數(shù)據(jù)不能超過內(nèi)存大小。Redis 有部分存在硬盤上,這樣能保證數(shù)據(jù)的持久性。
數(shù)據(jù)支持類型 Memcache 對數(shù)據(jù)類型支持相對簡單。Redis 有復(fù)雜的數(shù)據(jù)類型。
使用底層模型不同 它們之間底層實現(xiàn)方式 以及與客戶端之間通信的應(yīng)用協(xié)議不一樣。Redis 直接自己構(gòu)建了 VM 機制 ,因為一般的系統(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會浪費一定的時間去移動和請求。
Redis 線程?
為何使用單線程?
官方答案
因為 Redis 是基于內(nèi)存的操作,CPU 不會成為 Redis 的瓶頸,而最有可能是機器內(nèi)存的大小或者網(wǎng)絡(luò)帶寬。既然單線程容易實現(xiàn),而且 CPU 不會成為瓶頸,那就順理成章地采用單線程的方案了。
詳細(xì)原因
1)不需要各種鎖的性能消耗
Redis 的數(shù)據(jù)結(jié)構(gòu)并不全是簡單的 Key-Value,還有 List,Hash 等復(fù)雜的結(jié)構(gòu),這些結(jié)構(gòu)有可能會進行很細(xì)粒度的操作,比如在很長的列表后面添加一個元素,在hash當(dāng)中添加或者刪除一個對象。這些操作可能就需要加非常多的鎖,導(dǎo)致的結(jié)果是同步開銷大大增加。
2)單線程多進程集群方案
單線程的威力實際上非常強大,每核心效率也非常高,多線程自然是可以比單線程有更高的性能上限,但是在今天的計算環(huán)境中,即使是單機多線程的上限也往往不能滿足需要了,需要進一步摸索的是多服務(wù)器集群化的方案,這些方案中多線程的技術(shù)照樣是用不上的。
所以單線程、多進程的集群不失為一個時髦的解決方案。
多線程
1.Redis 6 之前真的是單線程嗎?
Redis 在處理客戶端的請求時,包括獲取 (socket 讀)、解析、執(zhí)行、內(nèi)容返回 (socket 寫) 等都由一個順序串行的主線程處理,這就是所謂的單線程。但如果嚴(yán)格來講從 Redis 4 之后并不是單線程,除了主線程外,它也有后臺線程在處理一些較為緩慢的操作,例如清理臟數(shù)據(jù)、無用連接的釋放、大 key 的刪除等等。
2.Redis 6 之前為什么使用單線程?
使用了單線程后,可維護性高。多線程模型雖然在某些方面表現(xiàn)優(yōu)異,但是它卻引入了程序執(zhí)行順序的不確定性,帶來了并發(fā)讀寫的一系列問題,增加了系統(tǒng)復(fù)雜度、同時可能存在線程切換、甚至加鎖解鎖、死鎖造成的性能損耗。
同時 Redis 通過 AE 事件模型以及 IO 多路復(fù)用等技術(shù),即使單線程處理性能也非常高,因此沒有必要使用多線程。單線程機制使得 Redis 內(nèi)部實現(xiàn)的復(fù)雜度大大降低,Hash 的惰性 Rehash、Lpush 等等 “線程不安全” 的命令都可以無鎖進行。
3.Redis 6 為何引入多線程?
隨著目前行業(yè)內(nèi)越來越復(fù)雜的業(yè)務(wù)場景,有些公司動不動就上億的交易量,因此需要更大的 QPS。常見的解決方案是在分布式架構(gòu)中對數(shù)據(jù)進行分區(qū)并采用多個服務(wù)器,但該方案有非常大的缺點,比如:
1)要管理的 Redis 服務(wù)器太多,維護代價大;
2)某些適用于單個 Redis 服務(wù)器的命令不適用于數(shù)據(jù)分區(qū);
3)數(shù)據(jù)分區(qū)無法解決熱點讀/寫問題;
4)數(shù)據(jù)偏斜,重新分配和放大/縮小變得更加復(fù)雜等等。
從 Redis 自身角度來說,因為讀寫網(wǎng)絡(luò)的 read/write 系統(tǒng)調(diào)用占用了 Redis 執(zhí)行期間大部分 CPU 時間,瓶頸主要在于網(wǎng)絡(luò)的 IO 消耗, 優(yōu)化主要有兩個方向:
1)提高網(wǎng)絡(luò) IO 性能,典型的實現(xiàn)比如使用 DPDK 來替代內(nèi)核網(wǎng)絡(luò)棧的方式;
2)使用多線程充分利用多核,典型的實現(xiàn)比如 Memcached。
協(xié)議棧優(yōu)化的這種方式跟 Redis 關(guān)系不大,支持多線程是一種最有效最便捷的操作方式。所以總結(jié)起來,Redis 支持多線程主要就是兩個原因:
可以充分利用服務(wù)器 CPU 資源,目前主線程只能利用一個核;
多線程任務(wù)可以分?jǐn)?Redis 同步 IO 讀寫負(fù)荷。
4.多線程如何開啟以及配置?
Redis 6 的多線程默認(rèn)是禁用的,只使用主線程。如需開啟需要修改 redis.conf 配置文件中的 io-threads-do-reads yes。
開啟多線程后,還需要設(shè)置線程數(shù),否則是不生效的。同樣修改 redis.conf 文件中的 io-threads [n] 配置。
關(guān)于線程數(shù)的設(shè)置,官方有一個建議:4 核的機器建議設(shè)置為 2 或 3 個線程,8 核的建議設(shè)置為 6 個線程,線程數(shù)一定要小于機器核數(shù)。還需要注意的是,線程數(shù)并不是越大越好,官方認(rèn)為超過了 8 個基本就沒什么意義了。
5.Redis 多線程的實現(xiàn)機制?
大致流程如下:
1)主線程負(fù)責(zé)接收建立連接請求,獲取 socket 放入全局等待讀處理隊列;
2)主線程處理完讀事件之后,通過 RR(Round Robin) 將這些連接分配給這些 IO 線程;
3)主線程阻塞等待 IO 線程讀取 socket 完畢;
4)主線程通過單線程的方式執(zhí)行請求命令,請求數(shù)據(jù)讀取并解析完成,但并不執(zhí)行;
5)主線程阻塞等待 IO 線程將數(shù)據(jù)回寫 socket 完畢;
6)解除綁定,清空等待隊列。
該設(shè)計的特點:
1)IO 線程要么同時在讀 socket,要么同時在寫,不會同時讀或?qū)憽?/p>
2)IO 線程只負(fù)責(zé)讀寫 socket 解析命令,不負(fù)責(zé)命令處理。
6.多線程是否會導(dǎo)致線程安全問題?
從上面的實現(xiàn)機制可以看出,Redis 的多線程部分只是用來處理網(wǎng)絡(luò)數(shù)據(jù)的讀寫和協(xié)議解析,執(zhí)行命令仍然是單線程順序執(zhí)行。所以我們不需要去考慮控制 key、lua、事務(wù),LPUSH/LPOP 等等的并發(fā)及線程安全問題。
7.Redis 和 Memcached 多線程區(qū)別?
相同點:都采用了 master 線程 - worker 線程的模型。
不同點:Memcached 執(zhí)行主邏輯也是在 worker 線程里,模型更加簡單,實現(xiàn)了真正的線程隔離,符合我們對線程隔離的常規(guī)理解。而 Redis 把處理邏輯交還給 master 線程,雖然一定程度上增加了模型復(fù)雜度,但也解決了線程并發(fā)安全等問題。
ref Redis 6.0 新特性-多線程連環(huán)13問!
Redis集群?
1. 集群模式
1.Redis 集群搭建有幾種模式?
主從模式
和 MySQL 需要主從復(fù)制的原因一樣,Redis 雖然讀寫速度非???,但是也會產(chǎn)生性能瓶頸,特別是在讀壓力上,為了分擔(dān)壓力,Redis 支持主從復(fù)制。Redis 的主從結(jié)構(gòu)一主一從,一主多從或級聯(lián)結(jié)構(gòu),復(fù)制類型可以根據(jù)是否是全量而分為全量同步和增量同步。
哨兵模式
在主從復(fù)制實現(xiàn)之后,如果想對 master 進行監(jiān)控,Redis 提供了一種哨兵機制,哨兵的含義就是監(jiān)控 Redis 系統(tǒng)的運行狀態(tài),通過投票機制,從 slave 中選舉出新的 master 以保證集群正常運行。
還可以啟用多個哨兵進行監(jiān)控以保證集群足夠穩(wěn)健,這種情況下,哨兵不僅監(jiān)控主從服務(wù),哨兵之間也會相互監(jiān)控。
Cluster 集群模式
ref Redis集群搭建的三種方式
2.Redis 主從復(fù)制的實現(xiàn)?
主從復(fù)制可以根據(jù)需要分為全量同步的增量同步兩種方式。
全量同步
Redis 全量復(fù)制一般發(fā)生在 slave 的初始階段,這時 slave 需要將 master 上的數(shù)據(jù)都復(fù)制一份,具體步驟如下:
1)slave 連接 master,發(fā)送 SYNC 命令;
2)master 接到 SYNC 命令后執(zhí)行 BGSAVE 命令生產(chǎn) RDB 文件,并使用緩沖區(qū)記錄此后執(zhí)行的所有寫命令;
3)master 執(zhí)行完 BGSAVE 后,向所有的 slave 發(fā)送快照文件,并在發(fā)送過程中繼續(xù)記錄執(zhí)行的寫命令;
4)slave 收到快照后,丟棄所有的舊數(shù)據(jù),載入收到的數(shù)據(jù);
5)master 快照發(fā)送完成后就會開始向 slave 發(fā)送緩沖區(qū)的寫命令;
6)slave 完成對快照的載入,并開始接受命令請求,執(zhí)行來自 master 緩沖區(qū)的寫命令;
7)slave 完成上面的數(shù)據(jù)初始化后就可以開始接受用戶的讀請求了。
增量同步
增量復(fù)制實際上就是在 slave 初始化完成后開始正常工作時 master 發(fā)生寫操作同步到 slave 的過程。增量復(fù)制的過程主要是 master 每執(zhí)行一個寫命令就會向 slave 發(fā)送相同的寫命令,slave 接受并執(zhí)行寫命令,從而保持主從一致。
ref Redis主從復(fù)制原理總結(jié)
3.Redis 的主從同步策略?
主從同步剛連接的時候進行全量同步,全量同步結(jié)束后開始增量同步。
如果有需要,slave 在任何時候都可以發(fā)起全量同步,其主要策略就是無論如何首先會嘗試進行增量同步,如果失敗則會要求 slave 進行全量同步,之后再進行增量同步。
注意:如果多個 slave 同時斷線需要重啟的時候,因為只要 slave 啟動,就會和 master 建立連接發(fā)送SYNC請求和主機全量同步,如果多個同時發(fā)送 SYNC 請求,可能導(dǎo)致 master IO 突增而發(fā)送宕機。所以我們要避免多個 slave 同時恢復(fù)重啟的情況。
4.哨兵模式的原理?
哨兵主要用于管理多個 Redis 服務(wù)器,主要有以下三個任務(wù):監(jiān)控、提醒以及故障轉(zhuǎn)移。
每個哨兵會向其它哨兵、master、slave 定時發(fā)送消息,以確認(rèn)對方是否還存活。如果發(fā)現(xiàn)對方在配置的指定時間內(nèi)未回應(yīng),則暫時認(rèn)為對方已掛。若“哨兵群”中的多數(shù) sentinel 都報告某一 master 沒響應(yīng),系統(tǒng)才認(rèn)為該 master “徹底死亡”,通過一定的 vote 算法從剩下的 slave 節(jié)點中選一臺提升為 master,然后自動修改相關(guān)配置。
5.哨兵模式故障遷移流程?
1)首先是從主服務(wù)器的從服務(wù)器中選出一個從服務(wù)器作為新的主服務(wù)器。
選點的依據(jù)依次是:
網(wǎng)絡(luò)連接正常 -> 5 秒內(nèi)回復(fù)過 INFO 命令 -> 10*down-after-milliseconds 內(nèi)與主連接過的 -> 從服務(wù)器優(yōu)先級 -> 復(fù)制偏移量 -> 運行id較小的。
2)選出之后通過 slaveif no ont 將該從服務(wù)器升為新主服務(wù)器;
3)然后再通過 slaveof ip port 命令讓其他從服務(wù)器復(fù)制該信主服務(wù)器。
缺點
主從服務(wù)器的數(shù)據(jù)要經(jīng)常進行主從復(fù)制,這樣會造成性能下降
當(dāng)主服務(wù)器宕機后,從服務(wù)器切換成主服務(wù)器的那段時間,服務(wù)是不可用的
2.2 Cluster 集群
1、什么是一致性 Hash 以及解決什么問題?
一致性 hash 其實是普通 hash 算法的改良版,其 hash 計算方法沒有變化,但是 hash 空間發(fā)生了變化,由原來的線性的變成了環(huán)。
緩存 key 通過 hash 計算之后得到在 hash 環(huán)中的位置,然后順時針方向找到第一個節(jié)點,這個節(jié)點就是存放 key 的節(jié)點。
由此可見,一致性 hash 主要是為了解決普通 hash 中擴容和宕機的問題。
同時還可以通過虛擬節(jié)點來解決數(shù)據(jù)傾斜的問題:就是在節(jié)點稀疏的 hash 環(huán)上對物理節(jié)點虛擬出一部分虛擬節(jié)點,key 會打到虛擬節(jié)點上面,而虛擬節(jié)點上的 key 實際也是映射到物理節(jié)點上的,這樣就避免了數(shù)據(jù)傾斜導(dǎo)致單節(jié)點壓力過大導(dǎo)致節(jié)點雪崩的問題。
ref 什么是一致性hash
2.Cluster 模式的原理?
其實現(xiàn)原理就是一致性 Hash。Redis Cluster 中有一個 16384 長度的槽的概念,他們的編號為 0、1、2、3 …… 16382、16383。這個槽是一個虛擬的槽,并不是真正存在的。正常工作的時候,Redis Cluster 中的每個 Master 節(jié)點都會負(fù)責(zé)一部分的槽,當(dāng)有某個 key 被映射到某個 Master 負(fù)責(zé)的槽,那么這個 Master 負(fù)責(zé)為這個 key 提供服務(wù)。
至于哪個 Master 節(jié)點負(fù)責(zé)哪個槽,這是可以由用戶指定的,也可以在初始化的時候自動生成(redis-trib.rb腳本)。這里值得一提的是,在 Redis Cluster 中,只有 Master 才擁有槽的所有權(quán),如果是某個 Master 的 slave,這個slave只負(fù)責(zé)槽的使用,但是沒有所有權(quán)。
3.Cluster 的分片機制?
為了使得集群能夠水平擴展,首要解決的問題就是如何將整個數(shù)據(jù)集按照一定的規(guī)則分配到多個節(jié)點上。對于客戶端請求的 key,根據(jù)公式 HASH_SLOT=CRC16(key) mod 16384,計算出映射到哪個分片上。而對于 CRC16 算法產(chǎn)生的 hash 值會有 16bit,可以產(chǎn)生 2^16-=65536 個值。
Redis 集群提供了靈活的節(jié)點擴容和收縮方案。在不影響集群對外服務(wù)的情況下,可以為集群添加節(jié)點進行擴容也可以下線部分節(jié)點進行縮容??梢哉f,槽是 Redis 集群管理數(shù)據(jù)的基本單位,集群伸縮就是槽和數(shù)據(jù)在節(jié)點之間的移動。
4、Cluster 集群的擴容流程?
當(dāng)一個 Redis 新節(jié)點運行并加入現(xiàn)有集群后,我們需要為其遷移槽和數(shù)據(jù)。首先要為新節(jié)點指定槽的遷移計劃,確保遷移后每個節(jié)點負(fù)責(zé)相似數(shù)量的槽,從而保證這些節(jié)點的數(shù)據(jù)均勻。
1)首先啟動一個 Redis 節(jié)點,記為 M4。
2)使用 cluster meet 命令,讓新 Redis 節(jié)點加入到集群中。新節(jié)點剛開始都是主節(jié)點狀態(tài),由于沒有負(fù)責(zé)的槽,所以不能接受任何讀寫操作,后續(xù)給他遷移槽和填充數(shù)據(jù)。
3)對 M4 節(jié)點發(fā)送 cluster setslot { slot } importing { sourceNodeId } 命令,讓目標(biāo)節(jié)點準(zhǔn)備導(dǎo)入槽的數(shù)據(jù)。
4)對源節(jié)點,也就是 M1,M2,M3 節(jié)點發(fā)送 cluster setslot { slot } migrating { targetNodeId } 命令,讓源節(jié)點準(zhǔn)備遷出槽的數(shù)據(jù)。
5)源節(jié)點執(zhí)行 cluster getkeysinslot { slot } { count } 命令,獲取 count 個屬于槽 { slot } 的鍵,然后執(zhí)行步驟 6)的操作進行遷移鍵值數(shù)據(jù)。
6)在源節(jié)點上執(zhí)行 migrate { targetNodeIp} " " 0 { timeout } keys { key... } 命令,把獲取的鍵通過 pipeline 機制批量遷移到目標(biāo)節(jié)點,批量遷移版本的 migrate 命令在 Redis 3.0.6 以上版本提供。
7)重復(fù)執(zhí)行步驟 5)和步驟 6)直到槽下所有的鍵值數(shù)據(jù)遷移到目標(biāo)節(jié)點。
8)向集群內(nèi)所有主節(jié)點發(fā)送 cluster setslot { slot } node { targetNodeId } 命令,通知槽分配給目標(biāo)節(jié)點。為了保證槽節(jié)點映射變更及時傳播,需要遍歷發(fā)送給所有主節(jié)點更新被遷移的槽執(zhí)行新節(jié)點。
5、Cluster 集群收縮流程?
收縮節(jié)點就是將 Redis 節(jié)點下線,整個流程需要如下操作流程。
1)首先需要確認(rèn)下線節(jié)點是否有負(fù)責(zé)的槽,如果是,需要把槽遷移到其他節(jié)點,保證節(jié)點下線后整個集群槽節(jié)點映射的完整性。
2)當(dāng)下線節(jié)點不再負(fù)責(zé)槽或者本身是從節(jié)點時,就可以通知集群內(nèi)其他節(jié)點忘記下線節(jié)點,當(dāng)所有的節(jié)點忘記改節(jié)點后可以正常關(guān)閉。
ref Redis Cluster數(shù)據(jù)分片機制
6、客戶端如何路由?
既然 Redis 集群中的數(shù)據(jù)是分片存儲的,那我們該如何知道某個 key 存在哪個節(jié)點上呢?即我們需要一個查詢路由,該路由根據(jù)給定的 key,返回存儲該鍵值的機器地址。
常規(guī)的實現(xiàn)方式便是采用如下圖所示的代理方案,即采用一個中央節(jié)點(比如HDFS中的NameNode)來管理所有的元數(shù)據(jù),但是這樣的方案帶來的最大問題就是代理節(jié)點很容易成為訪問的瓶頸,當(dāng)讀寫并發(fā)量高的時候,代理節(jié)點會嚴(yán)重的拖慢整個系統(tǒng)的性能。
Redis 并沒有選擇使用代理,而是客戶端直接連接每個節(jié)點。Redis 的每個節(jié)點中都存儲著整個集群的狀態(tài),集群狀態(tài)中一個重要的信息就是每個桶的負(fù)責(zé)節(jié)點。在具體的實現(xiàn)中,Redis 用一個大小固定為 CLUSTER_SLOTS 的 clusterNode 數(shù)組 slots 來保存每個桶的負(fù)責(zé)節(jié)點。
typedef struct clusterNode {
...
unsigned char slots[CLUSTER_SLOTS/8];
...
} clusterNode;
typedef struct clusterState {
// slots記錄每個桶被哪個節(jié)點存儲
clusterNode *slots[CLUSTER_SLOTS];
...
} clusterState;
在集群模式下,Redis 接收任何鍵相關(guān)命令時首先計算鍵對應(yīng)的桶編號,再根據(jù)桶找出所對應(yīng)的節(jié)點,如果節(jié)點是自身,則處理鍵命令;否則回復(fù) MOVED 重定向錯誤,通知客戶端請求正確的節(jié)點,這個過程稱為 MOVED 重定向。重定向信息包含了鍵所對應(yīng)的桶以及負(fù)責(zé)該桶的節(jié)點地址,根據(jù)這些信息客戶端就可以向正確的節(jié)點發(fā)起請求。
ref Redis集群詳解(上)
7、為什么是 163834 個槽位?
從前面的 Cluster 集群原理我們已經(jīng)了解到集群中的所有節(jié)點在握手成功后悔定期發(fā)送 ping/pong 消息,交換數(shù)據(jù)信息。
先來了解一下消息體傳遞了哪些數(shù)據(jù):
typedef struct {
//消息的長度(包括這個消息頭的長度和消息正文的長度)
uint32_t totlen;
//消息的類型
uint16_t type;
//消息正文包含的節(jié)點信息數(shù)量
//只在發(fā)送MEET 、PING 、PONG 這三種Gossip 協(xié)議消息時使用
uint16_t count;
//發(fā)送者所處的配置紀(jì)元
uint64_t currentEpoch;
//如果發(fā)送者是一個主節(jié)點,那么這里記錄的是發(fā)送者的配置紀(jì)元
//如果發(fā)送者是一個從節(jié)點,那么這里記錄的是發(fā)送者正在復(fù)制的主節(jié)點的配置紀(jì)元
uint64_t configEpoch;
//發(fā)送者的名字(ID )
char sender[REDIS_CLUSTER_NAMELEN];
//發(fā)送者目前的槽指派信息
unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
//如果發(fā)送者是一個從節(jié)點,那么這里記錄的是發(fā)送者正在復(fù)制的主節(jié)點的名字
//如果發(fā)送者是一個主節(jié)點,那么這里記錄的是REDIS_NODE_NULL_NAME
//(一個40 字節(jié)長,值全為0 的字節(jié)數(shù)組)
char slaveof[REDIS_CLUSTER_NAMELEN];
//發(fā)送者的端口號
uint16_t port;
//發(fā)送者的標(biāo)識值
uint16_t flags;
//發(fā)送者所處集群的狀態(tài)
unsigned char state;
//消息的正文(或者說,內(nèi)容)
union clusterMsgData data;
} clusterMsg;
上圖展示的消息體結(jié)構(gòu)無外乎是一些節(jié)點標(biāo)識,IP,端口號,發(fā)送時間等,但需要注意一下標(biāo)紅的 myslots 的 char 數(shù)組,長度為 16383/8,這其實是一個 bitmap,每一個位代表一個槽,如果該位為1,表示這個槽是屬于這個節(jié)點的。
消息體大小上的考量
至于這個消息體有多大?顯然最占空間的就是 myslots 數(shù)組:16384÷8÷1024=2kb。如果槽位達到 65536,則所占空間提升到 65536÷8÷1024=8kb,極大浪費帶寬。
Redis 集群主節(jié)點數(shù)量基本不可能超過 1000 個
集群節(jié)點越多,心跳包的消息體內(nèi)攜帶的數(shù)據(jù)越多。如果節(jié)點過1000個,也會導(dǎo)致網(wǎng)絡(luò)擁堵。
槽位越小,節(jié)點少的情況下,壓縮比高
ref 為什么Redis集群有16384個槽
ref 集群之間的消息
8、集群的故障發(fā)現(xiàn)與遷移?
故障發(fā)現(xiàn)
當(dāng)集群內(nèi)某個節(jié)點出現(xiàn)問題時,需要通過一種健壯的方式保證識別出節(jié)點是否發(fā)生了故障。Redis 集群內(nèi)節(jié)點通過 ping/pong 消息實現(xiàn)節(jié)點通信,消息不但可以傳播節(jié)點槽信息,還可以傳播其他狀態(tài)如:主從狀態(tài)、節(jié)點故障等。因此故障發(fā)現(xiàn)也是通過消息傳播機制實現(xiàn)的。 主要環(huán)節(jié)包括:
主觀下線(PFAIL-Possibly Fail)
集群中每個節(jié)點都會定期向其他節(jié)點發(fā)送ping消息,接收節(jié)點回復(fù)pong消息作為響應(yīng)。如果在cluster-node-timeout時間內(nèi)通信一直失敗,則發(fā)送節(jié)點會認(rèn)為接收節(jié)點存在故障,把接收節(jié)點標(biāo)記為主觀下線(PFail)狀態(tài)。
客觀下線(Fail)
Redis 集群對于節(jié)點最終是否故障判斷非常嚴(yán)謹(jǐn),只有一個節(jié)點認(rèn)為主觀下線并不能準(zhǔn)確判斷是否故障。當(dāng)某個節(jié)點判斷另一個節(jié)點主觀下線后,相應(yīng)的節(jié)點狀態(tài)會跟隨消息在集群內(nèi)傳播,通過Gossip消息傳播,集群內(nèi)節(jié)點不斷收集到故障節(jié)點的下線報告。當(dāng)半數(shù)以上持有槽的主節(jié)點都標(biāo)記某個節(jié)點是主觀下線時。觸發(fā)客觀下線流程。
故障恢復(fù)
故障節(jié)點變?yōu)榭陀^下線后,如果下線節(jié)點是持有槽的主節(jié)點則需要在它的從節(jié)點中選出一個替換它,從而保證集群的高可用。下線主節(jié)點的所有從節(jié)點承擔(dān)故障恢復(fù)的義務(wù),當(dāng)從節(jié)點通過內(nèi)部定時任務(wù)發(fā)現(xiàn)自身復(fù)制的主節(jié)點進入客觀下線時,將會觸發(fā)故障恢復(fù)流程。
ref 018.Redis Cluster故障轉(zhuǎn)移原理
Redis 的持久化機制是什么?各自的優(yōu)缺點?
1. Redis 的 RDB?
RDB 持久化是指在指定的時間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤。也是默認(rèn)的持久化方式。也就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進制文件中,默認(rèn)的文件名為 dump.rdb。
RDB 支持 同步(save 命令)、后臺異步(bgsave)以及自動配置三種方式觸發(fā)。
優(yōu)點
RDB 文件緊湊,全量備份,非常適合用于進行備份和災(zāi)難恢復(fù)
生成 RDB 文件時支持異步處理,主進程不需要進行任何磁盤IO操作
RDB 在恢復(fù)大數(shù)據(jù)集時的速度比 AOF 的恢復(fù)速度要快
缺點
RDB 快照是一次全量備份,存儲的是內(nèi)存數(shù)據(jù)的二進制序列化形式,存儲上非常緊湊。且在快照持久化期間修改的數(shù)據(jù)不會被保存,可能丟失數(shù)據(jù)。
2.Redis 的 AOF?
全量備份總是耗時的,有時候我們提供一種更加高效的方式 AOF,其工作機制更加簡單:會將每一個收到的寫命令追加到文件中。
隨著時間推移,AOF 持久化文件也會變的越來越大。為了解決此問題,Redis 提供了 bgrewriteaof 命令,作用是 fork 出一條新進程將內(nèi)存中的數(shù)據(jù)以命令的方式保存到臨時文件中,完成對AOF 文件的重寫。
AOF 也有三種觸發(fā)方式:1)每修改同步 always 2)每秒同步 everysec 3)不同no:從不同步。
優(yōu)點
AOF 可以更好的保護數(shù)據(jù)不丟失,一般 AOF 隔 1 秒通過一個后臺線程執(zhí)行一次 fsync 操作
AOF 日志文件沒有任何磁盤尋址的開銷,寫入性能非常高,文件不容易破損
AOF 日志文件即使過大的時候,出現(xiàn)后臺重寫操作,也不會影響客戶端的讀寫
AOF 日志文件的命令通過非??勺x的方式進行記錄,這個特性非常適合做災(zāi)難性的誤刪除的緊急恢復(fù)
缺點
對于同一份數(shù)據(jù)來說,AOF 日志文件通常比 RDB 數(shù)據(jù)快照文件更大
AOF開啟后,支持的寫 QPS 會比RDB支持的寫 QPS 低,因為 AOF 一般會配置成每秒 fsync 一次日志文件,當(dāng)然,每秒一次 fsync,性能也還是很高的
RDB 和 AOF 該如何選擇?
命令RDBAOF啟動優(yōu)先級低高體積小大恢復(fù)速度快慢數(shù)據(jù)安全性丟數(shù)據(jù)取決于刷盤策略輕重重輕
Redis 常見性能問題和解決方案
Master 最好不要寫內(nèi)存快照,如果 Master 寫內(nèi)存快照,save 命令調(diào)度 rdbSave函數(shù),會阻塞主線程的工作,當(dāng)快照比較大時對性能影響是非常大的,會間斷性暫停服務(wù)。
如果數(shù)據(jù)比較重要,某個 Slave 開啟 AOF 備份數(shù)據(jù),策略設(shè)置為每秒同步一。
為了主從復(fù)制的速度和連接的穩(wěn)定性,Master 和 Slave 最好在同一個局域網(wǎng)。
盡量避免在壓力很大的主庫上增加從。
主從復(fù)制不要用圖狀結(jié)構(gòu),用單向鏈表結(jié)構(gòu)更為穩(wěn)定,即:Master <- Slave1<- Slave2 <- Slave3……這樣的結(jié)構(gòu)方便解決單點故障問題,實現(xiàn) Slave 對 Master 的替換。如果 Master 掛了,可以立刻啟用 Slave1 做 Master,其他不變。
Redis 過期鍵的刪除策略?
定時刪除:在設(shè)置鍵的過期時間的同時,創(chuàng)建一個定時器 timer。讓定時器在鍵的過期時間來臨時,立即執(zhí)行對鍵的刪除操作。
惰性刪除:放任鍵過期不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話,就刪除該鍵;如果沒有過期,就返回該鍵。
定期刪除:每隔一段時間程序就對數(shù)據(jù)庫進行一次檢查,刪除里面的過期鍵。至于要刪除多少過期鍵,以及要檢查多少個數(shù)據(jù)庫,則由算法決定。
Redis 的回收策略(淘汰策略)?
volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰
allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)
注意這里的 6 種機制,volatile 和 allkeys 規(guī)定了是對已設(shè)置過期時間的數(shù)據(jù)集淘汰數(shù)據(jù)還是從全部數(shù)據(jù)集淘汰數(shù)據(jù),后面的 lru、ttl 以及 random 是三種不同的淘汰策略,再加上一種 no-enviction 永不回收的策略。
使用策略規(guī)則:
如果數(shù)據(jù)呈現(xiàn)冪律分布,也就是一部分?jǐn)?shù)據(jù)訪問頻率高,一部分?jǐn)?shù)據(jù)訪問頻率低,則使用 allkeys-lru
如果數(shù)據(jù)呈現(xiàn)平等分布,也就是所有的數(shù)據(jù)訪問頻率都相同,則使用 allkeys-random
Redis 事務(wù)
1.怎么理解 Redis 事務(wù)?
事務(wù)是一個單獨的隔離操作:事務(wù)中的所有命令都會序列化、按順序地執(zhí)行。事務(wù)在執(zhí)行的過程中,不會被其他客戶端發(fā)送來的命令請求所打斷。
事務(wù)是一個原子操作:事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。
2.Redis 事務(wù)相關(guān)的命令有哪幾個?
MULTI、EXEC、DISCARD、WATCH。
Redis分布式鎖
先拿 setnx 來爭搶鎖,搶到之后,再用 expire 給鎖加一個過期時間防止鎖忘記了釋放。
如何實現(xiàn)分布式鎖:
「互斥性」: 任意時刻,只有一個客戶端能持有鎖
「鎖超時釋放」:持有鎖超時,可以釋放,防止不必要的資源浪費,也可以防止死鎖
「可重入性」:一個線程如果獲取了鎖之后,可以再次對其請求加鎖
「高性能和高可用」:加鎖和解鎖需要開銷盡可能低,同時也要保證高可用,避免分布式鎖失效
「安全性」:鎖只能被持有的客戶端刪除,不能被其他客戶端刪除
RedLock 加鎖步驟:
1)按順序向集群中所有 master 節(jié)點請求加鎖;
2)根據(jù)設(shè)置的超時時間來判斷,是不是要跳過該 master 節(jié)點;
3)如果大于等于半數(shù)節(jié)點( N/2+1 )加鎖成功,并且使用的時間小于鎖的有效期,即可認(rèn)定加鎖成功啦;
4)如果獲取鎖失敗,解鎖!
緩存三大問題以及解決方案?
緩存穿透:查詢數(shù)據(jù)不存在
緩存空值
key 值校驗,如布隆篩選器 ref
緩存擊穿:緩存過期,伴隨大量對該 key 的請求
互斥鎖
熱點數(shù)據(jù)永不過期
熔斷降級
緩存雪崩:同一時間大批量的 key 過期
熱點數(shù)據(jù)不過期
隨機分散過期時間
柚子快報激活碼778899分享:面試 緩存 最全Redis原理
好文推薦
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。