redis05

redis cluster

 

数据节点

顺序分区

哈希分区
hash(key) % node_count

 

哈希分区

1.节点取余分区

hash(key) % node_count
优点:hash+取余的方式计算节点的分区很简单
缺点:当节点伸缩时候,数据节点关系发生变化,导致数据迁移
扩容的时候建议翻倍扩容,可以降低数据的迁移量。

 

2.一致性哈希分区

哈希+顺时针(优化取余)
约定长度2的32次方 位的哈希环,在其中分布若干个hash点。
第一步对每个key做哈希处理得到hashVal
第二步将hashVal顺时针偏移,得到的第一个hash点,即为分区的落点
优点:节点伸缩的时候,只会影响邻近节点,但是还是会有数据迁移
翻倍的伸缩,可以保证最小的迁移数据且达到数据的负载均衡

 

3.虚拟槽分区

预设虚拟槽,每个槽映射一个数据子集,一般比节点数大
采用CRC16(key) & 16383来决定节点
每个节点顺序地平均分布16384个槽,即当有5个节点时
A 0 ~ 3276
B 3277 ~ 6553
C 6554 ~ 9830
D 9831 ~ 13107
E 13108 ~ 16383

 

 

RedisCluster架构

 

节点

由多个master主节点组成,各个master都负责去读写,每个master都有各自的slave节点。

每个node的cluster_enabled配置为yes

Gossip协议

多个master节点之间会使用Gossip协议进行通信

1.meet消息

用于通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。

当A meet B以及A meet C之后,B就可以与C做交互了

2.ping消息

集群中交换最频繁的消息,集群内各个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。

ping消息发送封装了自身节点和部分其他节点的状态数据。

3.pong消息

当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。

pong消息内部封装了自身状态数据。

节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新

4.fail消息

当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新诶下线状态


指派槽

需要为RedisCluster指派槽,指定各个master节点的槽范围,让它进行正常的读写


复制

每个master节点包含若干个slave节点,形成主从复制的形式,以提高高可用性

通过各个节点之间相互监控来达到Sentinel的目的

 

Redis Cluster的安装

环境安装

 1 redis集群管理工具redis-trib.rb依赖ruby环境,首先需要安装ruby环境
 2 
 3 1.yum install ruby
 4   yum install rubygems
 5 
 6 2.上传工具包 redis-3.0.0.gem 
 7 
 8 3.安装ruby和redis的接口程序
 9 gem install /usr/local/redis-3.0.0.gem
10 
11 4.将Redis集群搭建脚本文件复制到/usr/local/redis/redis-cluster目录下
12 #cd /root/redis-3.0.0/src/
13 #ll *.rb
14 # cp redis-trib.rb /usr/local/redis/rediscluster/ -r

 

集群搭建
搭建集群最少也得需要3台主机,如果每台主机再配置一台从机的话,则最少需要6台机器。
端口设计如下:7001-7006

 1 cd /usr/local/redis
 2 
 3 1.复制出一个7001机器
 4 cp bin ./redis-cluster/7001 –r
 5 
 6 2.如果存在持久化文件,则删除
 7 cd  redis-cluster/7001
 8 rm -rf appendonly.aof dump.rdb
 9 
10 3.设置集群参数
11 cluster-enable yes
12 
13 4.修改端口
14 port 7001
15 
16 5.复制出7002-7006机器
17 cp 7001/ 7002 -r
18 cp 7001/ 7003 -r
19 cp 7001/ 7004 -r
20 cp 7001/ 7005 -r
21 cp 7001/ 7006 -r
22 
23 6.修改 7002 -7006 机器的端口
24 
25 
26 7.创建startall.sh可执行文件
27 
28 cd 7001
29 ./redis-server redis.conf
30 cd ../7002
31 ./redis-server redis.conf
32 cd ../7003
33 ./redis-server redis.conf
34 cd ../7004
35 ./redis-server redis.conf
36 cd ../7005
37 ./redis-server redis.conf
38 cd ../7006
39 ./redis-server redis.conf
40 cd ..
41 
42 8.增大权限
43 chomd u+x startall.sh
44 ./startall.sh
45 
46 9.创建集群
47 ./redis-trib.rb create --replicas 1 192.168.239.139:7001 192.168.239.139:7002 192.168.239.139:7003 192.168.239.139:7004 192.168.239.139:7005  192.168.239.139:7006

 

