redis 系列(四)- Redis Cluster

問題

雖然主從復制和哨兵模式完美的解決了Redis的單機問題,但是Redis仍然存在著以下兩個問題:

  1. 所有的寫操作都集中到主服務器上弄屡,主服務器CPU壓力比較大
  2. 不管是主服務器還是從服務器棺禾,它們都同樣保存了redis的所有數(shù)據(jù),隨著數(shù)據(jù)越來越多,可能會出現(xiàn)內存不夠用的問題

解題思路

在redis集群中践险,key只能保存在按照某種規(guī)律計算得到的節(jié)點上班套,對該key的讀取和更新也只能在該節(jié)點進行肢藐。比如redis集群一共有6個節(jié)點,現(xiàn)在我想執(zhí)行 set name hello吱韭,這個key為name吆豹,常見的某種規(guī)律有哈希取余"name".hashcode() % 6 + 1得到節(jié)點的位置為4鱼的,所以就放在第四個的位置上,以后不管我是讀取還是更新還是刪除痘煤,我都到第四個節(jié)點上凑阶。如此一來,便完美解決了上述兩個問題衷快。

Redis 分區(qū)方案(在哪里按照某種規(guī)律計算)

1. 客戶端分區(qū)方案
指在客戶端計算key得到將要保存的節(jié)點宙橱,然后客戶端再連接該節(jié)點端口,進行數(shù)據(jù)操作蘸拔。這種方案比較簡單师郑,但是一旦節(jié)點數(shù)發(fā)生變化,將要更新新的計算算法(比如取余這個6改成10)到所有客戶端上调窍,會比較麻煩宝冕。

客戶端分區(qū)方案

2. 代理分區(qū)方案
指在客戶端和服務器之間加了一層代理層,客戶端的命令先到代理層邓萨,代理層進行計算地梨,再分配到它對應的節(jié)點上;這種方法挺好的缔恳,節(jié)點數(shù)發(fā)生變化宝剖,只需要修改代理層的計算算法即可,但是需要多一層轉發(fā)褐耳,需要一定的耗時诈闺。

代理分區(qū)方案

3. 查詢路由方案
節(jié)點之間早就約定好哪些key是屬于自己,哪些key是屬于其它節(jié)點铃芦;客戶端最開始隨機把命令發(fā)給某個節(jié)點雅镊,節(jié)點計算并查看這個key是否屬于自己的,如果是自己的就進行處理刃滓,并把結果發(fā)回去仁烹;如果是其它節(jié)點的,就會把那個節(jié)點的信息(ip + 地址)轉發(fā)給客戶端咧虎,讓客戶端重定向卓缰,這么一說感覺是有點像http協(xié)議中的3XX狀態(tài)碼。今天的主角Redis Cluster就是基于查詢路由方案砰诵。

查詢路由方案

數(shù)據(jù)分區(qū)(某種規(guī)律有哪些)

數(shù)據(jù)分區(qū)一遍有兩種征唬,哈希分區(qū)和順序分區(qū);哈希分區(qū)顧名思義茁彭,就是對key進行哈希計算然后分區(qū)总寒;而順序分區(qū)則是對按順序對key進行分區(qū)。因為Redis cluster采用的是哈希分區(qū)理肺,所以這里只討論哈希分區(qū)摄闸。哈希分區(qū)也有很多規(guī)則善镰,如下:

1. 節(jié)點取余分區(qū)

對key進行hash計算,然后用節(jié)點的個數(shù)去取余得到應該在哪個節(jié)點hash(key) % N年枕。這種分區(qū)方法比較方便炫欺。就是當節(jié)點數(shù)變化的時候,幾乎所有的key都需要重新分配熏兄。

2. 一致性哈希分區(qū)
3. 虛擬槽分區(qū)

Redis Cluster中品洛,約定了有16383個槽,我們對key進行CRC16(key) & 16383計算后會得到這個key屬于哪個槽霍弹,這16383個槽在集群創(chuàng)建之初毫别,會自動或者手動的分配到不同的節(jié)點中娃弓,即key -> slot -> node典格。添加或者刪除新的節(jié)點的時候,只需要對對應的槽進行重新分配即可台丛。

redis cluster 的大概流程

