Redis Cluster集群总结性梳理

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

前面已经介绍了Redis Cluster集群及其部署过程,下面再补充下有关Redis Cluster应用原理部分内容,以便更加深刻透彻地理解Redis Cluster。

一、Redis Cluster集群最核心的三个目标

  • 性能:这是Redis赖以生存的看家本领,增加集群功能后当然不能对性能产生太大影响,所以Redis采取了P2P而非Proxy方式、异步复制、客户端重定向等设计,而牺牲了部分的一致性、使用性。
  • 水平扩展:集群的最重要能力当然是扩展,文档中称可以线性扩展到1000结点。
  • 可用性:在Cluster推出之前,可用性要靠Sentinel保证。有了集群之后也自动具有了Sentinel的监控和自动Failover能力。

二、Redis架构变化与CAP理论
        Redis Cluster集群功能推出已经有一段时间了。在单机版的Redis中,每个Master之间是没有任何通信的,所以我们一般在Jedis客户端或者Codis这样的代理中做Pre-sharding。按照CAP理论来说,单机版的Redis属于保证CP(Consistency & Partition-Tolerancy)而牺牲A(Availability),也就说Redis能够保证所有用户看到相同的数据一致性,因为Redis不自动冗余数据)和网络通信出问题时,暂时隔离开的子系统能继续运行分区容忍性,因为Master之间没有直接关系,不需要通信),但是不保证某些结点故障时,所有请求都能被响应可用性,某个Master结点挂了的话,那么它上面分片的数据就无法访问了)。

        有了Cluster功能后,Redis从一个单纯的NoSQL内存数据库变成了分布式NoSQL数据库,CAP模型也从CP变成了AP。也就是说,通过自动分片和冗余数据,Redis具有了真正的分布式能力,某个结点挂了的话,因为数据在其他结点上有备份,所以其他结点顶上来就可以继续提供服务,保证了Availability。然而,也正因为这一点,Redis无法保证曾经的强一致性了。这也是CAP理论要求的,三者只能取其二。

三、Redis Cluster集群部署

1)集群部署和配置

这个之前已经介绍过了,部署过程参考:http://www.cnblogs.com/kevingrace/p/7846324.html
要想开启Redis Cluster模式,有几项配置是必须的,还可以额外添加一些配置:

  • 绑定地址:bind 192.168.XXX.XXX。       不能绑定到127.0.0.1或localhost,否则指导客户端重定向时会报”Connection refused”的错误。
  • 开启Cluster:cluster-enabled yes
  • 集群配置文件:cluster-config-file nodes-7000.conf。     这个配置文件不是要我们去配的,而是Redis运行时保存配置的文件,所以我们也不可以修改这个文件。
  • 集群超时时间:cluster-node-timeout 15000。       结点超时多久则认为它宕机了。
  • 槽是否全覆盖:cluster-require-full-coverage no。    默认是yes,只要有结点宕机导致16384个槽没全被覆盖,整个集群就全部停止服务,所以一定要改为no
  • 后台运行:daemonize yes
  • 输出日志:logfile “./redis.log”
  • 监听端口:port 7000

配置好后,根据集群规模,拷贝出来几份同样的配置文件,唯一不同的就是监听端口,可以依次改为7001、7002… 因为Redis Cluster如果数据冗余是1的话,至少要3个Master和3个Slave,所以可以拷贝出6个实例的配置文件。为了避免相互影响,为6个实例的配置文件建立独立的文件夹。

2)redis-trib管理器
redis-trib依赖Ruby和RubyGems,以及redis扩展。可以先用which命令查看是否已安装ruby和rubygems,用gem list –local查看本地是否已安装redis扩展。
最简便的方法就是用apt或yum包管理器安装RubyGems后执行gem install redis。如果网络或环境受限的话,可以手动安装RubyGems和redis扩展(可以从CSDN下载):

1
2
3
4
5
6
7
8
9
[root@8gVm Software] # wget https://github.com/rubygems/rubygems/releases/download/v2.2.3/rubygems-2.2.3.tgz
[root@8gVm Software] # tar xzvf rubygems-2.2.3.tgz
[root@8gVm Software] # cd rubygems-2.2.3
[root@8gVm rubygems-2.2.3] # ruby setup.rb --no-rdoc --no-ri
 