集群连接与查看

1 ./redis-cli –h 127.0.0.1 –p 7001 -c    c表示集群连接方式
2 
3                               
4 查看集群 
5 >cluster info
6 
7 查看集群节点
8 >cluster nodes

 

 

集群伸缩

 

一. 伸缩原理

集群伸缩 = 槽和数据在节点之间的移动

二、扩容集群

1.准备新节点(例如,加入6385,6386)

需要是集群模式 cluster_enabled = yes
配置和其他集群节点保持一致
启动后是一个孤儿节点

redis-server conf/redis-6385.conf
redis-server conf/redis-6386.conf


2.加入集群


redis-trib.rb add-node newHost:newPort existsHost:existPort --slave --master-id

redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
reids-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379


在没有图形化界面时,建议使用redis-trib.rb能够避免新节点已加入其它集群,造成故障
新节点加入到集群中,有两类作用
为它迁移槽和数据实现扩容
作为从节点负责参与故障转移

 

3.迁移槽和数据

基于原生命令

1. 对目标节点发送:cluster setslot ${slot} importing \${sourceNodeId},让目标节点准备导入槽的数据。
2. 对源节点发送:cluster set ${slot} migrating \${targetNodeId},让源节点准备迁出槽的数据
3. 源节点循环执行:cluster getkeysinslot ${slot} \${count},每次获取count个属于槽的键
4. 在源节点上执行:migrate ${targetIp} \${targetPort} key 0 \${timeout},将指定的key迁移
5. 重复执行步骤3~步骤4直到槽下所有的数据迁移至目标节点
6. 向集群内所有主节点发送 cluster setslot ${slot} node \${targetNodeId},通知槽分配给目标节点

 


基于redis-trib.rb

 1 #指定任意一个集群中的节点即可
 2 redis-trib.rb reshard ${everyHost}:${everyPort}
 3 #首先通过提示输入需要迁移多少个slot
 4 #然后提示输入接收这些槽的nodeId
 5 #接着提示输入源节点的节点ID集合,输入all则表示所有待迁移节点平均
 6 
 7 redis-trib.rb reshard host:port --from ${fromNodeId} --to ${toNodeId} 
 8     --slots ${slotCount} --yes --timeout --pipeline ${batchSize}
 9 #host:port     必传参数,集群内任意节点地址,用来获取整个集群信息
10 #--from     源节点ID,当有多个源节点用逗号分隔,如果是all,则源节点为集合内除目标节点外的所有主节点
11 #--to        目标节点ID,只能填写一个
12 #--slots    需要迁移槽的总数量
13 #--yes        迁移无需用户手动确认
14 #--timeout    每次migrate操作的超时时间,默认为60000毫秒=60秒
15 #--pipeline    控制每次批量迁移键的数量,默认是10

 

 

三、缩容集群
1.迁移槽到其他节点

#将槽从当前节点迁出可以使用上文中的reshard进行完成
redis-trib.rb reshard ${everyHost}:${everyPort}

2.通知其他节点忘记下线节点

#在‘其他节点’上依次执行如下命令,需要在60秒内执行
cluster forget ${downNodeId}


3.关闭节点

 

客户端路由

#计算指定key的对应的槽位
cluster keyslot ${key}


一、moved重定向

1. 客户端给任一节点发送请求
2. 节点计算该请求的槽位以及对应的节点
3. 如果对应的节点是自身,则将执行命令并返回执行结果
4. 如果对应的节点不是当前节点自身,将会返回一个moved异常:MOVED ${slot} \${targetHost} \${targetPort}
5. 然后客户端再重新按照targetHost:targetPort连接另一redis节点,发送请求,跳转至第2步。

 

二、ask重定向

1. ask请求发生在slot迁移的过程中
2. 客户端向source发送命令(不存在moved重定向的情况)
3. source节点回复客户端一个ASK重定向:ASK ${slot} \${targetHost} \${targetPort}
4. 客户端给target节点发送一个Asking请求
5. 然后紧接着客户端发送命令给target节点
6. target节点执行请求并响应结果

 