集群創(chuàng)建之初耍缴,我們可以自動或者手動給每個節(jié)點分配槽位。每個節(jié)點通過Gossip協(xié)議挽霉,會和其它節(jié)點交換槽信息防嗡,得到并且保存槽與節(jié)點的全局對應關系圖。于是節(jié)點收到客戶端發(fā)來的命令以后侠坎,對key進行CRC16(key) & 16383的計算得到槽位蚁趁,對比這個槽位是不是屬于自己的,如果是自己的就進行處理实胸,并把結果發(fā)回去他嫡;如果是其它節(jié)點的,就會把那個節(jié)點的信息(ip + 地址)轉發(fā)給客戶端庐完,然后客戶端再重新請求钢属;當然客戶端也不會那么傻,每次都是隨機請求節(jié)點门躯,客戶端在啟動的時候也會和服務器交換信息得到槽和節(jié)點的映射圖淆党,客戶端請求的節(jié)點,也是客戶端自己計算CRC16(key) & 16383得到槽位讶凉,再對比關系圖而得到的節(jié)點染乌,如果節(jié)點發(fā)生變化了(即收到請求重定向),它也會更新這個關系圖懂讯。

創(chuàng)建集群

  1. 準備節(jié)點,一個高可用的redis集群至少要有6個節(jié)點
# redis-6379.conf
port 6379 
daemonize yes
protected-mode no
logfile "6379.log"
dbfilename "dump-6379.rdb"
cluster-enabled yes # 開啟集群
cluster-node-timeout 15000 #節(jié)點超時時間荷憋,15s
cluster-config-file "nodes-6379.conf" #集群內部配置文件

# redis-6380.conf
port 6380
daemonize yes
protected-mode no
logfile "6380.log"
dbfilename "dump-6380.rdb"
cluster-enabled yes # 開啟集群
cluster-node-timeout 15000 #節(jié)點超時時間,15s
cluster-config-file "nodes-6380.conf" #集群內部配置文件

# redis-6381.conf
port 6381 
daemonize yes
protected-mode no
logfile "6381.log"
dbfilename "dump-6381.rdb"
cluster-enabled yes # 開啟集群
cluster-node-timeout 15000 #節(jié)點超時時間域醇,15s
cluster-config-file "nodes-6381.conf" #集群內部配置文件

# redis-6382.conf
port 6382
daemonize yes
protected-mode no
logfile "6382.log"
dbfilename "dump-6382.rdb"
cluster-enabled yes # 開啟集群
cluster-node-timeout 15000 #節(jié)點超時時間台谊,15s
cluster-config-file "nodes-6382.conf" #集群內部配置文件

# redis-6383.conf
port 6383
daemonize yes
protected-mode no
logfile "6383.log"
dbfilename "dump-6383.rdb"
cluster-enabled yes # 開啟集群
cluster-node-timeout 15000 #節(jié)點超時時間蓉媳,15s
cluster-config-file "nodes-6383.conf" #集群內部配置文件

# redis-6384.conf
port 6384
daemonize yes
protected-mode no
logfile "6384.log"
dbfilename "dump-6384.rdb"
cluster-enabled yes # 開啟集群
cluster-node-timeout 15000 #節(jié)點超時時間,15s
cluster-config-file "nodes-6384.conf" #集群內部配置文件

6個節(jié)點啟動成功后锅铅,我們可以在redis目錄下看到生成的cluster-config-file文件酪呻,打開nodes-6379.conf如下:

8ba45af25feef061507831ca1b3ddf71a7574631 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0

其中8ba45af25feef061507831ca1b3ddf71a7574631是6379redis的節(jié)點ID,這里我們只要知道它很重要就可以了。

  1. 節(jié)點握手盐须,打開客戶端進入6379玩荠,然后依次運行cluster meet 139.199.168.61 6380cluster meet 139.199.168.61 6384
139.199.168.61:6379> cluster meet 139.199.168.61 6380
OK
139.199.168.61:6379> cluster meet 139.199.168.61 6381
OK
139.199.168.61:6379> cluster meet 139.199.168.61 6382
OK
139.199.168.61:6379> cluster meet 139.199.168.61 6383
OK
139.199.168.61:6379> cluster meet 139.199.168.61 6384
OK