[root@8gVm Software] # wget https://rubygems.org/downloads/redis-3.2.1.gem
[root@8gVm Software] # gem install redis-3.2.1.gem --local --no-rdoc --no-ri
Successfully installed redis-3.2.1
1 gem installed

3)集群建立
首先,启动配置好的6个Redis实例。

1
[root@8gVm redis-3.0.4] # for ((i=0; i<6; ++i));docd cfg-cluster/700$i && ../../src/redis-server redis.conf.700$i && cd -;done

此时6个实例还没有形成集群,现在用redis-trb.rb管理脚本建立起集群。可以看到,redis-trib默认用前3个实例作为Master,后3个作为Slave。因为Redis基于Master-Slave做数据备份,而非像Cassandra或Hazelcast一样不区分结点角色,自动复制并分配Slot的位置到各个结点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@8gVm redis-3.0.4] # src/redis-trib.rb create --replicas 1 192.168.1.100:7000 192.168.1.100:7001 192.168.1.100:7002 192.168.1.100:7003 192.168.1.100:7004 192.168.1.100:7005
>>> Creating cluster
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
Connecting to node 192.168.1.100:7005: OK
>>> Performing  hash  slots allocation on 6 nodes...
Using 3 masters:
192.168.1.100:7000
192.168.1.100:7001
192.168.1.100:7002
Adding replica 192.168.1.100:7003 to 192.168.1.100:7000
Adding replica 192.168.1.100:7004 to 192.168.1.100:7001
Adding replica 192.168.1.100:7005 to 192.168.1.100:7002
     ...
Can I  set  the above configuration? ( type  'yes'  to accept):  yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to  join  the cluster
Waiting  for  the cluster to  join ....
>>> Performing Cluster Check (using node 192.168.1.100:7000)
     ...
[OK] All nodes agree about slots configuration.
>>> Check  for  open  slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

至此,Redis Cluster集群就已经建立成功了!“贴心”的Redis还在utils/create-cluster下提供了一个create-cluster脚本,能够创建出一个集群,类似上面建立起的3主3从的集群。

4)Redis Cluster集群简单测试
连接到集群中的任意一个结点,启动redis-cli时要加-c选项,存取两个Key-Value感受一下Redis久违的集群功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000
192.168.1.100:7000>  set  foo bar
-> Redirected to slot [12182] located at 192.168.1.100:7002
OK
192.168.1.100:7002>  set  hello world
-> Redirected to slot [866] located at 192.168.1.100:7000
OK
192.168.1.100:7000> get foo
-> Redirected to slot [12182] located at 192.168.1.100:7002
"bar"
192.168.1.100:7002> get hello
-> Redirected to slot [866] located at 192.168.1.100:7000
"world"

仔细观察能够注意到,redis-cli根据指示,不断在7000和7002结点之前重定向跳转。如果启动时不加-c选项的话,就能看到以错误形式显示出的MOVED重定向消息。

1
2
3
[root@8gVm redis-3.0.4] # src/redis-cli -h 192.168.1.100 -p 7000
192.168.1.100:7000> get foo
(error) MOVED 12182 192.168.1.100:7002

5)Redis Cluster 集群重启
目前redis-trib的功能还比较弱,需要重启集群的话,需要先手动kill掉各个进程,然后重新启动就可以了。这确实有点太傻X, 网上有人反馈说重启有问题,不过本人暂时还没遇到问题。

1
[root@8gVm redis-3.0.4] # ps -ef | grep redis|grep -v grep | awk '{print $2}' | xargs kill -9

