你了解Redis集群中的秘密吗?
?前言
今天早上北京地鐵,看見一個正在學(xué)習(xí)Redis的小姐姐,很優(yōu)秀
在之前的Redis系列文章中,介紹了Redis的持久化、主從復(fù)制以及哨兵機(jī)制,主從復(fù)制+哨兵機(jī)制,既可以解決主master和從slave的解耦合實現(xiàn)多服務(wù)器備份數(shù)據(jù),又可以通過哨兵機(jī)制來解決主宕機(jī)之后的主從切換,來實現(xiàn)高可用
但是,上面這種方式仍然有不足,最主要的問題就是**存儲能力受到單機(jī)限制,以及無法實現(xiàn)寫操作的負(fù)載均衡(寫操作均由主來完成)
在Redis3.0在開始使用引入的分布式存儲方案,即Redis集群cluster, 集群是由多個節(jié)點組成的,Redis集群的數(shù)據(jù)分區(qū)在這些節(jié)點中,節(jié)點分為主節(jié)點和從節(jié)點,一個集群有多個主節(jié)點和從節(jié)點,只有主節(jié)點負(fù)責(zé)讀寫請求和集群的維護(hù),而從節(jié)點只負(fù)責(zé)主節(jié)點數(shù)據(jù)和狀態(tài)信息的復(fù)制
集群的搭建
這一部分我們來一起搭建一個含有6個節(jié)點的簡單的集群,三主三從,可以選擇所有節(jié)點在同一臺服務(wù)器上,也可以選擇不在同一服務(wù)器上
這里演示以同一服務(wù)器為準(zhǔn),以端口號不同來區(qū)分主從,配置從簡,三個主節(jié)點端口號為:6001、6002、6003,三個對應(yīng)從節(jié)點端口號為7001、7002、7003,搭建集群大致需要四個步驟:
(1)啟動節(jié)點:以集群的模式啟動相應(yīng)的Redis節(jié)點,節(jié)點獨立\
(2)節(jié)點握手:節(jié)點之間握手,形成一個網(wǎng)絡(luò)\
(3)分配槽位:將16384個槽位分配給多個主節(jié)點
(4)指定從節(jié)點:為每一個主節(jié)點指定相應(yīng)的從節(jié)點
1、啟動節(jié)點
其實啟動集群的節(jié)點也是和正常節(jié)點一樣的,也是通過redis-server來啟動,但是配置文件需要修改,以集群的方式來啟動,看下配置文件案例
#redis-6001.conf port 6001 cluster-enabled yes cluster-config-file "node-6001.conf" logfile "log-6001.log" dbfilename "dump-6001.rdb" daemonize yes其中的cluster-enabled和cluster-config-file都是和集群相關(guān)的配置;
cluster-enabled配置設(shè)置相應(yīng)的啟動模式是單機(jī)模式或者集群模式,如果是yes則為按照集群模式啟動,no則為單機(jī)模式
可以通過info server命令來查看服務(wù)器信息,然后會看到redis_mode這一項對應(yīng)的值是cluster,代表集群模式,如果是standalone,則為單機(jī)模式;
cluster-config-file則指定了集群配置文件的位置,每個節(jié)點在運行的時候維護(hù)一個集群配置文件,這和剛剛上面配置文件是不同的,屬于集群特有
每當(dāng)集群信息發(fā)生變化時,集群中的所有節(jié)點會將信息更新到集群配置文件中,節(jié)點重啟的時候也可以重新讀取該配置文件,獲取到集群的信息
Redis節(jié)點啟動的時候,首先檢查是否含有集群配置文件,如果有則使用集群配置文件進(jìn)行配置啟動,如果沒有則初始化配置并且保存到集群配置文件中,集群配置文件是由節(jié)點自動維護(hù)更新的,不需要人工維護(hù)
我們按照上述代碼塊編輯好配置文件之后,使用正常的 redis-server redis-6001.conf 來啟動redis節(jié)點,節(jié)點成功啟動之后,返回值的第一項就是節(jié)點id,由40個16進(jìn)制字符組成,節(jié)點id只在集群初始化的時候創(chuàng)建一次,然后保存到配置文件中,下一次啟動的時候自動讀取使用該id;
和主從復(fù)制篇中runid不同的是,runid是每次重啟都會重新創(chuàng)建,但是節(jié)點id只在集群初始化的時候創(chuàng)建一次
2、節(jié)點握手
節(jié)點啟動之后,是互相獨立的,并不知道其它節(jié)點的存在,需要進(jìn)行節(jié)點的握手,才可以把這些節(jié)點組成一個網(wǎng)絡(luò),互相知道對方的存在,節(jié)點握手使用cluster mett ip port 命令來實現(xiàn)
比如我們現(xiàn)在在端口號為6001上的客戶端上執(zhí)行cluster meet 127.0.0.1 6002,可以完成6001服務(wù)器和6002服務(wù)器的握手
同理,我們把其余的6003、7001、7002、7003的節(jié)點都握手,這樣6000節(jié)點便可以感受到其余五個服務(wù)器了,通過節(jié)點之間的通信,每個節(jié)點也可以感受到其它的節(jié)點的存在
3、分配槽位
集群中有16384個槽位,槽是數(shù)據(jù)管理和遷移的基本單位,當(dāng)數(shù)據(jù)庫中的16384個槽位都進(jìn)行了分配之后,集群處于線程狀態(tài),如果有任意一個槽未進(jìn)行分配,則集群處于下線狀態(tài),我們可以通過cluster info命令來查看集群的狀態(tài),看到cluster_state的狀態(tài)是fail,分配槽使用cluster addslots命令,分配槽給主節(jié)點
redis-cli -p 6001cluster addslots {0..5461} redis-cli -p 6002cluster addslots {5462..10922} redis-cli -p 6003cluster addslots {10923..16383}此時再用cluster info查看集群狀態(tài),處于OK狀態(tài)
4、指定主從關(guān)系
集群中指定主從關(guān)系不再像主從復(fù)制那樣使用slaveof命令,而是使用cluster replicate命令,參數(shù)是節(jié)點的id,就是我們上面提到的節(jié)點id,可以通過cluster node來查看節(jié)點id
redis-cli -p 8000 cluster replicate 2d87d7708efa6df6feb3b1e0cce081dd8984420d redis-cli -p 8001 cluster replicate f1e370caddac65e23477e098863177ba4387a506 redis-cli -p 8002 cluster replicate 171d843413b3d3d0b63f644b6cf07a467108c214此時再用cluster node查看節(jié)點狀態(tài),可以看到節(jié)點的主從關(guān)系已經(jīng)建立,實際上,前三步完成后便可以對外提供服務(wù),但是需要指定相應(yīng)的從節(jié)點之后,集群才可以提供高可用的服務(wù)
集群搭建完畢,開始原理部分
集群內(nèi)部
我們雖然上面搞會了搭建集群,但是我們還不知道關(guān)于集群的內(nèi)部原理部分,內(nèi)部原理這塊我大體分為數(shù)據(jù)分區(qū)、節(jié)點通信、集群狀態(tài)三部分來說明
1、數(shù)據(jù)分區(qū):
數(shù)據(jù)分區(qū)有順序分區(qū),哈希分區(qū)等,而哈希分區(qū)具有天然的隨機(jī)性,集群使用的分區(qū)方案便是哈希分區(qū)的一種。
衡量數(shù)據(jù)分區(qū)方法好壞的標(biāo)準(zhǔn)有很多,其中比較重要的兩個因素是
- 數(shù)據(jù)分布是否均勻
- 增加或刪減節(jié)點對數(shù)據(jù)分布的影響。
由于哈希的隨機(jī)性,哈希分區(qū)基本可以保證數(shù)據(jù)分布均勻,因此在比較哈希分區(qū)方案時,重點要看增減節(jié)點對數(shù)據(jù)分布的影響。
哈希分區(qū)又可分為哈希取余分區(qū)、一致性哈希分區(qū)、帶虛擬節(jié)點的一致性哈希分區(qū),接下來簡單介紹下
哈希取余分區(qū)
哈希取余分區(qū)思路非常簡單:計算key的hash值,然后對節(jié)點數(shù)量進(jìn)行取余,從而決定數(shù)據(jù)映射到哪個節(jié)點上。
該方案最大的問題是,當(dāng)新增或刪減節(jié)點時,節(jié)點數(shù)量發(fā)生變化,系統(tǒng)中所有的數(shù)據(jù)都需要重新計算映射關(guān)系,引發(fā)大規(guī)模數(shù)據(jù)遷移
一致性哈希
將整個哈希值空間組織成一個虛擬的圓環(huán),范圍為0-2^32-1;
對于每個數(shù)據(jù),根據(jù)key計算hash值,確定數(shù)據(jù)在環(huán)上的位置,然后從此位置沿環(huán)順時針行走,找到的第一臺服務(wù)器就是其應(yīng)該映射到的服務(wù)器
與哈希取余分區(qū)相比,一致性哈希分區(qū)將增減節(jié)點的影響限制在相鄰節(jié)點。
以上圖為例,如果在node1和node2之間增加node5,則只有node2中的一部分?jǐn)?shù)據(jù)會遷移到node5;如果去掉node2,則原node2中的數(shù)據(jù)只會遷移到node4中,只有node4會受影響
一致性哈希分區(qū)的主要問題在于,當(dāng)節(jié)點數(shù)量較少時,增加或刪減節(jié)點,對單個節(jié)點的影響可能很大,造成數(shù)據(jù)的嚴(yán)重不平衡。
還是以上圖為例,如果去掉node2,node4中的數(shù)據(jù)由總數(shù)據(jù)的1/4左右變?yōu)?/2左右,與其他節(jié)點相比負(fù)載過高
帶虛擬節(jié)點的一致性哈希
集群采用的是帶虛擬節(jié)點的一致性哈希分區(qū),在redis中這里的虛擬節(jié)點被稱為槽(slot),redis被設(shè)計為16384個槽。
槽是介于數(shù)據(jù)和實際節(jié)點之間的虛擬概念;每個實際節(jié)點包含一定數(shù)量的槽,每個槽包含哈希值在一定范圍內(nèi)的數(shù)據(jù)。
引入槽以后,數(shù)據(jù)的映射關(guān)系由數(shù)據(jù)hash->實際節(jié)點,變成了數(shù)據(jù)hash->槽->實際節(jié)點,在使用了槽的一致性哈希分區(qū)中,槽是數(shù)據(jù)管理和遷移的基本單位。
槽解耦了數(shù)據(jù)和實際節(jié)點之間的關(guān)系,增加或刪除節(jié)點對系統(tǒng)的影響很小
舉個簡單例子說明:我們存取的key會根據(jù)crc16的算法得出一個結(jié)果,然后把結(jié)果對 16384 求余數(shù),這樣每個 key 都會對應(yīng)一個編號在 0-16383 之間的哈希槽,通過這個值,去找到對應(yīng)的插槽所對應(yīng)的節(jié)點,然后直接自動跳轉(zhuǎn)到這個對應(yīng)的節(jié)點上進(jìn)行存取操作
2、節(jié)點通信:
節(jié)點通信主要是依靠端口號、Gossip協(xié)議來完成的
端口號
我們通過哨兵那一篇知道了哨兵系統(tǒng)中,哨兵節(jié)點是不存儲數(shù)據(jù)的,主要是負(fù)責(zé)控制故障轉(zhuǎn)移;
而在集群系統(tǒng)中,所有的節(jié)點都是存儲數(shù)據(jù)的,也都參與集群的維護(hù),更像是一個大家庭,大家一起來維護(hù),集群中的每個節(jié)點都提供了兩個端口號:
- 普通端口:即我們在前面指定的端口(6001這些)。普通端口主要用于為客戶端提供服務(wù),在節(jié)點間數(shù)據(jù)遷移時也會使用
- 集群端口:集群端口是普通端口+10000(10000是固定值,無法改變),如6001節(jié)點的集群端口為16001。集群端口只用于節(jié)點之間的通信,如搭建集群、增減節(jié)點等操作時節(jié)點間的通信,為了保證集群可以正常工作,可以關(guān)閉本地防火墻,不要使用客戶端連接集群接口
Gossip協(xié)議
節(jié)點之間通信,主要分為幾種類型:單對單、廣播、Gossip協(xié)議幾種類型
單對單,這個應(yīng)該不用我多嗶嗶;
廣播的話,指的是向集群內(nèi)所有節(jié)點發(fā)送消息,優(yōu)點是集群內(nèi)所有節(jié)點獲得的集群信息是一致的,速度快,缺點是每條消息都要發(fā)送給所有節(jié)點,消耗帶寬;
Gossip協(xié)議,在有限的節(jié)點網(wǎng)絡(luò)中,每個節(jié)點隨機(jī)的和部分節(jié)點通信(其實并不真隨機(jī),而是根據(jù)特定的規(guī)則選擇相應(yīng)的節(jié)點),經(jīng)過看似無序的通信之后每個節(jié)點的狀態(tài)很快會達(dá)到一致
Gossip協(xié)議的優(yōu)點是負(fù)載低、去中心化、容錯性高;缺點主要是集群的收斂速度慢。
3、集群狀態(tài):
集群的狀態(tài)這個概念是比較廣的,包括集群是否處于上線狀態(tài)、集群中有哪些節(jié)點、節(jié)點是否可達(dá)、槽的狀態(tài)、節(jié)點的主從狀態(tài)等
那么,集群狀態(tài)的信息是由誰來維護(hù)的呢?又是怎樣維護(hù)的呢?
集群的狀態(tài)是由集群中所有節(jié)點共同維護(hù)的,向上面說的,每個節(jié)點對應(yīng)著有個集群中的端口號,也會生成相應(yīng)的集群配置文件;
節(jié)點為了存儲集群狀態(tài),最主要的是通過clusterNode和clusterState這兩種結(jié)構(gòu)維護(hù)集群,前者是記錄了一個節(jié)點的狀態(tài),后者是記錄了集群整體的狀態(tài)
clusterNode
clusterNode結(jié)構(gòu)保存了一個節(jié)點的當(dāng)前狀態(tài),包括創(chuàng)建時間、節(jié)點id、ip和端口號等。
每個節(jié)點都會用一個clusterNode結(jié)構(gòu)記錄自己的狀態(tài),并為集群內(nèi)所有其他節(jié)點都創(chuàng)建一個clusterNode結(jié)構(gòu)來記錄節(jié)點狀態(tài)
typedef struct clusterNode {//節(jié)點創(chuàng)建時間mstime_t ctime;//節(jié)點idchar name[REDIS_CLUSTER_NAMELEN];//節(jié)點的ip和端口號char ip[REDIS_IP_STR_LEN];int port;//節(jié)點標(biāo)識:整型,每個bit都代表了不同狀態(tài),如節(jié)點的主從狀態(tài)、是否在線、是否在握手等int flags;//配置紀(jì)元:故障轉(zhuǎn)移時起作用,類似于哨兵的配置紀(jì)元uint64_t configEpoch;//槽在該節(jié)點中的分布:占用16384/8個字節(jié),16384個比特;每個比特對應(yīng)一個槽:比特值為1,則該比特對應(yīng)的槽在節(jié)點中;比特值為0,則該比特對應(yīng)的槽不在節(jié)點中unsigned char slots[16384/8];//節(jié)點中槽的數(shù)量int numslots;…………} clusterNode;clusterState
clusterState結(jié)構(gòu)保存了在當(dāng)前節(jié)點視角下,集群所處的狀態(tài)。主要字段包括:
typedef struct clusterState {//自身節(jié)點clusterNode *myself;//配置紀(jì)元uint64_t currentEpoch;//集群狀態(tài):在線還是下線int state;//集群中至少包含一個槽的節(jié)點數(shù)量int size;//哈希表,節(jié)點名稱->clusterNode節(jié)點指針dict *nodes;//槽分布信息:數(shù)組的每個元素都是一個指向clusterNode結(jié)構(gòu)的指針;如果槽還沒有分配給任何節(jié)點,則為NULLclusterNode *slots[16384];………… } clusterState;集群方案的設(shè)計
設(shè)計集群方案時,至少要考慮以下因素:
- 數(shù)據(jù)量和訪問量:估算應(yīng)用需要的數(shù)據(jù)量和總訪問量(考慮業(yè)務(wù)發(fā)展,留有冗余),結(jié)合每個主節(jié)點的容量和能承受的訪問量(可以通過benchmark得到較準(zhǔn)確估計),計算需要的主節(jié)點數(shù)量
- 高可用要求:根據(jù)故障轉(zhuǎn)移的原理,至少需要3個主節(jié)點才能完成故障轉(zhuǎn)移,且3個主節(jié)點不應(yīng)在同一臺物理機(jī)上;每個主節(jié)點至少需要1個從節(jié)點,且主從節(jié)點不應(yīng)在一臺物理機(jī)上;因此高可用集群至少包含6個節(jié)點
- 節(jié)點數(shù)量限制:Redis官方給出的節(jié)點數(shù)量限制為1000,主要是考慮節(jié)點間通信帶來的消耗。在實際應(yīng)用中應(yīng)盡量避免大集群;如果節(jié)點數(shù)量不足以滿足應(yīng)用對Redis數(shù)據(jù)量和訪問量的要求,可以考慮:(1)業(yè)務(wù)分割,大集群分為多個小集群;(2)減少不必要的數(shù)據(jù);(3)調(diào)整數(shù)據(jù)過期策略等
- 適度冗余:Redis可以在不影響集群服務(wù)的情況下增加節(jié)點,因此節(jié)點數(shù)量適當(dāng)冗余即可,不用太大
從節(jié)點服務(wù)器內(nèi)部維護(hù)了masterhost、masterport兩個字段,用于保存主節(jié)點服務(wù)器的IP和端口信息
關(guān)于主從復(fù)制的問題還有很多,下面我會單獨肝一篇來分析主從復(fù)制中存在的問題以及解決辦法
結(jié)束語
感謝大家能夠做我最初的讀者和傳播者,請大家相信,只要你給我一
份愛,我終究會還你們一頁情的。
歡迎大家關(guān)注我的公眾號【左耳君】,探索技術(shù),分享生活
哦對了,后續(xù)所有的文章都會更新到這里
https://github.com/DayuMM2021/Java
總結(jié)
以上是生活随笔為你收集整理的你了解Redis集群中的秘密吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。