cluster meet 兩個節(jié)點互相感知對方存在,發(fā)起節(jié)點發(fā)送發(fā)送Gossip協(xié)議中的meet消息給接收節(jié)點贼邓,接收節(jié)點收到meet消息后阶冈,保存發(fā)起節(jié)點的信息,然后通過返回pong消息把自己的信息也返回回去塑径,之后兩個節(jié)點會定期ping/pong進行節(jié)點通信女坑。我們可以把它理解為把某個節(jié)點拉到一個集群里面,如果把其它節(jié)點也拉進來以后统舀,集群里面的節(jié)點兩兩之間都會互相握手匆骗。等所有節(jié)點都拉到集群以后,我們可以執(zhí)行cluster nodes來查看集群中節(jié)點間的關系誉简。

139.199.168.61:6379> cluster nodes
8ba45af25feef061507831ca1b3ddf71a7574631 10.104.90.159:6379 myself,master - 0 0 1 connected
0573105a355722bc6dd5ab29dea072ce1a6956df 139.199.168.61:6381 master - 0 1540711564922 2 connected
a08f700001a5902dd82b51eb74b4ec8028202d75 139.199.168.61:6382 master - 0 1540711562919 3 connected
dc7a392e05e8b9840164bb21ef662168e28d71b4 139.199.168.61:6380 master - 0 1540711563919 0 connected
ba7816cc7ed0f5c0360708048a68c29b6bf66193 139.199.168.61:6383 master - 0 1540711565924 4 connected
d0585a4fda8ab743bd5b3448f26f46f1e09c19c9 139.199.168.61:6384 master - 0 1540711561916 5 connected
  1. 分配槽
    以上只是建立了一個集群碉就,但是其實集群還不能工作,可以用cluster info來查看集群狀態(tài):
139.199.168.61:6379> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_sent:288
cluster_stats_messages_received:288

可以看到此時集群的狀態(tài)是fail闷串,失敗的瓮钥,我們需要把這16383個槽分出去,集群才能正常工作烹吵,分配槽的命令如下:
/usr/local/redis-3.2.6/src/redis-cli -h 139.199.168.61 -p 6379 cluster addslots {0..5461}
/usr/local/redis-3.2.6/src/redis-cli -h 139.199.168.61 -p 6380 cluster addslots {5462..10922}
/usr/local/redis-3.2.6/src/redis-cli -h 139.199.168.61 -p 6381 cluster addslots {10923..16383}
PS:注意0和5641之間隔的是兩個點碉熄,因為看書上寫的是三個點,會報(error) ERR Invalid or out of range slot的錯誤年叮。
這樣子就把所有的槽都分出去了具被,但是只用到了三個節(jié)點,剩下三個節(jié)點我們可以作為從節(jié)點只损,可以使用cluster replicate 主節(jié)點id來把某個節(jié)點掛為某個節(jié)點的從節(jié)點一姿。

139.199.168.61:6382> cluster replicate 8ba45af25feef061507831ca1b3ddf71a7574631
139.199.168.61:6383> cluster replicate a08f700001a5902dd82b51eb74b4ec8028202d75
139.199.168.61:6384> cluster replicate 0573105a355722bc6dd5ab29dea072ce1a6956df

最后我們來看一下節(jié)點狀態(tài):

139.199.168.61:6379> cluster info
cluster_state:ok  #狀態(tài)OK
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_sent:7325
cluster_stats_messages_received:7325

再來查看一下節(jié)點關系:

139.199.168.61:6379> cluster nodes
8ba45af25feef061507831ca1b3ddf71a7574631 10.104.90.159:6379 myself,master - 0 0 1 connected 0-5461
0573105a355722bc6dd5ab29dea072ce1a6956df 139.199.168.61:6381 master - 0 1540714889809 2 connected 10923-16383
a08f700001a5902dd82b51eb74b4ec8028202d75 139.199.168.61:6382 slave 8ba45af25feef061507831ca1b3ddf71a7574631 0 1540714890811 3 connected
dc7a392e05e8b9840164bb21ef662168e28d71b4 139.199.168.61:6380 master - 0 1540714885803 0 connected 5462-10922
ba7816cc7ed0f5c0360708048a68c29b6bf66193 139.199.168.61:6383 slave dc7a392e05e8b9840164bb21ef662168e28d71b4 0 1540714891813 4 connected
d0585a4fda8ab743bd5b3448f26f46f1e09c19c9 139.199.168.61:6384 slave 0573105a355722bc6dd5ab29dea072ce1a6956df 0 1540714888807 5 connected