6)Redis Cluster集群数据迁移
这就需要体验一下Redis集群的Resharding功能了~~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
1)创建测试数据
首先保存foo1~10共10个Key-Value作为测试数据。
[root@8gVm redis-3.0.4] # for ((i=0; i<10; ++i))
do
> src /redis-cli  -c -h 192.168.1.100 -p 7000  set  foo$i bar
done
 
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000
192.168.1.100:7000> keys *
1)  "foo6"
2)  "foo7"
3)  "foo3"
4)  "foo2"
192.168.1.100:7000> get foo4
-> Redirected to slot [9426] located at 192.168.1.100:7001
"bar"
192.168.1.100:7001> keys *
1)  "foo4"
2)  "foo8"
192.168.1.100:7001> get foo5
-> Redirected to slot [13555] located at 192.168.1.100:7002
"bar"
192.168.1.100:7002> keys *
1)  "foo5"
2)  "foo1"
3)  "foo10"
4)  "foo9"
<br>
2)启动新结点
参照之前的方法新拷贝出两份redis.conf配置文件redis.conf.7010和7011,与之前结点的配置文件做一下区分。启动新的两个Redis实例之后,通过redis-trib.rb脚本添加新的Master和Slave到集群中。
[root@8gVm redis-3.0.4] # cd cfg-cluster/7010 && ../../src/redis-server redis.conf.7010 && cd -
[root@8gVm redis-3.0.4] # cd cfg-cluster/7011 && ../../src/redis-server redis.conf.7011 && cd -
 
 
3)添加到集群
使用redis-trib.rb add-node分别将两个新结点添加到集群中,一个作为Master,一个作为其Slave。
[root@8gVm redis-3.0.4] # src/redis-trib.rb add-node 192.168.1.100:7010 192.168.1.100:7000
>>> Adding node 192.168.1.100:7010 to cluster 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7005: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
>>> Performing Cluster Check (using node 192.168.1.100:7000)
     ...
[OK] All nodes agree about slots configuration.
>>> Check  for  open  slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Connecting to node 192.168.1.100:7010: OK
>>> Send CLUSTER MEET to node 192.168.1.100:7010 to  make  it  join  the cluster.
[OK] New node added correctly.
 
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master - 0 1442452249525 0 connected
     ...
 
[root@8gVm redis-3.0.4] # src/redis-trib.rb add-node --slave --master-id 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7011 192.168.1.100:7000
>>> Adding node 192.168.1.100:7011 to cluster 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7010: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7005: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
>>> Performing Cluster Check (using node 192.168.1.100:7000)
     ...
[OK] All nodes agree about slots configuration.
>>> Check  for  open  slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Connecting to node 192.168.1.100:7011: OK
>>> Send CLUSTER MEET to node 192.168.1.100:7011 to  make  it  join  the cluster.
Waiting  for  the cluster to  join .
>>> Configure node as replica of 192.168.1.100:7010.
[OK] New node added correctly.
 
 
4) Resharding
通过redis-trib.rb reshard可以交互式地迁移Slot。下面的例子将5000个Slot从7000~7002迁移到7010上。也可以通过. /redis-trib .rb reshard <host>:<port> --from <node- id > --to <node- id > --slots -- yes 在程序中自动完成迁移。
[root@8gVm redis-3.0.4] # src/redis-trib.rb reshard 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
Connecting to node 192.168.1.100:7010: OK
Connecting to node 192.168.1.100:7001: OK
Connecting to node 192.168.1.100:7002: OK
Connecting to node 192.168.1.100:7005: OK
Connecting to node 192.168.1.100:7011: OK
Connecting to node 192.168.1.100:7003: OK
Connecting to node 192.168.1.100:7004: OK
>>> Performing Cluster Check (using node 192.168.1.100:7000)
M: b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000
    slots:0-5460 (4128 slots) master
    1 additional replica(s)
M: 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010
    slots:0 (4000 slots) master
    1 additional replica(s)
    ...
[OK] All nodes agree about slots configuration.
>>> Check  for  open  slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots  do  you want to move (from 1 to 16384)? 5000
What is the receiving node ID? 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9
Please enter all the  source  node IDs.
   Type  'all'  to use all the nodes as  source  nodes  for  the  hash  slots.
   Type  'done'  once you entered all the  source  nodes IDs.
Source node  #1:all
 
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master - 0 1442455872019 7 connected 0-1332 5461-6794 10923-12255
b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000 myself,master - 0 0 1 connected 1333-5460
b5ab302f5c2395e3c8194c354a85d02f89bace62 192.168.1.100:7001 master - 0 1442455875022 2 connected 6795-10922
0c565e207ce3118470fd5ed3c806eb78f1fdfc01 192.168.1.100:7002 master - 0 1442455874521 3 connected 12256-16383
 
迁移完成后,查看之前保存的foo1~10的分布情况,可以看到部分Key已经迁移到了新的结点7010上。
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 keys "*"
1)  "foo3"
2)  "foo7"
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7001 keys "*"
1)  "foo4"
2)  "foo8"
3)  "foo0"
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7002 keys "*"
1)  "foo1"
2)  "foo9"
3)  "foo5"
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7010 keys "*"
1)  "foo6"
2)  "foo2"

