一文讲清RedisCluster

网友投稿 1203 2023-05-21

一文讲清RedisCluster

一文讲清RedisCluster

1 集群的意义从单机的一主多从复制架构到现在的分布式架构

主要有如下维度:

业务追求更高QPS数据量Scale Up已经无法满足,超过了单机极限,考虑Scale Out分布式网络流量业务流量超过服务器网卡上限,考虑分布式分流离线计算需要中间环节缓冲等需求

2 meet节点之间完成相互通信的基础,有一定的频率和规则。

CLUSTER MEET命令被用来连接不同的开启集群支持的 Redis 节点,以进入工作集群。

2.1 基本思想每个节点默认都是相互不信任的,并且被认为是未知的节点,以便万一因为系统管理错误或地址被修改,而不太可能将多个不同的集群节点混成一个集群。

因此,为了使给定节点能将另一个节点接收到组成 Redis Cluster 的节点列表中,这里只有两种方法:

系统管理员发送一个CLUSTER MEET命令强制一个节点会见另一个节点一个已知节点发送一个保存在 gossip 部分的节点列表,包含着未知节点。如果接收的节点已经将发送节点信任为已知节点,它会处理 gossip 部分并且发送一个握手消息给未知的节点。

Redis Cluster 需要形成一个完整的网络(每个节点都连接着其他每个节点),但为创建一个集群,不需要发送形成网络所需的所有CLUSTER MEET命令。发送CLUSTER MEET消息以便每个节点能够到达其他每个节点只需通过一条已知的节点链就够了。由于在心跳包中会交换 gossip 信息,将会创建节点间缺失的链接。

所以,如果我们通过CLUSTER MEET链接节点 A 和 B ,并且 B 和 C 有链接,那么节点 A 和 C 会发现他们握手和创建链接的方法。

2.2 案例假设某一集群有A、B、C、D四个节点,可以只发送以下一组命令给 A :

CLUSTER MEET B-ip B-port CLUSTER MEET C-ip C-port CLUSTER MEET D-ip D-port

由于 A 知道及被其他所有节点知道,它将会在发送的心跳包中包含gossip部分,这将允许其他每个节点彼此都创建一个链接,即使集群很大,也能在数秒内形成一个完整网络。

CLUSTER MEET无需相互执行,即若发送命令给 A 以加入B ,那就不必也发送给 B 以加入 A。

2.3 实现细节:MEET 和 PING 包当某一给定节点接收到一个MEET消息时,命令中指定的节点仍不知发送了该命令,所以为使节点强制将接收命令的节点将它作为信任的节点接受它,它会发送MEET包而非PING包。两个消息包有相同的格式,但是MEET强制使接收消息包的节点确认发送消息包的节点为可信任的。

3 指派槽

把16384个槽平分给节点管理,每个节点只对自己负责的槽进行读写每个节点间都相互通信,所以每个节点都知道其它节点所管理槽的范围

客户端与指派槽

4 集群伸缩集群的伸缩包括新节点的加入和旧节点退出。

4.1 加入新节点Redis集群加入新节点主要如下几步:

准备新节点

启动一个集群模式下的Redis节点

加入集群

通过与任意一集群中的节点握手加入新节点

迁移slot到新节点

再向新节点分配它负责的slot并向其迁移slot对应数据。

由于Redis采用Gossip协议,所以可让新节点与任一现有集群节点握手,一段时间后整个集群都会知道加入了新节点。

4.1.1 案例向如下集群中新加入一个节点6385。由于负载均衡的要求,加入后四个节点每个节点负责4096个slots,但集群中原来的每个节点都负责5462个slots,所以6379、6380、6381节点都需要向新的节点6385迁移1366个slots。

Redis集群并没有一个自动实现负载均衡的工具,把多少slots从哪个节点迁移到哪个节点完全是由用户指定。

迁移数据的流程图

迁移key可以用pipeline进行批量的迁移。

对于扩容,原理已经很清晰了,至于具体操作,网上很多。至于缩容,也是先手动完成数据迁移,再关闭redis。收缩时如果下线的节点有负责的槽需要迁移到其他节点,再通过cluster forget命令让集群内所有节点忘记被下线节点

5 客户端路由5.1 moved重定向

每个节点通信共享Redis Cluster中槽和集群中对应节点的关系。

客户端向Redis Cluster的任一节点发送命令接收命令的节点再计算自己的槽和对应节点

1.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端

2.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常

3.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息

4.客户端向目标节点发送命令,获取命令执行结果

客户端不会自动找到目标节点执行命令,需要二次执行

5.2 ask重定向由于集群伸缩时,需要数据迁移。

当客户端访问某key,节点告诉客户端key在源节点,再去源节点访问时,却发现key已迁移到目标节点,就会返回ask。

客户端向目标节点发送命令,目标节点中的槽已经迁移到其它节点目标节点会返回ask转向给客户端客户端向新节点发送Asking命令再向新节点发送命令新节点执行命令,把命令执行结果返回给客户端