節(jié)點id,節(jié)點ip/端口跃惫,是否是主節(jié)點叮叹,節(jié)點的槽位分配一覽無余。至此爆存,一個完整的redis cluster集群創(chuàng)建成功蛉顽。

節(jié)點通信(Gossip協(xié)議)

Gossip協(xié)議

常用的Gossip消息可分為:ping消息、pong消息先较、meet消息携冤、fail消息悼粮。


  • meet消息 用于通知新節(jié)點加入。消息發(fā)送者通知接收者加入到當前集群曾棕,meet消息通信正常完成后扣猫,接收節(jié)點會加入到集群中并進行周期性的ping、pong消息交換翘地。
  • ping消息 集群內每個節(jié)點每秒向多個其他節(jié)點發(fā)送ping消息申尤,用于檢測節(jié)點是否在線和交換彼此狀態(tài)信息,ping消息發(fā)送封裝了自身節(jié)點和部分其他節(jié)點的狀態(tài)數(shù)據(jù)衙耕。
  • pong消息 當接收到ping昧穿、meet消息時,作為響應消息回復給發(fā)送方確認消息正常通信橙喘。pong消息內部封裝了自身狀態(tài)數(shù)據(jù)时鸵。節(jié)點也可以向集群內廣播自身的pong消息來通知整個集群對自身狀態(tài)進行更新。
  • fail消息 當節(jié)點判定集群內另一個節(jié)點下線時渴杆,會向集群內廣播一個fail消息寥枝,其他節(jié)點接收到fail消息之后把對應節(jié)點更新為下線狀態(tài)宪塔。
通信過程

我們知道集群中的節(jié)點為了交換自身的槽位信息磁奖,節(jié)點與節(jié)點之間會不停的進行通信。通信采用Gossip協(xié)議某筐,工作原理是節(jié)點彼此不停的通信交換信息比搭,一段時間后所有的節(jié)點都會知道集群的完整信息,有點類似流言傳播南誊。節(jié)點ping其它節(jié)點的時候身诺,也會把其它節(jié)點的信息帶上,接收方會記錄這些節(jié)點的信息抄囚,然后再向這些節(jié)點發(fā)送ping信息霉赡。

槽位信息在哪里?
typedef struct {
  char sig[4]; /* 信號標示 */
  uint32_t totlen; /* 消息總長度 */
  uint16_t ver; /* 協(xié)議版本*/
  uint16_t type; /* 消息類型,用于區(qū)分meet,ping,pong等消息 */
  uint16_t count; /* 消息體包含的節(jié)點數(shù)量幔托,僅用于meet,ping,ping消息類型*/
  uint64_t currentEpoch; /* 當前發(fā)送節(jié)點的配置紀元 */
  uint64_t configEpoch; /* 主節(jié)點/從節(jié)點的主節(jié)點配置紀元 */
  uint64_t offset; /* 復制偏移量 */
  char sender[CLUSTER_NAMELEN]; /* 發(fā)送節(jié)點的nodeId */
  unsigned char myslots[CLUSTER_SLOTS/8]; /* 發(fā)送節(jié)點負責的槽信息 */
  char slaveof[CLUSTER_NAMELEN]; /* 如果發(fā)送節(jié)點是從節(jié)點穴亏,記錄對應主節(jié)點的nodeId */
  uint16_t port; /* 端口號 */
  uint16_t flags; /* 發(fā)送節(jié)點標識,區(qū)分主從角色,是否下線等 */
  unsigned char state; /* 發(fā)送節(jié)點所處的集群狀態(tài) */
  unsigned char mflags[3]; /* 消息標識 */
  union clusterMsgData data /* 消息正文 */;
} clusterMsg;

我們來看一下消息的格式重挑,這里面有個myslots的char數(shù)組嗓化,長度為16383/8,這其實是一個bitmap,每一個位代表一個槽谬哀,如果該位為1刺覆,表示這個槽是屬于這個節(jié)點的。節(jié)點計算出某個key的槽位以后史煎,只需要對比一下這個bitmap的第幾個位是否是1谦屑,如果是1則它可以處理這個key驳糯,如果不是則查找一下其他節(jié)點的myslots,直到找到匹配的節(jié)點氢橙,然后把節(jié)點信息返回給客戶端结窘。