7)Redis Cluster集群故障转移

在高可用性方面,Redis可算是能够”Auto”一把了!Redis Cluster重用了Sentinel(哨兵)的代码逻辑,不需要单独启动一个Sentinel集群,Redis Cluster本身就能自动进行Master选举和Failover切换。下面我们故意kill掉7010结点,之后可以看到结点状态变成了fail,而Slave 7011被选举为新的Master。

1
2
3
4
5
6
7
8
9
10
11
[root@8gVm redis-3.0.4] # kill 43637
 
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master,fail - 1442456829380 1442456825674 7 disconnected
b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000 myself,master - 0 0 1 connected 1333-5460
b5ab302f5c2395e3c8194c354a85d02f89bace62 192.168.1.100:7001 master - 0 1442456848722 2 connected 6795-10922
0c565e207ce3118470fd5ed3c806eb78f1fdfc01 192.168.1.100:7002 master - 0 1442456846717 3 connected 12256-16383
5a3c67248b1df554fbf2c93112ba429f31b1d3d1 192.168.1.100:7005 slave 0c565e207ce3118470fd5ed3c806eb78f1fdfc01 0 1442456847720 6 connected
99bff22b97119cf158d225c2b450732a1c0d3c44 192.168.1.100:7011 master - 0 1442456849725 8 connected 0-1332 5461-6794 10923-12255
cd305d509c34842a8047e19239b64df94c13cb96 192.168.1.100:7003 slave b2036adda128b2eeffa36c3a2056444d23b548a8 0 1442456848220 4 connected
64b544cdd75c1ce395fb9d0af024b7f2b77213a3 192.168.1.100:7004 slave b5ab302f5c2395e3c8194c354a85d02f89bace62 0 1442456845715 5 connected

尝试查询之前保存在7010上的Key,可以看到7011顶替上来继续提供服务,整个集群没有受到影响。

1
2
3
4
5
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 get foo6
"bar"
[root@8gVm redis-3.0.4] #
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 get foo2
"bar"

通过上面可以知道,用Redis提供的redis-trib或create-cluster脚本能几步甚至一步就建立起一个Redis集群。本篇为了深入了解Redis Cluster的用户,所以要暂时抛开这些方便的工具,完全手动建立一遍上面的3主3从集群。

8)Redis Cluster集群发现:MEET
最开始时,每个Redis实例自己是一个集群,可以通过cluster meet让各个结点互相“握手”。这也是Redis Cluster目前的一个欠缺之处:缺少结点的自动发现功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c :7000 myself,master - 0 0 0 connected
 
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster meet 192.168.1.100 7001
OK
     ...
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster meet 192.168.1.100 7005
OK
 
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
7b953ec26bbdbf67179e5d37e3cf91626774e96f 192.168.1.100:7003 master - 0 1442466369259 4 connected
5d9f14cec1f731b6477c1e1055cecd6eff3812d4 192.168.1.100:7005 master - 0 1442466368659 4 connected
33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 192.168.1.100:7000 myself,master - 0 0 1 connected
63162ed000db9d5309e622ec319a1dcb29a3304e 192.168.1.100:7001 master - 0 1442466371262 3 connected
45baa2cb45435398ba5d559cdb574cfae4083893 192.168.1.100:7002 master - 0 1442466372264 2 connected
cdd5b3a244761023f653e08cb14721f70c399b82 192.168.1.100:7004 master - 0 1442466370261 0 connecte

9)Redis Cluster集群的角色设置(REPLICATE)
结点全部“握手”成功后,就可以用cluster replicate命令为结点指定角色了,默认每个结点都是Master。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7003 cluster replicate 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c
OK
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7004 cluster replicate 63162ed000db9d5309e622ec319a1dcb29a3304e
OK
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7005 cluster replicate 45baa2cb45435398ba5d559cdb574cfae4083893
OK
 
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes
7b953ec26bbdbf67179e5d37e3cf91626774e96f 192.168.1.100:7003 slave 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 0 1442466812984 4 connected
5d9f14cec1f731b6477c1e1055cecd6eff3812d4 192.168.1.100:7005 slave 45baa2cb45435398ba5d559cdb574cfae4083893 0 1442466813986 5 connected
33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 192.168.1.100:7000 myself,master - 0 0 1 connected
63162ed000db9d5309e622ec319a1dcb29a3304e 192.168.1.100:7001 master - 0 1442466814987 3 connected
45baa2cb45435398ba5d559cdb574cfae4083893 192.168.1.100:7002 master - 0 1442466811982 2 connected
cdd5b3a244761023f653e08cb14721f70c399b82 192.168.1.100:7004 slave 63162ed000db9d5309e622ec319a1dcb29a3304e 0 1442466812483 3 connected

