十九redis的架构模式
期望架构能满足:
- 高可用
- 内存可扩展
- 机器性能满足:读写
- 网络流量能满足
- 故障自动转移
单机
只有一个master承载整个业务的缓存层。
优点:
- 简单
缺点:
- 存在单点故障
- 读写数据存在性能瓶颈
- 内存有限
主从模式
一主多从
优点:
1. 简单
2. 能够提供高性能的读数据
缺点:
1. 故障不能自动转移
2. 写数据存在瓶颈
3. 内存有限
哨兵模式
在主从模式的基础上,增加哨兵来进行自动故障转移。架构目标是高可用。
优点:
1. 故障自动转移,当master挂了,哨兵可以自动提升slave为master
2. 能够提供高性能的读数据
3. 高可用
缺点:
1. 写数据存在瓶颈
2. 内存有限
redis Cluster模式
多个master共管理16384个槽,每个master都可以配置多个slave节点。架构目标是解决扩展性。
优点:
1. 高可用
2. 故障自动转移
3. 支持横向扩展到1000个节点
缺点:
1. 架构复杂
特点:
1. 所有节点互通互联(ping-pong机制),内部使用二进制协议优化传输速度和带宽
2. 失败节点是用过集群超过半数的节点检测失效时才失败
3. 客户端与节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任意一个可用节点即可
4. redis-cluster把所有物理节点映射到[0-16383]slot上(不一定平均分配)
5. redis集群预分好16384个c槽,当需要在集群中新增一个Key时,根据CRC16(key) & 16384的值,决定将一个key放到那个槽中
6. 一个node可以配置主从模式,当master失败后,会提升slave为master,同一个主从下的所有redis挂掉,整个集群不提供服务
搭建
创建集群
一个redis cluster集群最少需要6个redis实例,其中两两为主从,则3个master节点,3个salve节点。我们在一台机器上进行测试准备6个端口:
7000,7001,7002,7003,7004,7005。
准备安装文件:
- rubygems https://rubygems.org/pages/download
- ruby http://www.ruby-lang.org/en/downloads/
- redis-3.2.2.gem https://rubygems.global.ssl.fastly.net/gems/redis-3.2.2.gem
- openssl http://www.openssl.org/source/
- 安装redis3.0后的版本
- 创建目录存放6个端口的配置文件和日志 mkdir data/cluster 在cluster上创建6个子目录: mkdir 7000 7001 7002 7003 7004 7005
- 修改redis.conf文件,分别复制到/data/cluster/700x目录下
daemonize yes # 后台启动
port 7000 # 修改端口号 从7000~7005
cluster-enabled yes #开启cluster
cluster-config-file nodes-7000.conf # 存储集群的节点信息,cluster操作,不要手动修改
cluster-node-timeout 15000 # 节点最大的超时时间,当大于这个值,节点为响应成功则认为节点失败,后续进行下线判断
# 当节点B,timeout设置的时间很长,可能导致这个时间段内写入B的数据丢失
appendonly yes #开启aof持久化
pidfile /var/run/redis_7000.pid
- 安装ruby环境
4.1 离线下载上传ruby下载包
4.2 解压 tar -zxvf ruby-2.5.0.tar.gz
4.3 安装 make && make install
4.4 检查是否安装成功 ruby -v - 安装rubyGems(ruby包管理器)
5.1 离线下载安装包
5.2 解压 tar -zxvf rubygems-2.7.4.tgz
5.3 安装 ruby setup.rb - 安装openssl
6.1 离线下载上传openssl
6.2 解压 tar -xzvf openssl-1.0.2n.tar.gz
6.3 安装 cd openssl-1.0.2n
6.4 ./config -fPIC --prefix=/usr/local/openssl enable-shared
6.5 ./config -t make && make install
6.6 验证安装是否成功 openssl version - 安装redis-trib.rb运行依赖的ruby的包redis-3.2.2.gem
7.1 离线下载上传redis-3.2.2.gem
7.2 执行命令 gem install /usr/software/redis-3.2.2.gem - 使用redis-trib.rb创建集群
8.1 分别启动7000~7005端口的redis
8.2 /usr/software/redis-4.0.6/src/redis-trib.rb create --replicas 1 192.168.1.0:7000 192.168.1.0:7001 192.168.1.0:7002 192.168.1.0:7003
192.168.1.0:7004 192.168.1.0:7005
这里1表示:节点是一主一从;利用redis-trib创建cluster的操作,只需要一次即可。
8.3 查看集群节点分配情况 ./redis-trib.rb chech 192.168.1.0:7000(通过集群任一节点的ip+port可查看整个集群)
新增节点
- 以7006端口启动一个redis,redis.conf配置文件参考上面的部分
- 7006以master加入cluster集群 ./redis-trib.rd add-node 192.168.1.0:7006 192.168.1.0.7000
192.168.1.0:7000表示7006要加入的集群(7000~7005) - 此时7006加入了集群,但是还没有分配slot给7006
- 分配slot给7006,现在16384个slot给了7000,7001,7002三个节点,需要进行分片迁移:reshard
redis-trib.rb reshard 192.168.1.0:7000 (这个7000可以是7000~7006任一一个用来指定集群) - 接下来cluster会输出信息询问你迁移多少个slot到那个节点: 16348/4 = 4096,迁移4096个slot到9006
[ok] All 16384 slots coverd.
How many slots do you want to move (fron 1 to 16384)? 4096
What is the receiving node ID?
- 接下来会询问你是从指定的节点迁移slot还是从全部节点迁移4096个slot,我们这里简单点从全部节点中共迁移4096个slot,输入all
- 接下来cluster会询问你是否开始执行分片,输入yes,开始执行。
- 检查扩容是否成功 ./redis-trib.rb check 192.168.1.0:7000 查看集群信息
新增从节点,比较简单,不需要迁移slot。
- 以7007端口启动一个redis,redis.conf配置文件参考上面的部分
- slave加入集群中 ./redis-trib.rb add-node --slave --master-id $[nodeid] 192.168.1.0:7007 192.168.1.0:7000
nodeid: 新加入的7007的主节点,如果没有指定master-id,新增的从节点将会随机到从几点较少的主节点上
192.168.1.0:7000 加入的集群 - 检查加入是否成功 ./redis-trib.rb chech 192.168.1.0:7000
移除节点
- 如果master节点有数据,先迁移slot,迁移完成删除节点
- ./redis-trib.rb del-node 192.168.1.0:7000 ${nodeid}
salve节点直接删除即可。
hash规则
节点取余数
对键进行hash,再根据节点的个数N对hash进行取余。 hash(key) % N,这种方式实现比较简单,但是当节点变化时,会导致所有的键重新计算hash取余。
一致性hash
虚拟槽分区
通过计算键的CRC16的值,再通过16383进行取模(结果会均匀分布在0~16383之间),计算出键路由到的槽。
slot = CRC16(key) & 16383;
16383(10进制) = 1111 1111 1111 11 (二进制)
为什么使用CRC16,在redis官方文档中有说明 redis cluster specification
14 out of 16 CRC16 output bits are used (this is why there is a modulo 16384 operation in the formula above).
In our tests CRC16 behaved remarkably well in distributing different kinds of keys evenly across the 16384 slots.
为什么槽位被设计为16384个呢
CRC16计算出的结果是16位,也就是65536,为什么取模的时候用的是16383(也就是槽位数),而不是65536呢?
- 节省带宽
当节点间进行信息通信时携带的结构体中的大致结构
typedef struct {
char sig[4]; //信号标示
uint16_t type; // 消息类型 用户区分 meet, ping, pong等消息
unsigned char myslots[CLUSTER_SLOTS/8]; //发送节点负责的槽信息
.........
}
当cluster_clots = 16384时,myslots占用的空间: 16384/8/1024 = 2kb;
当cluster_clots = 65536,myslots占用的空间: 65536/8/1024 = 8kb;
当集群内节点很多,频繁交换信息需要消耗大量带宽。
- 由于设计折衷,redis集群节点不太可能扩展到1000个以上。
所以16384个槽位够用了。 - myslots表示此节点负责的槽位,实际是一个bitmap(当为1表示这个槽点由这个节点负责),在传输过程中会对这个bitmap进行压缩,如果bitmap的填充率(填充率 =所有槽位数/N N表示redis节点)很高,则压缩率就很低。
The reason is:
- Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with16k slots, but would use a prohibitive 8k of space using 65k slots.
- At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.
So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.
总结
如果业务要简单使用的缓存不考虑可用性,可以采用单机。
如果希望继续提供性能,redis宕机不影响业务正常,可以采用主从。
如果希望redis高可用,可以采用哨兵模式。
如果希望可以横向扩展,可以采用cluster模式。
参考
https://redis.io/topics/cluster-tutorial/#redis-cluster-configuration-parameters
why redis-cluster use 16384 slots?