什么是Redis Cluster
Redis Cluster让多个Redis节点以集群的方式存储数据,数据会自动分片保存。
在提供分区的情况下,Redis Cluster可以保证一定程度的可用性,即在一个节点挂掉的情况下,整个集群依然可以正常提供服务。但是出现大多数master节点都挂的情况时,整个集群也会一起挂掉。
综上,Redis Cluster提供了两个特性:
1、数据的自动分区存储
2、高可用性。即部分节点挂掉不影响集群的可用性。
TCP端口
每个Redis Cluster节点会占用两个TCP端口,一个监听客户端的请求,默认是6379,另外一个在前一个端口加上10000,比如16379,来监听数据的请求。
节点和节点之间会监听第二个端口,用一套二进制协议来通信。
节点之间会通过套协议来进行失败检测,配置更新,failover认证等等。
为了保证节点之间正常的访问,需要注意防火墙的配置。
数据分片
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽的方式来分配。
Redis Cluster默认分配了16384个槽,在分配的时候,会采用根据CRC16(key) % 16384的计算结果来将数据分配到节点上。
Redis Cluster中的每个节点会负责存储数据槽的一个子集,假设有三个节点
- A节点存储0到5500
- B节点存储5501到11000
- C节点存储11001到16383
根据上面的公式计算的结果,如果大于0小于5500,那么数据将被存储到A节点上。
这样的特性使得增加或者删除节点非常得容易。假如要加入节点D,只需要将A,B,C的部分槽转移到D上;同理,要删除C,只需要将C的槽转移到A和B上,当C节点的数据被转移,清空后,就可以删除C节点了。
在移动哈希槽的时候,不需要停机维护。
如果一个指令涉及到多个key时,只要这些key属于同一个槽,Redis Cluster允许同时操作他们。用户可以通过hash tags强制将多个key归到一个槽中。
主从模式
Redis Cluster支持主从模式,从节点会保存主节点的全部数据
在上面的例子中,假设B节点挂了,那么5501-11000的槽将不能够提供服务。
现在给上面的节点添加一个从节点,那么我们将有A,B,C,A1,B1,C1六个节点。
当B节点挂掉,B1将会升级为主节点,系统的可用性将不受到影响。
但是,如果B和B1节点同时挂掉的话,集群依然会挂掉。
数据一致性
Redis Cluster不会保证数据的强一致性。在部分场景下可能会出现数据丢失的现象。
会丢数据的第一个原因是Redis Cluster中主节点异步向从节点拷贝数据。
在客户写入数据时,会经过下面的过程。
- 写到主节点B
- 主节点B返回"OK"
- 主节点将数据复制到从节点B1,B2,B3
可以看到,主节点B在向客户返回"OK"的之前,并不会等待B1,B2,B3确认写入的回应。这样主要是为了性能的考虑,不能让客户等待太长时间。
假设一种情况,client写入数据,节点B确认这次写入,但是在步骤3之前挂掉了,B1成为了新的主节点。那么这次写入就永远失效了。
可以看到Redis的高性能也是需要一定的代价的。Redis Cluster采取的方案实际上是数据一致性和高性能的折衷。
Redis Cluster支持同步写入,见WAIT操作,这样会降低丢数据的可能性,但是在复杂的情况下,还是可能出现从节点没有接到写入,然后成为主节点。
假设有A,B,C,A1,B1,C1六个节点,三主三从,现在有一个客户Z1。
现在网络出现隔离,A,C,A1,B1,C1之间是联通的;Z1和B是联通的。Z1向B写入成功,如果网络很快恢复,那么一切正常,但是没有很快恢复的情况下,B1会成为新的主节点,Z1向B的写入就丢失了。
上面说了这么多,总结一句话,
<strong><big>即使有了集群,也不能将Redis用做高可靠性的存储。</big></strong>
部署相关
配置选项
- cluster-enabled<yes/no> 如果设置为yes,redis实例会开启cluster模式;如果设置为no,standalone模式
- cluster-config-file<filename>
- cluster-node-timeout<milliseconds> 一个节点被判定为挂掉的最大时间
- **cluster-slave-validity-factor <factor>
**:如果设置为0,从节点会一直failover主节点 - **cluster-migration-barrier <count>
**:主节点对应最小从节点的个数 - **cluster-require-full-coverage <yes/no>
**:如果设为yes,一旦部分哈希槽不能访问到,集群会停止接受写入;如果设为no,即使在部分哈希槽不能被访问到的情况下,集群依然后接受写入。
创建集群
我用的是mac系统,其他类unix系统应该差不多。
先装一些工具软件,下载一下源码。redis的使用的第三方库的源码都包含在项目中了,这简直是编译安装的福音。
brew install ruby
brew install gem
gem install redis
git clone https://github.com/antirez/redis
cd redis
make
然后进入目录utils/create-cluster,执行命令
./create-cluster start
看到输出,建立了6个单独的实例。
Starting 30001
Starting 30002
Starting 30003
Starting 30004
Starting 30005
Starting 30006
执行命令
./create-cluster create
看到输出
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:30001
127.0.0.1:30002
127.0.0.1:30003
Adding replica 127.0.0.1:30004 to 127.0.0.1:30001
Adding replica 127.0.0.1:30005 to 127.0.0.1:30002
Adding replica 127.0.0.1:30006 to 127.0.0.1:30003
M: 6687e151e76830f8544ab9527bbb65b8d9063de4 127.0.0.1:30001
slots:0-5460 (5461 slots) master
M: ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 127.0.0.1:30002
slots:5461-10922 (5462 slots) master
M: 4a808670aae774e49343aac8526b641d5aa79de8 127.0.0.1:30003
slots:10923-16383 (5461 slots) master
S: cee189bce870d87c318840a1e6a2d5045806acba 127.0.0.1:30004
replicates 6687e151e76830f8544ab9527bbb65b8d9063de4
S: 68838ae12c64ccfccfe55b607a99454678d35ea4 127.0.0.1:30005
replicates ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0
S: 075ced4cef8e8ec06076b5193efb81812d72dc1b 127.0.0.1:30006
replicates 4a808670aae774e49343aac8526b641d5aa79de8
Can I set the above configuration? (type 'yes' to accept): yes
输入yes后,建立集群
可以看到30001,30002,30003作为主节点
30004,30005,30006分别作为其从节点。
执行脚本
来看一下代码
start
在执行start指令的时候,实际上是执行了下面的操作。
../../src/redis-server --port $PORT --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
可以看到##配置选项里面项目都设置了一个值,来看一下--cluster-config-file这个配置里的文件长啥样。挑一个主节点看看
cat nodes-30003.conf
ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 127.0.0.1:30002 master - 0 1473069793428 2 connected 5461-10922
4a808670aae774e49343aac8526b641d5aa79de8 127.0.0.1:30003 master - 0 1473069793529 3 connected 10923-16383
075ced4cef8e8ec06076b5193efb81812d72dc1b 127.0.0.1:30006 slave 4a808670aae774e49343aac8526b641d5aa79de8 0 1473069793428 6 connected
cee189bce870d87c318840a1e6a2d5045806acba 127.0.0.1:30004 slave 6687e151e76830f8544ab9527bbb65b8d9063de4 0 1473069793428 4 connected
6687e151e76830f8544ab9527bbb65b8d9063de4 127.0.0.1:30001 myself,master - 0 0 1 connected 0-5460
68838ae12c64ccfccfe55b607a99454678d35ea4 127.0.0.1:30005 slave ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 0 1473069793024 5 connected
vars currentEpoch 6 lastVoteEpoch 0
再挑一个从节点看看
cat nodes-30006.conf
4a808670aae774e49343aac8526b641d5aa79de8 127.0.0.1:30003 master - 0 1473069792576 3 connected 10923-16383
cee189bce870d87c318840a1e6a2d5045806acba 127.0.0.1:30004 myself,slave 6687e151e76830f8544ab9527bbb65b8d9063de4 0 0 4 connected
68838ae12c64ccfccfe55b607a99454678d35ea4 127.0.0.1:30005 slave ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 0 1473069793485 5 connected
ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 127.0.0.1:30002 master - 0 1473069793079 2 connected 5461-10922
075ced4cef8e8ec06076b5193efb81812d72dc1b 127.0.0.1:30006 slave 4a808670aae774e49343aac8526b641d5aa79de8 0 1473069792578 3 connected
6687e151e76830f8544ab9527bbb65b8d9063de4 127.0.0.1:30001 master - 0 1473069793079 1 connected 0-5460
vars currentEpoch 6 lastVoteEpoch 0
可以看到这里面维护了一个状态表,纪录了所有节点的
信息,状态等等。这个文件会将集群的信息保存下来,用于集群下次的启动,根据节点收到的信息,这个文件会经常改变,并保存。
create
执行create指令时,实际上是执行了下面的操作
../../src/redis-trib.rb create --replicas 1 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006
redis-trib.rb是redis作者写的一个工具。
用用看
redis-cli -c -p 30001
127.0.0.1:30001> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:30003
OK
127.0.0.1:30003> set hello world
-> Redirected to slot [866] located at 127.0.0.1:30001
OK
127.0.0.1:30001> get foo
-> Redirected to slot [12182] located at 127.0.0.1:30003
"bar"
127.0.0.1:30003> get hellp
-> Redirected to slot [8380] located at 127.0.0.1:30002
(nil)
127.0.0.1:30002> get hello
-> Redirected to slot [866] located at 127.0.0.1:30001
"world"
127.0.0.1:30001>
可以看到,集群会根据key值所在的槽重定向至对应的节点。