Redis-cluster
Redis-cluster集群
2017-05-01
版本:redis-3.0.7
环境:CentOS7
参考:https://redis.io/topics/cluster-tutorial
http://www.redis.cn/topics/cluster-tutorial.html
http://hot66hot.iteye.com/blog/2050676
redis 配置参数解释:http://www.tuicool.com/articles/EVRRVjm
初探
目前redis支持的cluster特性(已测试):
1):节点自动发现
2):slave->master 选举,集群容错
3):Hot resharding:在线分片
4):集群管理:cluster xxx
5):基于配置(nodes-port.conf)的集群管理
6):ASK 转向/MOVED 转向机制.
redis cluster 架构
1)redis-cluster架构图
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的master节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key
2) redis-cluster选举:容错
(1)领着选举过程是集群中所有master参与,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作.
(2):什么时候整个集群不可用(cluster_state:fail)?
a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态. ps : redis-3.0.0.rc1加入cluster-require-full-coverage参数,默认关闭,打开集群兼容部分失败.
b:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态.
ps:当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
初步
本文档是Redis集群的一般介绍,没有涉及复杂难懂的分布式概念的赘述,只是提供了从用户角度来如何搭建测试以及使用的方法,如果你打算使用并深入了解Redis集群,推荐阅读完本章节后,仔细阅读 Redis 集群规范 一章。
本教程试图提供最终用户一个简单的关于集群和一致性特征的描述
请注意,本教程使用于Redis3.0(包括3.0)以上版本
如果你计划部署集群,那么我们建议你从阅读这个文档开始。
Redis集群介绍
Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。
Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.
Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令. Redis 集群的优势:
- 自动分割数据到不同的节点上。
- 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。
Redis 集群的数据分片
Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
- 节点 A 包含 0 到 5500号哈希槽.
- 节点 B 包含5501 到 11000 号哈希槽.
- 节点 C 包含11001 到 16384号哈希槽.
这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我像移除节点A,需要将A中得槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.
Redis 集群的主从复制模型
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.
在我们例子中具有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用.
然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了
不过当B和B1 都失败后,集群是不可用的.
Redis 一致性保证
Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作.
第一个原因是因为集群是用了异步复制. 写操作过程:
- 客户端向主节点B写入一条命令.
- 主节点B向客户端回复命令状态.
- 主节点将写操作复制给他得从节点 B1, B2 和 B3.
主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。
举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .
Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.
注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项.
搭建并使用Redis集群
因redis-cluster依赖zlib、ruby、rubygem、gem-redis,前三者可以通过yum 在线安装。
gem-redis安装命令:gem install redis
搭建集群的第一件事情我们需要一些运行在 集群模式的Redis实例. 这意味这集群并不是由一些普通的Redis实例组成的,集群模式需要通过配置启用,开启集群模式后的Redis实例便可以使用集群特有的命令和特性了.
下面是一个最少选项的集群的配置文件:
port 7000
daemonize yes
#用于开启实例的集群模式
cluster-enabled yes
#当前集群节点的配置文件,每个节点都有各自的配置文件,集群模式下,每个redis节点生成一个自己的集群配置文件,这个文件不需要人工修改,由redis自己维护。 cluster-config-file nodes.conf
#这一时间限制称为节点超时时间,在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的(Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立) cluster-node-timeout 5000
#开启aof模式持久化 appendonly yes
文件中的 cluster-enabled 选项用于开启实例的集群模式, 而 cluster-conf-file 选项则设定了保存节点配置文件的路径, 默认值为 nodes.conf.节点配置文件无须人为修改, 它由 Redis 集群在启动时创建, 并在有需要时自动进行更新。
要让集群正常运作至少需要三个主节点,不过在刚开始试用集群功能时, 强烈建议使用六个节点: 其中三个为主节点, 而其余三个则是各个主节点的从节点。
首先, 让我们进入一个新目录, 并创建六个以端口号为名字的子目录, 稍后我们在将每个目录中运行一个 Redis 实例: 命令如下:
mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005
在文件夹 7000 至 7005 中, 各创建一个 redis.conf 文件, 文件的内容可以使用上面的示例配置文件, 但记得将配置中的端口号从 7000 改为与文件夹名字相同的号码。
源码安装redis-3.0.7.tar.gz
$ tar -zxvf redis-3.0.7.tar.gz -C /usr/local/redis-cluster
cd /usr/local/redis-cluster/redis-3.0.7
make PREFIX=/usr/local/redis-cluster/redis-3.0.7 install 这种方式安装的好处是在源码目录下生成一个bin目录,是启动及客户端连接redis的脚本都将从src目录拷贝进bin目录
然后使用类似以下命令, 在每个标签页中打开一个实例:
cd 7000
../redis-3.0.7/bin/redis-server ./redis.conf
实例打印的日志显示, 因为 nodes.conf 文件不存在, 所以每个节点都为它自身指定了一个新的 ID :
[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1
实例会一直使用同一个 ID , 从而在集群中保持一个独一无二(unique)的名字。
搭建集群
现在我们已经有了六个正在运行中的 Redis 实例, 接下来我们需要使用这些实例来创建集群, 并为每个节点编写配置文件。
通过使用 Redis 集群命令行工具 redis-trib , 编写节点配置文件的工作可以非常容易地完成: redis-trib 位于 Redis 源码的 src 文件夹中, 它是一个 Ruby 程序, 这个程序通过向实例发送特殊命令来完成创建新集群, 检查集群, 或者对集群进行重新分片(reshared)等工作。
./redis-3.0.7/src/redis-trib.rb create --replicas 1 192.168.1.221:7000 192.168.1.221:7001 192.168.1.221:7002 192.168.1.221:7003 192.168.1.221:7004 192.168.1.221:7005
这个命令在这里用于创建一个新的集群, 选项–replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
之后跟着的其他参数则是这个集群实例的地址列表,3个master3个slave redis-trib 会打印出一份预想中的配置给你看, 如果你觉得没问题的话, 就可以输入 yes , redis-trib 就会将这份配置应用到集群当中,让各个节点开始互相通讯,具体执行命令得到如下信息:
[sann@node221 redis-cluster]$ ./redis-3.0.7/src/redis-trib.rb create --replicas 1 192.168.1.221:7000 192.168.1.221:7001 192.168.1.221:7002 192.168.1.221:7003 192.168.1.221:7004 192.168.1.221:7005
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.1.221:7000
192.168.1.221:7001
192.168.1.221:7002
Adding replica 192.168.1.221:7003 to 192.168.1.221:7000
Adding replica 192.168.1.221:7004 to 192.168.1.221:7001
Adding replica 192.168.1.221:7005 to 192.168.1.221:7002
M: 5cfaef2da6312f5608cfec337eab12ef138754e9 192.168.1.221:7000
slots:0-5460 (5461 slots) master
M: 545034cd7e2bba538b3caffa563dbfb7358bc545 192.168.1.221:7001
slots:5461-10922 (5462 slots) master
M: 0ca43e9e83e4c60903c599472288b35cde15ea27 192.168.1.221:7002
slots:10923-16383 (5461 slots) master
S: 3493e2c0ea85a280edd33449aafdd042d7a5f495 192.168.1.221:7003
replicates 5cfaef2da6312f5608cfec337eab12ef138754e9
S: 0b9d75179f91eadcd7432e4a9b529ce5e8196f82 192.168.1.221:7004
replicates 545034cd7e2bba538b3caffa563dbfb7358bc545
S: 999cbfa8e0faf4790a5740371fcd90f6388be597 192.168.1.221:7005
replicates 0ca43e9e83e4c60903c599472288b35cde15ea27
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.221:7000)
M: 5cfaef2da6312f5608cfec337eab12ef138754e9 192.168.1.221:7000
slots:0-5460 (5461 slots) master
M: 545034cd7e2bba538b3caffa563dbfb7358bc545 192.168.1.221:7001
slots:5461-10922 (5462 slots) master
M: 0ca43e9e83e4c60903c599472288b35cde15ea27 192.168.1.221:7002
slots:10923-16383 (5461 slots) master
M: 3493e2c0ea85a280edd33449aafdd042d7a5f495 192.168.1.221:7003
slots: (0 slots) master
replicates 5cfaef2da6312f5608cfec337eab12ef138754e9
M: 0b9d75179f91eadcd7432e4a9b529ce5e8196f82 192.168.1.221:7004
slots: (0 slots) master
replicates 545034cd7e2bba538b3caffa563dbfb7358bc545
M: 999cbfa8e0faf4790a5740371fcd90f6388be597 192.168.1.221:7005
slots: (0 slots) master
replicates 0ca43e9e83e4c60903c599472288b35cde15ea27
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
这样redis-cluster集群就启动了。
【如果脚本报错】
custom_require.rb:36:in `require': cannot load such file -- redis (LoadError)
from /usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
from ./redis-trib.rb:25:in `<main>'
那么说明Ruby的redis接口没有安装,可以通过sudo gem install redis 进行安装。
使用集群
Redis 集群现阶段的一个问题是客户端实现很少。
以下是一些我知道的实现:
- redis-rb-cluster 是我(@antirez)编写的 Ruby 实现, 用于作为其他实现的参考。 该实现是对 redis-rb 的一个简单包装, 高效地实现了与集群进行通讯所需的最少语义(semantic).
- redis-py-cluster 看上去是 redis-rb-cluster 的一个 Python 版本, 这个项目有一段时间没有更新了(最后一次提交是在六个月之前), 不过可以将这个项目用作学习集群的起点。
- 使用最多的时java客户端,Jedis 最近添加了对集群的支持, 详细请查看项目README中Jedis Cluster部分.
- thunk-redis 提供对 Node.js 和 io.js的支持。
- Redis unstable 分支中的
redis-cli
程序实现了非常基本的集群支持, 可以使用命令 redis-cli -c 来启动。
测试 Redis 集群比较简单的办法就是使用 redis-rb-cluster 或者 redis-cli , 接下来我们将使用 redis-cli 为例来进行演示:
查看集群目前状况:
192.168.1.221:7000> cluster info
cluster_state: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:6
cluster_my_epoch:1
cluster_stats_messages_sent:14399
cluster_stats_messages_received:14399
$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"
注意: 如果你是使用脚本创建的集群节点,那么默认端口可能是从30001开始。
redis-cli 对集群的支持是非常基本的, 所以它总是依靠 Redis 集群节点来将它转向(redirect)至正确的节点。一个真正的(serious)集群客户端应该做得比这更好: 它应该用缓存记录起哈希槽与节点地址之间的映射(map), 从而直接将命令发送到正确的节点上面。这种映射只会在集群的配置出现某些修改时变化, 比如说, 在一次故障转移(failover)之后, 或者系统管理员通过添加节点或移除节点来修改了集群的布局(layout)之后, 诸如此类。