10)Redis Cluster的槽指派(ADDSLOTS)
设置好主从关系之后,就可以用cluster addslots命令指派16384个槽的位置了。有点恶心的是,ADDSLOTS命令需要在参数中一个个指明槽的ID,而不能指定范围。这里用Bash 3.0的特性简化了,不然就得用Bash的循环来完成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7000 cluster addslots {0..5000}
OK
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7001 cluster addslots {5001..10000}
OK
[root@8gVm redis-3.0.4] # src/redis-cli -c -h 192.168.1.100 -p 7001 cluster addslots {10001..16383}
OK
 
[root@8gVm redis-3.0.4] # src/redis-trib.rb check 192.168.1.100:7000
Connecting to node 192.168.1.100:7000: OK
   ...
>>> Performing Cluster Check (using node 192.168.1.100:7000)
   ...
[OK] All nodes agree about slots configuration.
>>> Check  for  open  slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

这样就通过手动执行命令得到了与之前一样的集群。

11)Redis Cluster集群的数据迁移(MIGRATE)
真正开始Resharding之前,redis-trib会先在源结点和目的结点上执行cluster setslot <slot> importing和cluster setslot <slot> migrating命令,将要迁移的槽分别标记为迁出中和导入中的状态。然后,执行cluster getkeysinslot获得Slot中的所有Key。最后就可以对每个Key执行migrate命令进行迁移了。槽迁移完成后,执行cluster setslot命令通知整个集群槽的指派已经发生变化。
关于迁移过程中的数据访问,客户端访问源结点时,如果Key还在源结点上就直接操作。如果已经不在源结点了,就向客户端返回一个ASK错误,将客户端重定向到目的结点。

12)Redis Cluster集群内部数据结构
Redis Cluster功能涉及三个核心的数据结构clusterState、clusterNode、clusterLink都在cluster.h中定义。这三个数据结构中最重要的属性就是:clusterState.slots、clusterState.slots_to_keys和clusterNode.slots了,它们保存了三种映射关系:

  • clusterState:集群状态
  • nodes:所有结点
  • migrating_slots_to:迁出中的槽
  • importing_slots_from:导入中的槽
  • slots_to_keys:槽中包含的所有Key,用于迁移Slot时获得其包含的Key
  • slots:Slot所属的结点,用于处理请求时判断Key所在Slot是否自己负责
  • clusterNode:结点信息
  • slots:结点负责的所有Slot,用于发送Gossip消息通知其他结点自己负责的Slot。通过位图方式保存节省空间,16384/8恰好是2048字节,所以槽总数16384不能随意定!
  • clusterLink:与其他结点通信的连接

集群状态,每个节点都保存着一个这样的状态,记录了它们眼中的集群的样子。另外,虽然这个结构主要用于记录集群的属性,但是为了节约资源,有些与节点有关的属性,比如 slots_to_keys 、 failover_auth_count 也被放到了这个结构里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
ypedef struct clusterState {
     ...
     指向当前节点的指针
     clusterNode *myself;  /* This node */
 
     集群当前的状态:是在线还是下线
     int state;            /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */
 
     集群节点名单(包括 myself 节点)
     字典的键为节点的名字,字典的值为 clusterNode 结构
     dict *nodes;          /* Hash table of name -> clusterNode structures */
 
     记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点
     migrating_slots_to[i] = NULL 表示槽 i 未被迁移
     migrating_slots_to[i] = clusterNode_A 表示槽 i 要从本节点迁移至节点 A
     clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
 
     记录要从源节点迁移到本节点的槽,以及进行迁移的源节点
     importing_slots_from[i] = NULL 表示槽 i 未进行导入
     importing_slots_from[i] = clusterNode_A 表示正从节点 A 中导入槽 i
     clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
 
     负责处理各个槽的节点
     例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理
     clusterNode *slots[REDIS_CLUSTER_SLOTS];
 
     跳跃表,表中以槽作为分值,键作为成员,对槽进行有序排序
     当需要对某些槽进行区间(range)操作时,这个跳跃表可以提供方便
     具体操作定义在 db.c 里面
     zskiplist *slots_to_keys;
     ...
} clusterState;
 
