Redis 最为突出的特性就是:执行命令的速度非常快(原因是所有数据都存放在内存中)。但是单机 Redis 总会遇到瓶颈的,比如:并发、流量、内存等。在 Redis 3.0 之前,官方并没有提供集群方案,在访问量比较大的情况,基本使用的是 Twemproxy、Codis 等集群方案。直到 Redis 3.0,官方推出了 Redis Cluster,实现了切片集群方案。今天就来聊聊 Redis Cluster。
1 虚拟槽
Redis Cluster 采用虚拟槽分区,一个集群有 16384 个虚拟槽,这些虚拟槽类似数据分区,每个键值对都会根据它的 key,按照 CRC16 算法计算一个 16bit 的值,然后再用这个 16bit 的值对 16384 取模,通过模映射到一个虚拟槽中。
Redis Cluster 使用虚拟槽,可以解耦数据和节点之间的关系,大大简化了节点扩缩容的难度,并且重要的是扩缩容不影响数据一致性。
2 集群搭建
2.1 安装 Redis
安装 6 个 Redis 实例(本节的版本为:5.0.7,6 个实例在同一台 Centos7 上部署的,端口分配是:7001 到 7006),Redis 安装可以参考官方文档(https://redis.io/download)。
2.2 修改配置文件
在 Redis 的配置文件中,加入如下参数,开启 Redis 集群功能。
代码语言:javascript复制cluster-enabled yes
cluster-config-file /data/redis{port}/data/nodes{port}.conf
cluster-node-timeout 5000
简单介绍上面三个参数的含义:
- cluster-enabled:是否开启集群模式
- cluster-config-file:集群内部配置文件
- cluster-node-timeout:节点超时时间,单位毫秒
2.3 创建集群
部署 Redis Cluster 方案时,可以使用 cluster create 命令创建集群,此时,Redis 会自动把这些槽平均分布在集群实例上。
当然,如果想自定义每个实例的槽分配,也可以使用 cluster meet 命令手动建立实例间的连接,形成集群,再使用 cluster addslots 命令,指定每个实例上的哈希槽个数(比如集群中配置不统一的场景)。要注意的是,在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作。
本节内容就只讲使用 cluster create 命令创建集群的过程。
首先执行集群创建命令:
代码语言:javascript复制redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
这里的 --cluster-replicas 1 表示每个主节点都分配一个从节点,如果为 2,则表示每个主节点分配两个从节点。 生产环境也建议至少配置一个从节点。
如图,在下方输入 yes 确认
出现如下信息,则表示集群创建完毕
查看集群状态
代码语言:javascript复制redis-cli --cluster check 127.0.0.1:7001
可以看到,槽位分配和主从配置已经自动完成。
3 扩缩容
3.1 扩容
继续安装两个 Redis 节点 7007 和 7008,然后按照 2.2 的方式在配置文件中加入集群相关参数。
增加节点,命令如下:
代码语言:javascript复制redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
127.0.0.1:7007 表示新增节点 127.0.0.1:7001 表示集群中任意节点
查看集群状态
代码语言:javascript复制redis-cli --cluster check 127.0.0.1:7001
发现 127.0.0.1:7007 已经加入到集群中了
再添加从节点:
代码语言:javascript复制redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7001 --cluster-slave --cluster-master-id c9d5817021fe802c094806a79c3c52a37b8b15f5
查看集群状态
代码语言:javascript复制redis-cli --cluster check 127.0.0.1:7001
如图,框中的就是新增加的一组主从。
然后进行槽迁移
代码语言:javascript复制redis-cli --cluster reshard 127.0.0.1:7001
在最后一行,显示要分配多少槽
How many slots do you want to move (from 1 to 16384)?
因为扩容后是 4 个主实例,因此这个位置输入 4096,表示每个实例分配 4096 个槽
随后会出现
其中:
What is the receiving node ID?
这里输入新增节点 7007 的 node-id
Source node #1:
这里输入 all,表示所有节点平均分配
或者手动输入 node-id 分配,然后输入 done 结束
查看集群状态
代码语言:javascript复制redis-cli --cluster check 127.0.0.1:7001
发现会从每个老节点上抽取一些槽,转移到新的节点上。
3.2 缩容
如果需要进行缩容,首先要下线缩容节点的槽,执行以下命令,可以将 7007 节点上的 1364 个槽转移到 7001 节点上
代码语言:javascript复制redis-cli --cluster reshard --cluster-from c9d5817021fe802c094806a79c3c52a37b8b15f5 --cluster-to 99d09213eab4bd707e4923d6574182e23384b765 --cluster-slots 1364 127.0.0.1 7001
按照同样的方式,将其余的槽迁移到 7002 和 7003 两个节点。
查看集群信息,发现 7007 节点没任何槽了
然后删除节点
代码语言:javascript复制redis-cli --cluster del-node 127.0.0.1:7001 c9d5817021fe802c094806a79c3c52a37b8b15f5
该操作会将删除的节点服务关闭。
同样的办法,把从节点也删除
代码语言:javascript复制redis-cli --cluster del-node 127.0.0.1:7001 81d26724de7961a22a93cf64901a68b40c1f90cd
4 数据迁移
其实,Redis Cluster 在进行类似上面的扩缩容操作时,客户端依然可以进行数据改查,那么 Redis Cluster 是怎样实现这一动态扩缩容特性的呢?
这里就来聊聊 Redis Cluster 的迁移原理:
比如需要将 masterA 节点中编号为 1、2、3 的 slot 迁移到 masterB 节点上,在slot 迁移的中间状态,slot 1、2、3 在 masterA 节点的状态表现为 MIGRATING 状态,在 masterB 节点的状态表现为 IMPORTING。
4.1 MIGRATING 状态
如果 key 存在,则成功处理
如果 key 不存在,则返回客户端 ASK,客户端根据 ASK 首先发送 ASKING 命令到目标节点,然后发送请求的命令到目标节点。
假如 key 包含多个命令,如果都存在,则成功处理,如果都不存在,则返回客户端 ASK,如果一部分存在,则返回客户端 TRYAGAIN,通知客户端稍后重试,这样当所有的 key 都迁移完毕的时候,客户端重试请求的时候会得到 ASK,然后经过一次重定向就可以获取这一批键了。
4.2 IMPORTING 状态
正常命令会被 MOVED 重定向,如果是 ASKING 命令,则命令会被执行,从而 key 没在被迁移的节点,已经被迁移到目标节点的情况命令可以被顺利执行;如果 key 不存在,则新建;如果 key 不在该节点上,命令会被 MOVED 重定向,刷新客户端中 node 的映射关系。
5 节点通信
为什么客户端在访问任何一个实例时,都能获得其他槽的数据呢?
因为 Redis 实例会把自己的虚拟槽信息发给同集群下的其它实例,当集群创建完成后,每个实例就有所有哈希槽的映射关系了。
6 故障检测
集群中的每个节点都会定期的向集群中其他节点发送 ping 消息,以此交换各个节点状态信息。
7 集群优势
这里再总结下 Redis cluster 的一些优势
- 无中心架构
- 动态扩缩容:如本节内容,Redis 可支持动态扩缩容
- 高可用性:部分节点不可用时,集群仍可用
8 集群限制
Redis Cluster 与 Redis 单机版相比,存在一些限制,我们在运维或者开发过程,应该提前了解的,这里就总结几点限制:
- key 批量操作支持有限。如 mset、mget,目前只支持具有相同 slot 值的 key 执行批量操作。
- 只支持多 key 在同一个节点上的事务操作
- 不能将一个大的键值对象如 hash、list 等映射到不同的节点
- 只支持 db 0
- 复制结构只支持一层
9 Redis Cluster 建议
尽量避免大 key、热 key,因为这可能导致某一个节点成为系统的短板。
不建议使用事务、避免使用阻塞操作(比如 save、flushall、keys * 等)