为什么不能简单使用MOVED重定向?因为虽然MOVED意味着我们认为哈希槽由另一个节点永久提供,并且应该对指定节点尝试下一个查询,所以ASK意味着仅将下一个查询发送到指定节点。

之所以需要这样做,是因为下一个关于哈希槽的查询可能是关于仍在A中的键的,因此我们始终希望客户端尝试A,然后在需要时尝试B。由于只有16384个可用的哈希槽中有一个发生,因此群集上的性能下降是可以接受的。

5.3 moved V.S ask都是客户端重定向

moved:槽已经确定转移ask:槽还在迁移中

5.4 smart智能客户端目标追求性能

设计思路

从集群中选一个可运行节点,使用Cluster slots初始化槽和节点映射将Cluster slots的结果映射在本地,为每个节点创建JedisPool,然后就可以进行数据读写操作

注意事项

每个JedisPool中缓存了slot和节点node的关系key和slot的关系:对key进行CRC16规则进行hash后与16383取余得到的结果就是槽JedisCluster启动时,已经知道key,slot和node之间的关系,可以找到目标节点JedisCluster对目标节点发送命令,目标节点直接响应给JedisCluster如果JedisCluster与目标节点连接出错,则JedisCluster会知道连接的节点是一个错误的节点此时JedisCluster会随机节点发送命令,随机节点返回moved异常给JedisClusterJedisCluster会重新初始化slot与node节点的缓存关系,然后向新的目标节点发送命令,目标命令执行命令并向JedisCluster响应如果命令发送次数超过5次,则抛出异常"Too many cluster redirection!"基本图示

全面图示

6 批量操作mget、mset须在同一槽。

Redis Cluster不同于Redis 单节点,甚至和一个 Sentinel 监控的主从模式也不一样。主要是因为集群自动分片,将一个key 映射到16384槽之一,这些槽分布在多节。因此操作多 key 的命令必须保证所有的key都映射同一槽,避免跨槽执行错误。一个单独的集群节点,只服务一组专用的keys,请求一个命令到一个Server,只能得到该Server上拥有keys的对应结果。一个非常简单的例子是执行KEYS命令,当发布该命令到集群中某节点时,只能得到该节点上拥有key,并非集群中所有key。要得到集群中所有key,必须从集群的所有主节点上获取所有key。

对于分散在redis集群中不同节点的数据,我们如何比较高效地批量获取数据呢?

6.1 串行mget定义for循环,遍历所有key,分别去所有的Redis节点中获取值并进行汇总,简单,但效率不高,需n次网络时间。

6.2 串行I/O优化串行的mget,在客户端本地做内聚,对每个key hash,然后取余,知道key对应槽

本地已缓存了槽与节点的对应关系,然后对key按节点进行分组,成立子集,然后使用pipeline把命令发送到对应的node,需要nodes次网络时间,大大减少了网络时间开销。

6.3 并行I/O优化串行IO,分组key后,根据节点数量启动对应的线程数,根据多线程模式并行向node节点请求数据,只需1次网络时间

6.4 hash_tag不做任何改变,hash后就比较均匀地散在每个节点上

是否能像单机,一次IO将所有key取出呢?hash-tag提供了这样功能:若将上述key改为如下,即大括号括起来相同的内容,保证所有的key只向一个node请求数据,这样执行类似mget命令只需要去一个节点获取数据即可,效率更高。

6.5 选型对比

第一种方式,使用多线程解决批量问题,减少带宽时延,提高效率,这种做法就如上面所说简单便捷(我们目前批量操作类型比较多),有效。但问题比较明显。批量操作数量不大即可满足。搜狐的cachecloud采用第二点,先将key获取槽点,然后分node pipeline操作。这种做法相对比第一种做法较优。

7 故障转移7.1 故障发现Redis Cluster通过ping/pong消息实现故障发现:不需要sentinel。ping/pong不仅能传递节点与槽的对应消息,也能传递其他状态,比如:节点主从状态,节点故障等

故障发现就是通过这种模式来实现,分为主观下线和客观下线:

7.1.1 主观下线定义某节点认为另一节点不可用,这仅代表一个节点对另一节点的判断,不代表所有节点的认知。

流程

节点-1定时发ping消息给节点-2若发送成功,代表节点-2正常运行,节点-2会响应PONG消息给节点1,节点1更新与2的最后通信时间若发送失败,则节点-1与节点-2间通信异常判断连接,在下一定时任务周期时,仍然会与节点2发送ping消息若节点-1发现与节点-2最后通信时间超过node-timeout,则把节点2标识为pfail状态

7.1.2 客观下线定义当半数以上持有槽的主节点都标记某节点主观下线。可以保证判断的公平性。

集群模式下,只有主节点(master)才有读写权限和集群槽的维护权限,从节点(slave)只有复制的权限。

流程

1.某个节点接收到其他节点发送的ping消息,如果接收到的ping消息中包含了其他pfail节点,这个节点会将主观下线的消息内容添加到自身的故障列表中,故障列表中包含了当前节点接收到的每一个节点对其他节点的状态信息