节点状态
struct clusterNode {
     ...
     节点标识
     使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
     以及节点目前所处的状态(比如在线或者下线)。
     int flags;      /* REDIS_NODE_... */
 
     由这个节点负责处理的槽
     一共有 REDIS_CLUSTER_SLOTS / 8 个字节长
     每个字节的每个位记录了一个槽的保存状态
     位的值为 1 表示槽正由本节点处理,值为 0 则表示槽并非本节点处理
     比如 slots[0] 的第一个位保存了槽 0 的保存情况
     slots[0] 的第二个位保存了槽 1 的保存情况,以此类推
     unsigned char slots[REDIS_CLUSTER_SLOTS /8 ]; /* slots handled by this node */
 
     指针数组,指向各个从节点
     struct clusterNode **slaves; /* pointers to slave nodes */
 
     如果这是一个从节点,那么指向主节点
     struct clusterNode *slaveof; /* pointer to the master node */
     ...
};
 
/* clusterLink encapsulates everything needed to talk with a remote node. */
clusterLink 包含了与其他节点进行通讯所需的全部信息
typedef struct clusterLink {
     ...
     TCP 套接字描述符
     int fd;                     /* TCP socket  file  descriptor */
 
     与这个连接相关联的节点,如果没有的话就为 NULL
     struct clusterNode *node;   /* Node related to this link  if  any, or NULL */
     ...
} clusterLink;

13)Redis Cluster集群的处理流程全梳理
在单机模式下,Redis对请求的处理很简单。Key存在的话,就执行请求中的操作;Key不存在的话,就告诉客户端Key不存在。然而在集群模式下,因为涉及到请求重定向和Slot迁移,所以对请求的处理变得很复杂,流程如下:

  • 检查Key所在Slot是否属于当前Node?
  • 计算crc16(key) % 16384得到Slot
  • 查询clusterState.slots负责Slot的结点指针
  • 与myself指针比较
  • 若不属于,则响应MOVED错误重定向客户端
  • 若属于且Key存在,则直接操作,返回结果给客户端
  • 若Key不存在,检查该Slot是否迁出中?(clusterState.migrating_slots_to)
  • 若Slot迁出中,返回ASK错误重定向客户端到迁移的目的服务器上
  • 若Slot未迁出,检查Slot是否导入中?(clusterState.importing_slots_from)
  • 若Slot导入中且有ASKING标记,则直接操作
  • 否则响应MOVED错误重定向客户端

