前言
這里來了解一下,Redis 中常見的集群方案
幾種常用的集群方案
- 主從集群模式
- 哨兵機制
- 切片集群(分片集群)
主從集群模式
主從集群,主從庫之間采用的是讀寫分離
- 主庫:所有的寫操作都在讀庫發生,然后主庫同步數據到從庫,同時也可以進行讀操作;
- 從庫:只負責讀操作;
主庫需要復制數據到從庫,主從雙方的數據庫需要保存相同的數據,將這種情況稱為"數據庫狀態一致"
來看下如何同步之前先來了解下幾個概念
1、服務器的運行ID(run ID):每個 Redis 服務器在運行期間都有自己的run ID
,run ID
在服務器啟動的時候自動生成。
從服務器會記錄主服務器的run ID
,這樣如果發生斷網重連,就能判斷新連接上的主服務器是不是上次的那一個,這樣來決定是否進行數據部分重傳還是完整重新同步。
2、復制偏移量 offset:主服務器和從服務器都會維護一個復制偏移量
主服務器每次向從服務器中傳遞 N 個字節的時候,會將自己的復制偏移量加上 N。
從服務器中收到主服務器的 N 個字節的數據,就會將自己額復制偏移量加上 N。
通過主從服務器的偏移量對比可以很清楚的知道主從服務器的數據是否處于一致。
如果不一致就需要進行增量同步了,具體參加下文的增量同步
全量同步
從服務器首次加入主服務器中發生的是全量同步
如何進行第一次同步?
1、從服務器連接到主服務器,然后發送 psync 到主服務器,因為第一次復制,不知道主庫run ID
,所以run ID
為?;
2、主服務器接收到同步的響應,回復從服務器自己的run ID
和復制進行進度 offset;
3、主服務器開始同步所有數據到從庫中,同步依賴 RDB 文件,主庫會通過 bgsave 命令,生成 RDB 文件,然后將 RDB 文件傳送到從庫中;
4、從庫收到 RDB 文件,清除自己的數據,然后載入 RDB 文件;
5、主庫在同步的過程中不會被阻塞,仍然能接收到命令,但是新的命令是不能同步到從庫的,所以主庫會在內存中用專門的 replication buffer
,記錄 RDB 文件生成后收到的所有寫操作,然后在 RDB 文件,同步完成之后,再將replication buffer
中的命令發送到從庫中,這樣就保證了從庫的數據同步。
增量同步
如果主從服務器之間發生了網絡閃斷,從從服務將會丟失一部分同步的命令。
在舊版本,Redis 2.8
之前,如果發生了網絡閃斷,就會進行一次全量復制。
在 2.8 版本之后,引入了增量同步的技術,這里主要是用到了 repl_backlog_buffer
Redis 主庫接收到寫操作的命令,首先會寫入replication buffer
(主要用于主從數據傳輸的數據緩沖),同時也會把這些操作命令也寫入repl_backlog_buffer
這個緩沖區。
這里可能有點疑惑,已經有了replication buffer
為什么還多余引入一個repl_backlog_buffer
呢?
-
repl_backlog_buffer
一個主庫對應一個repl_backlog_buffer
,也就是所有從庫對應一個repl_backlog_buffer
,從庫自己記錄自己的slave_repl_offset
。 -
replication buffer
用于主節點與各個從節點間,數據的批量交互。主節點為各個從節點分別創建一個緩沖區,由于各個從節點的處理能力差異,各個緩沖區數據可能不同。
如何主從斷開了,當然對應的replication buffer
也就沒有了。這時候就依賴repl_backlog_buffer
進行數據的增量同步了。
repl_backlog_buffer
是一個環形緩沖區,主庫會記錄自己寫到的位置,從庫則會記錄自己已經讀到的位置。
這里借用Redis核心技術與實戰的一張圖片
剛開始主服務器的 master_repl_offset 和從服務器 slave_repl_offset 的位置是一樣的,在從庫因為網絡原因斷連之后,隨著主庫寫操作的進行,主從偏移量會出現偏移距離。
當從服務器連上主服務器之后,從服務把自己當前的 slave_repl_offset 告訴主服務器,然后主服務器根據自己的 master_repl_offset 計算出和從服務器之間的差距,然后把兩者之間相差的命令操作同步給從服務器。
舉個栗子
比如這里從服務器1,剛剛由于網絡原因斷連了一會,然后又恢復了連接,這時候,可能缺失了一段時間的命令同步,repl_backlog_buffer
的增量同步機制就登場了。
repl_backlog_buffer
會根據主服務器的master_repl_offset
和從服務器slave_repl_offset
,計算出兩者命令之間的差距,之后把差距同步給replication buffer
,然后發送到從服務器中。
repl_backlog_buffer
中的緩沖空間要設置的大一點,如果從庫讀的過慢,因為是環形緩沖區,可能出現命令覆蓋的情況,如果出現命令被覆蓋了,從庫的增量同步就無法進行了,這時候會進行一次全量的復制。
緩沖空間的計算公式是:緩沖空間大小 = 主庫寫入命令速度 * 操作大小 - 主從庫間網絡傳輸命令速度 * 操作大小
。在實際應用中,考慮到可能存在一些突發的請求壓力,我們通常需要把這個緩沖空間擴大一倍,即 repl_backlog_size = 緩沖空間大小 * 2,這也就是 repl_backlog_size 的最終值。
哨兵機制
對于主從集群模式,如果從庫發生了故障,還有主庫和其它的從庫可以接收請求,但是如果主庫掛了,就不能進行正常的數據寫入,同時數據同步也不能正常的進行了,當然這種情況,我們需要想辦法避免,于是就引入了下面的哨兵機制。
什么是哨兵機制
sentinel(哨兵機制):是 Redis 中集群的高可用方式,哨兵節點是特殊的 Redis 服務,不提供讀寫,主要來監控 Redis 中的實例節點,如果監控服務的主服務器下線了,會從所屬的從服務器中重新選出一個主服務器,代替原來的主服務器提供服務。
核心功能就是:監控,選主,通知。
監控:哨兵機制,會周期性的給所有主服務器發出 PING 命令,檢測它們是否仍然在線運行,如果在規定的時間內響應了 PING 通知則認為,仍在線運行;如果沒有及時回復,則認為服務已經下線了,就會進行切換主庫的動作。
選主:當主庫掛掉的時候,會從從庫中按照既定的規則選出一個新的的主庫,
通知:當一個主庫被新選出來,會通知其他從庫,進行連接,然后進行數據的復制。當客戶端試圖連接失效的主庫時,集群也會向客戶端返回新主庫的地址,使得集群可以使用新的主庫。
如何保證選主的準確性
哨兵會通過 PING 命令檢測它和從庫,主庫之間的連接情況,如果發現響應超時就會認為給服務已經下線了。
當然這會存在誤判的情況,如果集群的網絡壓力比較大,網路堵塞,這時候會存在誤判的情況。
如果誤判的節點是從節點,影響不會很大,拿掉一個從節點,對整體的服務,影響不大,還是會不間斷的對外提供服務。
如果誤判的節點是主節點,影響就很大了,主節點被標注下線了,就會觸發后續的選主,數據同步,等一連串的動作,這一連串的動作很很消耗性能的。所以對于誤判,應該去規避。
如何減少誤判呢?
引入哨兵集群,一個哨兵節點可能會進行誤判,引入多個少哨兵節點一起做決策,就能減少誤判了。
當有多個哨兵節點的時候,大多數哨兵節點認為主庫下線了,主庫才會真正的被標記為下線了,一般來講當有 N 個哨兵實例時,最好要有N/2 + 1
個實例判斷主庫下線了,才能最終判定主庫的下線狀態。當然這個數值在 Redis 中是可以配置的。
如何選主
選舉主節點的規則
1、過濾掉已經下線的服務器;
2、過濾掉最近5秒鐘沒有回復過主節點的 INFO(用于觀察服務器的角色) 命令的服務器,保證選中的服務器都是最近成功通過信的;
3、過濾掉和下線主服務器連接超過down-after-milliseconds*10
毫秒的從服務器,down-after-milliseconds
是主服務器下線的時間,這一操作避免從服務器與主服務器過早的斷開,影響到從庫中數據同步,因為斷開時間越久,從庫里面的數據就越老舊過時。
然后對這些服務器根據slave-priority
優先級(這個優先級是手動設置的,比如希望那個從服務器優先變成主服務器,優先級就設置的高一點) 進行排序。
如果幾臺從服務器優先級相同,然后根據復制偏移量從大到小進行排序,如果還有相同偏移量的從服務器,然后按照 runID 從小到大進行排序,直到選出一臺從服務器。
哨兵進行主節點切換
當根據選舉規則,選出了可以成為主節點的從節點,如何進行切換呢?
在哨兵中也是有一個 Leader 節點的,當一個從庫被選舉出來,從庫的切換是由 Leader 節點完成的。
Leader 節點的選舉用的是 Raft 算法,關于什么是 Raft 算法可參考Raft一致性算法原理
在raft算法中,在任何時刻,每一個服務器節點都處于這三個狀態之一:
- Follower:追隨者,跟隨者都是被動的:他們不會發送任何請求,只是簡單的響應來自領導者或者候選人的請求;
- Candidate:候選人,如果跟隨者接收不到消息,那么他就會變成候選人并發起一次選舉,獲得集群中大多數選票的候選人將成為領導者。
- Leader:領導者,系統中只有一個領導人并且其他的節點全部都是跟隨者,領導人處理所有的客戶端請求(如果一個客戶端和跟隨者聯系,那么跟隨者會把請求重定向給領導人)
哨兵節點的選舉總結起來就是:
1、每個做主觀下線的sentinel節點向其他sentinel節點發送命令,要求將自己設置為領導者;
2、接收到的sentinel可以同意或者拒絕;
3、如果該sentinel節點發現自己的票數已經超過半數并且超過了 quorum,quorum 用來配置判斷主節點宕機的哨兵節點數。簡單點講就是:如果 Sentinel 集群有 quorum 個哨兵認為 master 宕機了,就「客觀」的認為 master 宕機了;
4、如果此過程選舉出了多個領導者,那么將等待一段時重新進行選舉;
故障轉移
- sentinel的領導者從從機中選舉出合適的叢機進行故障轉移;
-
對選取的從節點進行
slave of no one
命令,(這個命令用來讓從機關閉復制功能,并從從機變為主機); - 更新應用程序端的鏈接到新的主節點;
- 對其他從節點變更 master 為新的節點;
- 修復原來的 master 并將其設置為新的 master 的從機。
消息通知
哨兵和哨兵之前,哨兵和從庫之間,哨兵和客戶端是如何相互發現,進行消息傳遞?
哨兵和哨兵之間的相互發現,通過 Redis 提供的pub/sub
機制實現,因為每個哨兵節點都會和主庫進行連接,通過在主庫中發布信息,訂閱信息,就能找到其他實例的連接信息。
哨兵節點和從庫,通過哨兵向主庫發送 INFO 命令來完成,哨兵給主庫發送 INFO 命令,主庫接受到這個命令后,就會把從庫列表返回給哨兵。接著,哨兵就可以根據從庫列表中的連接信息,和每個從庫建立連接,并在這個連接上持續地對從庫進行監控。
哨兵和客戶端之間:每個哨兵實例也提供pub/sub
機制,客戶端可以從哨兵訂閱消息,來獲知主從庫切換過程中的不同關鍵事件。
哨兵提升一個從庫為新主庫后,哨兵會把新主庫的地址寫入自己實例的 pubsub(switch-master)
中。客戶端需要訂閱這 個pubsub,當這個 pubsub 有數據時,客戶端就能感知到主庫發生變更,同時可以拿到最新的主庫地址,然后把寫請求寫到這個新主庫即可,這種機制屬于哨兵主動通知客戶端。
如果客戶端因為某些原因錯過了哨兵的通知,或者哨兵通知后客戶端處理失敗了,安全起見,客戶端也需要支持主動去獲取最新主從的地址進行訪問。
切片集群
對于數據庫我們知道,如果數據量大會進行分庫分表,一般有兩種方案縱向拆分和橫向拆分。這在 Redis 中,同樣適用。
Redis 中的擴展
- 縱向擴展:更改節點類型以調整集群大小,升級單個Redis實例的資源配置,包括增加內存容量、增加磁盤容量、使用更高配置的CPU。
- 橫向擴展:通過添加或刪除節點組(分片)來更改復制組中的節點組(分片)數量。
簡單點講就是:垂直擴容就是增加自身的容量,橫向擴容就是加機器。
缺點對比
縱向擴容:
1、如果一味的增加自身的容量,意味著自身存儲的數據將會越來越大,過大的數據,持久化時間將會變得很長,影響自身的響應速度;
2、同樣堆硬件總歸是有上線,達到一定量之后,還是要考慮進行橫向擴容;
橫向擴容:
橫向擴容要面臨的問題,如果發生了分片的擴容,就需要考慮數據的遷移,同時數據切片后,在多個實例之間如何分布?,客戶端如何知道訪問的數據在哪個實例中。。。
雖然有這些問題的存在,好在已經有一些成熟的方案來處理橫向擴容所遇到的問題了
官方的集群解決方案就是Redis Cluster
;社區的解決方案有 Codis 和 Twemproxy,Codis 是由我國的豌豆莢團隊開源的,Twemproxy 是 Twitter 團隊的開源的。
這里主要看下Redis Cluster
是如何進行處理的
Redis Cluster方案
1、Redis Cluster
方案采用哈希槽來處理 KEY 在不同實例中的分布,一個切片集群共有16384個哈希槽,這些哈希槽類似于數據分區,每個鍵值對都會根據它的key,被映射到一個哈希槽中。
2、一個 KEY ,首先會根據CRC16算法計算一個16 bit的值;然后,再用這個 16bit 值對 16384 取模,得到0~16383范圍內的模數,每個模數代表一個相應編號的哈希槽。
3、然后把哈希槽分配到所有的實例中,例如,如果集群中有N個實例,那么,每個實例上的槽個數為16384/N個。
當然這是平均分配的,如果平均分配額哈希槽中,某一個實例中 KEY,存儲的數據比較大,造成某一個實例的內存過大,這時候可以通過cluster addslots
手動調節哈希槽的分配。
當手動分配哈希槽時,需要把16384個槽都分配完,否則Redis集群無法正常工作。
客戶端中的 KEY 如何找到對應的實例
在集群剛剛創建的時候,每個實例只知道自己被分配了哪些哈希槽,是不知道其他實例擁有的哈希槽信息的。但是,Redis 實例會把自己的哈希槽信息發給和它相連接的其它實例,來完成哈希槽分配信息的擴散。
所以當客戶端和集群實例連接后,就可以知道所有的哈希槽的映射,客戶端會把哈希槽的映射保存在本地,這樣如果客戶端響應一個 KEY ,計算出哈希槽,然后就可以向對應的實例發送請求了。
哈希槽重新分配
數據在可能發生遷移,這時候哈希槽就會重新分配了
栗如:
1、集群中的實例,有增加或減少;
2、引入了負載均衡,需要重新分配哈希槽;
因為重新分配了哈希槽,所以原來的映射關系可能發生了改變,實例之間可以通過相互通知,快速的感知到映射關系的變化。但是,客戶端無法主動感知這些變化,客戶端對 KEY 的響應,可能依舊映射到了之前的實例節點,面對這種情況,如何處理呢?
1、如果數據已經遷移完了
Redis Cluster
中提供重定向機制,如果一個實例接收到客戶端的請求,但是對應的 KEY 已經轉移到別的實例節點中了,這時候會計算出 KEY 當前所處實例的地址,然后返回給客戶端,客戶端拿到最新的實例地址,重新發送請求就可以了。
$ GET hello (error) MOVED 12320 172.168.56.111:6379
2、數據遷移了一半
如果在遷移的過程中,只遷移了一半的數據,這時候服務器端就會返回 ASK 告知客戶端
GET hello (error) ASK 1332 012320 172.168.56.111:6379
ASK 就表示當前正在遷移中,客戶端需要訪問數據,就還需要向返回的地址信息,發送一條 ASKING 命令,讓這個實例允許客戶端的訪問請求,然后客戶端再發送具體的業務操作命令。
避免 Hot Key
Hot Key
就是采用切片集群部署的 Redis ,出現的集群訪問傾斜。
切片集群中的 Key 最終會存儲到集群中的一個固定的 Redis 實例中。某一個 Key 在一段時間內訪問遠高于其它的 Key,也就是該 Key 對應的 Redis 實例,會收到過大的流量請求,該實例容易出現過載和卡頓現象,甚至還會被打掛掉。
常見引發熱點 Key 的情況:
1、新聞中的熱點事件;
2、秒殺活動中的,性價比高的商品;
如何發現 Hot Key
1、提現預判;
根據業務經驗進行提前預判;
2、在客戶端進行收集;
通過在客戶端增加命令的采集,來統計發現熱點 Key;
3、使用 Redis 自帶的命令排查;
使用monitor命令統計熱點key(不推薦,高并發條件下會有造成redis 內存爆掉的隱患);
hotkeys參數,redis 4.0.3提供了redis-cli的熱點key發現功能,執行redis-cli時加上–hotkeys選項即可。但是該參數在執行的時候,如果key比較多,執行起來比較慢。
4、在Proxy層做收集
如果集群架構引入了 proxy,可以在 proxy 中做統計
5、自己抓包評估
Redis客戶端使用TCP協議與服務端進行交互,通信協議采用的是RESP。自己寫程序監聽端口,按照RESP協議規則解析數據,進行分析。缺點就是開發成本高,維護困難,有丟包可能性。
Hot Key 如何解決
知道了Hot Key
如何來應對呢
1、對 Key 進行分散處理;
舉個栗子
有一個熱 Key 名字為Hot-key-test
,可以將其分散為Hot-key-test1
,Hot-key-test2
...然后將這些 Key 分散到多個實例節點中,當客戶端進行訪問的時候,隨機一個下標的 Key 進行訪問,這樣就能將流量分散到不同的實例中了,避免了一個緩存節點的過載。
一般來講,可以通過添加后綴或者前綴,把一個 hotkey 的數量變成 redis 實例個數 N 的倍數 M,從而由訪問一個redis key
變成訪問N * M
個redis key。 N*M
個redis key
經過分片分布到不同的實例上,將訪問量均攤到所有實例。
const M = N * 2 //生成隨機數 random = GenRandom(0, M) //構造備份新key bakHotKey = hotKey + “_” + random data = redis.GET(bakHotKey) if data == NULL { data = GetFromDB() redis.SET(bakHotKey, expireTime + GenRandom(0,5)) }
2、使用本地緩存;
業務端還可以使用本地緩存,將這些熱 key 記錄在本地緩存,來減少對遠程緩存的沖擊。
避免 Big Key
什么是 Big Key
:我們將含有較大數據或含有大量成員、列表數的Key稱之為大Key。
- 一個STRING類型的Key,它的值為5MB(數據過大)
- 一個LIST類型的Key,它的列表數量為20000個(列表數量過多)
- 一個ZSET類型的Key,它的成員數量為10000個(成員數量過多)
- 一個HASH格式的Key,它的成員數量雖然只有1000個但這些成員的value總大小為100MB(成員體積過大)
Big Key 存在問題
- 內存空間不均勻:如果采用切片集群的部署方案,容易造成某些實例節點的內存分配不均勻;
- 造成網絡擁塞:讀取 bigkey 意味著需要消耗更多的網絡流量,可能會對 Redis 服務器造成影響;
- 過期刪除:big key 不單讀寫慢,刪除也慢,刪除過期 big key 也比較耗時;
- 遷移困難:由于數據龐大,備份和還原也容易造成阻塞,操作失敗;
如何發現 Big Key
- 使用 redis-cli 客戶端的命令 --bigkeys;
- 生成 rdb 文件,離線分析 rdb 文件。比如:redis-rdb-cli,rdbtools;
- 通過 scan 命令,對掃描出來的key進行類型判斷,例如:string長度大于10K,list長度大于10240認為是big bigkeys;
Big Key 如何避免
對于Big Key
可以從以下兩個方面進行處理
合理優化數據結構:
1、對較大的數據進行壓縮處理;
2、拆分集合:將大的集合拆分成小集合(如以時間進行分片)或者單個的數據。
選擇其他的技術來存儲 big key:
使用其他的存儲形式,考慮使用 cdn 或者文檔性數據庫 MongoDB。
Big Key 如何刪除
直接使用 DEL 命令會發生什么?危險:同步刪除 bigkey 會阻塞 Redis 其他命令,造成 Redis 阻塞。
推薦使用 UNLINK 命令,異步刪除 bigkey,不影響主線程執行其他命令。
在業務的低峰期使用 scan 命令查找 big key,對于類型為集合的key,可以使用腳本逐一刪除里面的元素。
參考
【Redis核心技術與實戰】https://time.geekbang.org/column/intro/100056701
【Redis設計與實現】https://book.douban.com/subject/25900156/
【估算兩臺服務器同時故障的概率】https://disksing.com/failure-probability-analysis/
【Redis中哨兵選舉算法】https://blog.csdn.net/weixin_44324174/article/details/108939199
【如何處理redis集群中hot key和big key】https://juejin.cn/post/6844903743083773959
【談談redis的熱key問題如何解決】https://www.cnblogs.com/rjzheng/p/10874537.html
【Redis 中常見的集群部署方案】https://boilingfrog.github.io/2022/02/20/redis中常見的集群部署方案/#主從集群模式
【Redis學習筆記】https://github.com/boilingfrog/Go-POINT/tree/master/redis
到此這篇關于Redis中常見的幾種集群部署方案的文章就介紹到這了,更多相關Redis 集群部署內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文地址:https://www.cnblogs.com/ricklz/p/15916014.html