2.当前节点把主观下线的消息内容添加到自身的故障列表之后,会尝试对故障节点进行客观下线操作

7.2 故障恢复从节点接收到它的主节点客观下线的通知,则进行故障恢复。

资格检查

对从节点的资格进行检查,只有难过检查的从节点才可以开始进行故障恢复每个从节点检查与故障主节点的断线时间超过cluster-node-timeout * cluster-slave-validity-factor数字,则取消资格cluster-node-timeout默认为15秒,cluster-slave-validity-factor默认值为10如果这两个参数都使用默认值,则每个节点都检查与故障主节点的断线时间,如果超过150秒,则这个节点就没有成为替换主节点的可能性

准备选举时间使偏移量最大的从节点具备优先级成为主节点的条件。

选举投票对选举出来的多个从节点进行投票,选出新的主节点。

替换主节点

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

8 开发运维常见问题8.1 集群完整性

cluster-require-full-coverage默认为yes,即集群中所有节点都在服务且16384个槽都可用,集群才会提供服务,以保证集群完整性。当某节点故障或者正在故障转移时获取数据会提示:(error)CLUSTERDOWN The cluster is down

但是大多数业务都无法容忍,建议把cluster-require-full-coverage设为no

8.2 带宽消耗

Redis Cluster节点之间会定期交换Gossip消息,以及做一些心跳检测官方建议Redis Cluster节点数量不要超过1000个,当集群中节点数量过多时,会产生不容忽视的带宽消耗消息发送频率:节点发现与其他节点最后通信时间超过cluster-node-timeout /2时,会直接发送PING消息消息数据量:slots槽数组(2kb空间)和整个集群1/10的状态数据(10个节点状态数据约为1kb)节点部署的机器规模:集群分布的机器越多且每台机器划分的节点数越均匀,则集群内整体的可用带宽越高

带宽优化

避免使用’大’集群:避免多业务使用一个集群,大业务可以多集群cluster-node-timeout:带宽和故障转移速度的均衡尽量均匀分配到多机器上:保证高可用和带宽

8.3 Pub/Sub广播

在任意一个cluster节点执行publish,则发布的消息会在集群中传播,集群中的其他节点都会订阅到消息,这样节点的带宽的开销会很大publish在集群每个节点广播,加重带宽

解决方案单独“走”一套redis sentinel。就是针对目标的几个节点构建redis sentinel,在这个里面实现广播。

8.4 集群倾斜分布式数据库存在倾斜问题是比较常见的。集群倾斜也就是各个节点使用的内存不一致

数据倾斜原因1.节点和槽分配不均,如果使用redis-trib.rb工具构建集群,则出现这种情况的机会不多

redis-trib.rb info ip:port查看节点,槽,键值分布 redis-trib.rb rebalance ip:port进行均衡(谨慎使用)

2.不同槽对应键值数量差异比较大

CRC16算法正常情况下比较均匀可能存在hash_tagcluster countkeysinslot {slot}获取槽对应键值个数

3.包含bigkey:例如大字符串,几百万的元素的hash,set等

在从节点:redis-cli --bigkeys优化:优化数据结构

4.内存相关配置不一致

hash-max-ziplist-value:满足一定条件情况下,hash可以使用ziplistset-max-intset-entries:满足一定条件情况下,set可以使用intset在一个集群内有若干个节点,当其中一些节点配置上面两项优化,另外一部分节点没有配置上面两项优化当集群中保存hash或者set时,就会造成节点数据不均匀优化:定期检查配置一致性

5.请求倾斜:热点key

重要的key或者bigkey

Redis Cluster某个节点有一个非常重要的key,就会存在热点问题

优化

避免bigkey热键不要用hash_tag当一致性不高时,可以用本地缓存+ MQ(消息队列)

9 读写分离

只读连接集群模式下,从节点不接受任何读写请求。当向从节点执行读请求时,重定向到负责槽的主节点readonly命令可以读:连接级别命令,当连接断开之后,需要再次执行readonlyredis cluster 默认slave 也是不能读的,如果要读取,需要执行 readonly,就可以了。

读写分离:更加复杂(成本很高,尽量不要使用)

同样的问题:复制延迟,读取过期数据,从节点故障修改客户端:cluster slaves {nodeId}

10 集群优劣集群的限制

key批量操作支持有限:例如mget,mset必须在一个slotkey事务和Lua支持有限:操作的key必须在一个节点key是数据分区的最小粒度:不支持bigkey分区不支持多个数据库:集群模式下只有一个db0复制只支持一层:不支持树形复制结构

集群不一定好

Redis Cluster满足容量和性能的扩展性,很多业务’不需要’大多数时客户端性能会’降低’命令无法跨节点使用:mget,keys,scan,flush,sinter等Lua和事务无法跨节点使用客户端维护更复杂:SDK和应用本身消耗(例如更多的连接池)很多场景Redis Sentinel已经够用了

参考

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:MySQL性能优化实践篇:如何保证数据库的效率
下一篇:趣谈MySQL历史,以及MariaDB初体验
相关文章