14)Redis Cluster集群现实存在的问题
尽管属于无中心化架构一类的分布式系统,但不同产品的细节实现和代码质量还是有不少差异的,就比如Redis Cluster有些地方的设计看起来就有一些“奇葩”和简陋:

  • 不能自动发现:无Auto Discovery功能。集群建立时以及运行中新增结点时,都要通过手动执行MEET命令或redis-trib.rb脚本添加到集群中
  • 不能自动Resharding:不仅不自动,连Resharding算法都没有,要自己计算从哪些结点上迁移多少Slot,然后还是得通过redis-trib.rb操作
  • 严重依赖外部redis-trib:如上所述,像集群健康状况检查、结点加入、Resharding等等功能全都抽离到一个Ruby脚本中了。还不清楚上面提到的缺失功能未来是要继续加到这个脚本里还是会集成到集群结点中?redis-trib也许要变成Codis中Dashboard的角色
  • 无监控管理UI:即便未来加了UI,像迁移进度这种信息在无中心化设计中很难得到
  • 只保证最终一致性:写Master成功后立即返回,如需强一致性,自行通过WAIT命令实现。但对于“脑裂”问题,目前Redis没提供网络恢复后的Merge功能,“脑裂”期间的更新可能丢失
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    ============================================redis cluster  install   ========================================
     
    3 主  3从, 从库交叉存放在 主库上
     
    主:ip 192.168.1.101 6381  从ip 192.168.1.102  6383
     
    主  IP 192.168.1.102  6382  从ip 192.168.1.103   6381
     
    主 ip 192.168.1.103  6383   从 ip 192.168.1.101  6382
     
     
     
    Redis cluster 集群配置文件
     
    创建集群目录
     
    mkdir  /data/redis_data/ {conf,data,logs,temp}
     
     
    101 节点的配置文件
     
    /data/redis_data/conf/redis-6381 .conf
    /data/redis_data/conf/redis-6383 .conf
     
    102 节点配置文件
    /data/redis_data/conf/redis-6382 .conf
    /data/redis_data/conf/redis-6381 .conf
     
    103 节点配置文件
    /data/redis_data/conf/redis-6383 .conf
    /data/redis_data/conf/redis-6382 .conf
     
     
    集群配置文件内容 范例
    =============================
    ################################## NETWORK #####################################
    bind 0.0.0.0
    protected-mode  yes
    port 6382
    tcp-backlog 511
    unixsocket  /data/redis_data/temp/redis .sock
    unixsocketperm 700
    timeout 0
    tcp-keepalive 300
    ################################# GENERAL #####################################
    daemonize  yes
    supervised no
    pidfile  /data/redis_data/temp/redis_6382 .pid
    # debug # verbose# notice # warning
    loglevel notice
    logfile  "/data/redis_data/logs/redis_6382.log"
    syslog-enabled no
    # Specify the syslog identity.
    # syslog-ident redis
    # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
    # syslog-facility local0
    databases 1
    ################################ SNAPSHOTTING  ################################
    #   save <seconds> <changes>
    #save 900 1
    #save 300 10
    #save 60 10000
    stop-writes-on-bgsave-error  yes
    rdbcompression no
    rdbchecksum  yes
    dbfilename dump_6382.rdb
    dir  /data/redis_data/data
    ################################# REPLICATION #################################
    # slaveof 192.168.1.101  6379
    # masterauth <master-password>
    slave-serve-stale-data  yes
    slave- read -only  yes
    repl-diskless- sync  no
    repl-diskless- sync -delay 5
    # repl-ping-slave-period 10
    repl-timeout 60
    repl-disable-tcp-nodelay no
    repl-backlog-size 1mb
    repl-backlog-ttl 3600
    # By default the priority is 100.
    slave-priority 100
    # min-slaves-to-write 3
    # min-slaves-max-lag 10
    # slave-announce-ip 5.5.5.5
    # slave-announce-port 1234
    ################################## SECURITY ###################################
    # requirepass foobared
    # rename-command CONFIG ""
    ################################### LIMITS ####################################
    maxclients 10000
    maxmemory 4294967296
    # maxmemory-policy noeviction
    # maxmemory-samples 5
    ############################## APPEND ONLY MODE ###############################
    appendonly  yes
    appendfilename  "appendonly_6382.aof"
    # appendfsync always
    appendfsync everysec
    # appendfsync no
    no-appendfsync-on-rewrite no
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    aof-load-truncated  yes
    ################################ LUA SCRIPTING  ###############################
    lua- time -limit 5000
    ################################ REDIS CLUSTER  ###############################
    cluster-enabled  yes
    cluster-config- file  nodes-6382.conf
    cluster-node-timeout 15000
    # cluster-migration-barrier 1
    # cluster-require-full-coverage yes
    ################################## SLOW LOG ###################################
    slowlog-log-slower-than 10000
    slowlog-max-len 128
    ################################ LATENCY MONITOR ##############################
    latency-monitor-threshold 0
    ############################# EVENT NOTIFICATION ##############################
    # PUBLISH __keyspace@0__:foo del
    # PUBLISH __keyevent@0__:del foo
    #  notify-keyspace-events Elg
    #  notify-keyspace-events Ex
    notify-keyspace-events  ""
    ############################### ADVANCED CONFIG ###############################
    hash -max-ziplist-entries 512
    hash -max-ziplist-value 64
    list-max-ziplist-size -2
    list-compress-depth 0
    set -max-intset-entries 512
    zset-max-ziplist-entries 128
    zset-max-ziplist-value 64
    hll-sparse-max-bytes 3000
    activerehashing  yes
    client-output-buffer-limit normal 0 0 0
    client-output-buffer-limit slave 256mb 64mb 60
    client-output-buffer-limit pubsub 32mb 8mb 60
    hz 10
    aof-rewrite-incremental-fsync  yes
     
     
     
     
     
     
     
    ==========================
     
    启动节点
     
    redis-server  /data/redis_data/conf/redis-6381 .conf
    redis-server  /data/redis_data/conf/redis-6383 .conf
     
     
    redis-server  /data/redis_data/conf/redis-6382 .conf
    redis-server  /data/redis_data/conf/redis-6381 .conf
     
    redis-server  /data/redis_data/conf/redis-6383 .conf
    redis-server  /data/redis_data/conf/redis-6382 .conf
     
     
     
    ==============================安装 redis-trib.rb====================================
     
     
    安装ruby 2.4.1.tgz
    . /configure  --prefix= /usr/local/ruby
    make
    make  install
    cd  /usr/local/ruby
    cp  ruby  /usr/local/bin/
    cp  gem  /usr/local/bin
     
     
    wget http: //rubygems .org /downloads/redis-3 .3.0.gem
     
    gem  install  -l redis-3.3.0.gem
     
    gem list --check redis gem
     
    cp  /data/software/redis-3 .2.9 /src/redis-trib .rb   /usr/local/bin
     
    redis-trib.rb
    出现相关的资料后就可以了
     
    =============================================集群模式配置=============================================
     
    以下这种方式貌似不能按照自己的思路添加主从
    redis-trib.rb create --replicas 1 192.168.1.101:6381 192.168.1.102:6382   192.168.1.103:6383 192.168.1.102:6381 192.168.1.103:6382   192.168.1.101:6383
     
    思路改为先加主库 再加从库
    添加主库
    redis-trib.rb create  192.168.1.101:6381 192.168.1.102:6382  192.168.1.103:6383
     
    添加从库
    把 102的6381 作为从库加入 101的6381
    redis-trib.rb add-node --slave 192.168.1.102:6381   192.168.1.101:6381
     
    redis-trib.rb add-node --slave 192.168.1.103:6382   192.168.1.102:6382
    redis-trib.rb add-node --slave 192.168.1.101:6383   192.168.1.103:6383
     
    检测
    redis-trib.rb check 192.168.1.101:6381
    redis-trib.rb check 192.168.1.102:6382
    redis-trib.rb check 192.168.1.103:6383
     
    随便链接一个就行了,