redis cluster 的伸縮

redis cluster 的伸縮實際就是槽在各個節(jié)點之間的轉移。

smart客戶端

redis-cli

現(xiàn)在來做一個實例充蓝,打開redis-cli隧枫,連接6379,如果處理一個不屬于這個節(jié)點的key:

139.199.168.61:6379> set name 11
(error) MOVED 5798 139.199.168.61:6380

可以看到節(jié)點6379返回一個重定向指令谓苟,name這個key的槽為5798官脓,這個槽在139.199.168.61:6380這臺服務器上。我們再去6380試試涝焙,可以看到可以正常處理卑笨。

139.199.168.61:6380> set name 11
OK

如果你想客戶端自己幫我們重定向,可以在啟動客戶端的時候 加上 -c:

[root@VM_90_159_centos redis-3.2.6]# /usr/local/redis-3.2.6/src/redis-cli -h 139.199.168.61 -p 6379 -c
139.199.168.61:6379> set name 14
-> Redirected to slot [5798] located at 139.199.168.61:6380
OK
JedisCluster

先上一份Java JedisCluster 的代碼:

        Set<HostAndPort> jedisClusterNode = new HashSet<>();
        //添加節(jié)點信息
        jedisClusterNode.add(new HostAndPort("139.199.168.61", 6379));
        jedisClusterNode.add(new HostAndPort("139.199.168.61", 6380));
        jedisClusterNode.add(new HostAndPort("139.199.168.61", 6381));
        jedisClusterNode.add(new HostAndPort("139.199.168.61", 6382));
        jedisClusterNode.add(new HostAndPort("139.199.168.61", 6383));
        jedisClusterNode.add(new HostAndPort("139.199.168.61", 6384));
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        //初始化JedisCluster
        JedisCluster jedisCluster = new JedisCluster(jedisClusterNode, 1000, 1000, 5, poolConfig);
        logger.debug("name = {}", jedisCluster.get("name"));
        jedisCluster.close();

前面我們說過仑撞,客戶端也會保存一份槽與節(jié)點的映射關系圖赤兴,當執(zhí)行某個命令的時候,也會計算CRC16(key) & 16383得到槽的位置隧哮,然后從映射關系中找到對應的節(jié)點信息桶良,再向節(jié)點發(fā)送請求,如果節(jié)點信息返回的是moved指令沮翔,它會重新更新映射關系陨帆。那么這份映射關系保存在哪里呢?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末采蚀,一起剝皮案震驚了整個濱河市疲牵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌榆鼠,老刑警劉巖纲爸,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妆够,居然都是意外死亡识啦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門责静,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袁滥,“玉大人,你說我怎么就攤上這事灾螃√夥” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嵌赠。 經(jīng)常有香客問我塑荒,道長,這世上最難降的妖魔是什么姜挺? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任齿税,我火速辦了婚禮,結果婚禮上炊豪,老公的妹妹穿的比我還像新娘凌箕。我一直安慰自己,他們只是感情好词渤,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布牵舱。 她就那樣靜靜地躺著,像睡著了一般缺虐。 火紅的嫁衣襯著肌膚如雪芜壁。 梳的紋絲不亂的頭發(fā)上饼丘,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天叽奥,我揣著相機與錄音徘郭,去河邊找鬼嗦锐。 笑死,一個胖子當著我的面吹牛涕滋,可吹牛的內容都是我干的剪菱。 我是一名探鬼主播犯祠,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼紊浩,長吁一口氣:“原來是場噩夢啊……” “哼窖铡!你這毒婦竟也來了?” 一聲冷哼從身側響起坊谁,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎滑臊,沒想到半個月后口芍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡雇卷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年鬓椭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片关划。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡小染,死狀恐怖,靈堂內的尸體忽然破棺而出贮折,到底是詐尸還是另有隱情裤翩,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布调榄,位于F島的核電站踊赠,受9級特大地震影響呵扛,放射性物質發(fā)生泄漏。R本人自食惡果不足惜筐带,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一今穿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伦籍,春花似錦蓝晒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至富蓄,卻和暖如春剩燥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背立倍。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工灭红, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人口注。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓变擒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寝志。 傳聞我的和親對象是個殘疾皇子娇斑,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348