move 和 ask 的比较

 

两者都是客户端的重定向

MOVED的场景下,可以确定槽不在当前节点

ASK的场景下,槽还在迁移过程中

 

 

三、smart客户端(JedisCluster)
1.smart客户端原理:追求性能
从集群中选一个可运行节点,使用clutser slots初始化槽和节点映射

将cluster slots结果映射到本地,为每个节点创建JedisPool

准备执行命令

 

JedisCluster

 1 Set<HostAndPort> nodeList = new HashSet<>();
 2 nodeList.add(new HostAndPort("127.0.0.1" , 7000) );
 3 nodeList.add(new HostAndPort("127.0.0.1" , 7001) );
 4 nodeList.add(new HostAndPort("127.0.0.1" , 7002) );
 5 nodeList.add(new HostAndPort("127.0.0.1" , 7003) );
 6 nodeList.add(new HostAndPort("127.0.0.1" , 7004) );
 7 nodeList.add(new HostAndPort("127.0.0.1" , 7005) );
 8 JedisPoolConfig poolConfig = new JedisPoolConfig();
 9 int timeout = 30_000;
10 JedisCluster jedisCluster = new JedisCluster(nodeList , timeout , poolConfig);
11 jedisCluster.set("hello" , "world");
12 System.out.println(jedisCluster.get("hello"));
13 
14 //获取所有节点的JedisPool
15 Map<String,JedisPool> jedisPoolMap = jedisCluster.getClusterNodes();

 


集群模式下批量操作的实现

mget、mset需要一组key必须在同一个槽下

 

1.串行GET/SET

对每一个key在for循环中依次执行GET/SET
优点:执行简单
缺点:n个key就需要n次网络时间,效率低下

2.根据CRC16&16383的结果做内聚后串行IO

在本地通过CRC16&16383计算出各个key的槽,然后以各个槽做内聚,然后串行依次访问各个node

3.根据CRC16&16383的结果做内聚后并行IO

在本地通过CRC16&16383计算出各个key的槽,然后以各个槽做内聚,然后并行访问各个node

4.使用hash_tag的方式

当一个key包含 {} 的时候,就不对整个key做hash,而仅对 {} 包括的字符串做hash。
此时对每个key拼接一个字符串{tag},就可以访问一个节点,完成O(1)的请求

 

比较

 

 

故障转移

 

一、故障发现

通过ping、pong消息实现故障发现:不需要sentinel

1.主观下线(pfail消息)

某个节点认为另一个节点不可用,“偏见”
主观下线流程
节点1每秒定时给节点2发送PING消息
当节点2收到PING消息后返回的PONG消息被节点1收到时,节点1会更新与节点2的最后更新时间
否则如果PING/PONG失败,会触发通信异常断开连接
当与节点2最后的通信时间达到或大于***cluster-node-timeout***,则节点1主观认为节点2下线。

 

2.客观下线(fail消息)

当半数以上持有槽的主节点都标记某节点主观下线

流程
当每个节点收到其他节点发来的PING消息时,会解析其中是否包含其他节点的主观下线消息
收到PING的结果会把收到的主观下线消息存入故障链表中
在故障链表中,节点可以知道有多少主节点主观下线
故障链表存在有效期,有效期为***cluster-node-timeout X 2***,防止很久之前的主观下线消息长久存在于故障链表中。
计算有效的下线报告数量,当达到半数以上时,就将节点更新为客观下线
并向集群广播下线节点的fail消息

 

3.客观下线的作用

通知集群内所有节点标记故障节点为客观下线
通知故障节点的从节点触发故障转移流程

 

 

二、故障恢复

 

1.资格检查(对从节点进行资格检查)

每个从节点检查与故障主节点的断线时间
超过cluster-node-timeout * cluster-slave-validity-factor取消资格
cluster-slave-validity-factor:默认是10

2.准备选举时间

对各个符合资格的从节点按照偏移量进行排序偏移量越大(与主节点数据越接近)的节点准备选举时间越小(越早被选举,后续投票阶段可获得更多票数)