***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************

本文转自散尽浮华博客园博客,原文链接:http://www.cnblogs.com/kevingrace/p/7955725.html,如需转载请自行联系原作者
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
存储 缓存 NoSQL
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
redis分布式锁、redisson、可重入、主从一致性、WatchDog、Redlock红锁、zookeeper;Redis集群、主从复制,全量同步、增量同步;哨兵,分片集群,Redis为什么这么快,I/O多路复用模型——用户空间和内核空间、阻塞IO、非阻塞IO、IO多路复用,Redis网络模型
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
|
2月前
|
监控 NoSQL Redis
看完这篇就能弄懂Redis的集群的原理了
看完这篇就能弄懂Redis的集群的原理了
62 0
|
2月前
|
存储 NoSQL 算法
深入理解Redis分片Cluster原理
本文深入探讨了Redis Cluster的分片原理,作为Redis官方提供的高可用性和高性能解决方案,Redis Cluster通过数据分片和横向扩展能力,有效降低单个主节点的压力。
深入理解Redis分片Cluster原理
|
2月前
|
缓存 NoSQL 网络协议
【Azure Redis 缓存】Azure Redis Cluster 在增加分片数时失败分析
【Azure Redis 缓存】Azure Redis Cluster 在增加分片数时失败分析
|
3月前
|
存储 NoSQL 算法
Redis 集群模式搭建
Redis 集群模式搭建
72 5
|
2月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Windows版创建 Redis Cluster 实验 (精简版)
【Azure Redis 缓存】Windows版创建 Redis Cluster 实验 (精简版)
|
3月前
|
存储 缓存 NoSQL
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
|
2月前
|
NoSQL Redis
Redis——单机迁移cluster集群如何快速迁移
Redis——单机迁移cluster集群如何快速迁移
58 0
|
3月前
|
缓存 NoSQL Java
Redis Spring配置集群
【7月更文挑战第5天】
67 10