3.选举投票

由可用的master节点给各个符合资格的slave节点投票
准备选举时间越小的节点,越早被master节点投票,就更可能得到更多的票数
当投票数达到 (可用master节点数)/2 + 1,可以晋升成为master节点

4.替换主节点

1. 当前从节点取消复制变成主节点(slaveof no one)
2. 执行cluster del slot撤销故障主节点负责的槽,并执行cluster add slot把这些槽分配给自己
3. 向集群广播自己的pong消息,表明已经替换了故障从节点

 

 

 

Redis Clutser常见问题

 

一、集群完整性

cluster-require-full-converage 默认为yes
完整性要求集群中所有节点都是在线状态并且16384个槽都在一个服务的状态***才对外提供服务
大多数业务无法容忍,cluster-require-full-coverage建议设置为no

二、带宽消耗

官方建议:RedisCluster的节点不要超过1000个
每秒的PING/PONG消息会引起大量的带宽消耗
消息发送频率:节点发现与其他节点通信时间超过cluster-node-timeout / 2时会直接发PING消息
消息数据量:slots槽数组(2KB空间)和整个集群1/10的状态数据(10个节点状态数据约1KB)
节点部署的机器规模:集群分布的机器越多且每台机器划分的节点数越均匀,则集群内整体的可用带宽越高

优化方法
避免使用“大”集群:避免多业务使用一个集群,大业务可用多集群
cluster-node-timeout:影响带宽和故障转移时间,需要权衡
尽量均匀分配到多机器上:保证高可用和带宽

三、Pub/Sub广播

问题:publish在集群每个节点中广播:加重带宽压力
解决方案1:需要使用Pub/Sub时,为了保证高可用,可以单独开启一套Redis Sentinel
解决方案2:使用消息队列

 

 

 

数据倾斜

 

1.数据倾斜

节点和槽的分配不均匀
redis-trib.rb info ip:port 查看节点、槽、键值分布
redis-trib.rb rebalance ip:port 进行槽的均衡(谨慎使用)
不同槽对应的键值数差异较大
CRC16正常情况下比较均匀
可能存在hashKey
cluster countkeysnslot ${slot} 获取槽对应的键值个数
包含bigKey(大字符串、几百万元素的hash、set、zset、list)
可以在从节点上执行:redis-cli --bigkeys
优化数据结构
内存相关配置不一致

 

2.请求倾斜(热点key或者bigKey)

通过优化数据结构避免bigKey
热点Key不要使用hash_tag
当一致性要求不高时,可以用本地缓存+MQ

 

读写分离

 

只读连接:集群模式的从节点不接受任何读写请求
重定向到负责槽的主节点
readonly命令可以读:连接级别的命令
读写分离:更加复杂
与主从复制有同样的问题:复制延迟、读取过期数据、从节点故障
需要实现自己的客户端:cluster slave ${nodeId}
思路与Redis Sentinel一致

 

数据迁移

官方迁移工具:redis-trib.rb import
只能从单机迁移到集群
不支持在线迁移:source需要停止写入
不支持断点续传
单线程迁移:影响速度
对source数据进行SCAN,然后进行导入操作


在线迁移
唯品会:redis-migrate-tool
豌豆荚:redis-port
均支持在线迁移,会伪装成source节点的slave,利用这份更新数据再传送给target

 

集群与单机比较

集群限制
key批量操作支持有限:例如mget、mset必须在一个slot
Key事务和Lua支持有限,操作的Key必须在一个节点
Key是数据分区的最小粒度,不支持bigKey分区
不支持多数据库:集群模式下只有db 0
复制只支持一层:不支持树形复制结构
分布式Redis不一定好
Redis Cluster:满足容量和性能的扩展性,很多业务不需要

不多数客户端性能会降低
命令无法跨节点使用:mget、mset、scan、flush、sinter
Lua和事务无法跨节点使用
客户端维护更复杂:SDK和应用本身消耗(例如更多的连接池)
很多场景下:Redis Sentinel已经足够好

 

 

 

 

 

posted @ 2019-08-18 18:07  曲阳阳  阅读(171)  评论(0编辑  收藏  举报