本篇主要针对可能会出现的异常情况。
一、与集群相关操作
在运维过程中,尤其是修复故障集群中,需要熟练掌握使用。
比如以下几种情况,都需要手动在线上紧急操作,做集群恢复:
1) 扩容缩容时,发生网络分区,两个分区节点数一样,造成无法投票。
2) 迁移时,目标节点发生宕机,导致各个节点管理的键空间分布不一致。
3 )集群中大部分节点不可用。
1. Redis提供测cluster集群元操作
http://www.redis.cn/commands/cluster-setslot.html
2.redis-cli提供的集合操作
image.png二、与集群相关的配置
1、cluster-enabled <yes/no>:
如果想在特定的Redis实例中启用Redis群集支持就设置为yes。
否则,实例通常作为独立实例启动。
2、cluster-config-file <filename>:
这不是用户可编辑的配置文件,而是Redis群集节点每次发生更改时自动保留群集配置(基本上为状态)的文件,
以便能够在启动时重新读取它。
文件列出了集群中各个节点的键空间分布以及集群本次和最近一次的epoch。
update集群信息时,通常会将此文件重写并刷新到磁盘上。
3、cluster-node-timeout <milliseconds>:
Redis群集节点间超过此时间通信失败,则会将对方设置为pfail。
这个值会影响故障切换耗时。
4、cluster-slave-validity-factor <factor>:
如果设置为0,无论主和从之间的链路保持断开连接的时间长短,从都将尝试故障切换。
如果该值为正值,则计算最大断开时间作为节点超时值乘以此选项提供的系数,
如果该节点是从,则在主断开连接的时间超过指定的超时值时,它不会尝试启动故障切换。
例如,如果节点超时设置为5秒,并且有效因子设置为10,
则与主设备断开连接超过50秒的从将不会尝试对其主进行故障切换。
如果没有从服务器节点能够对其进行故障转移,
则任何非零值都可能导致Redis群集在主服务器出现故障后不可用。
在这种情况下,只有主节点重新加入集群时,集群才会返回可用。
5、cluster-migration-barrier <count>:
主将保持连接的最小从数量。
6、cluster-require-full-coverage <yes/no>:
如果将其设置为yes,则默认情况下,任何一个slot不可用,集群停止接受写入。
如果该选项设置为no,群集在大多数主节点可用时,仍将提供查询。
在下面的测试中,将设置cluster-require-full-coverage no、cluster-node-timeout 15000。
三、异常测试
通过命令构建集群
./redis-cli --cluster create 127.0.0.1:7000 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 --cluster-replicas
构建完成后如下:集群包含3主3从
image.png
1、Redis Cluster可用性测试
1)单个slot不可用
集群仍可用。但当cluster-require-full-coverage yes,集群将不可用。
2)某个master和它的slave不可达
shutdwon 从实例7000
shutdwon 从实例7000的主实例7005
此时集群依然处于可用状态cluster_state:ok。
image.png
3)大部分节点不可用
shutdwon 主实例7000
shutdwon 主实例7000的从7004
shutdwon 主实例7001
唯一存活的主节点为7002,大约过了15~16s,实例7002发现整个集群不可用,设置自己的集群状态cluster_state:fail。
因为是大部分节点不可用,所以不会发生投票将pfail状态的节点设置为fail。7002主要是依靠ping超时(超时的节点设置为pfail),直到累积超时的节点超过(server.cluster->size / 2) + 1,则可判断整个集群不可用。
也就是说当大部分节点不可用,发现集群不可用至少为15s。
日志:
image.png
其中投票数的计算:
needed_quorum = (server.cluster->size / 2) + 1;
其中server.cluster->size 是通过cluster meet命令到集群中的主节点数之和。
假设集群为6主6从,那么needed_quorum=4。此时发生网络分区,两边集群拥有的节点数都是3个,那么就会导致集群不可用。设置为奇数,就不会出现类似的情况。所以将节点数设置为奇数比偶数,可用性更高。
同时在扩容缩容时,如果发生网络分区,是非常容易出现无法投票情况。此时就需要手动修复(整个修复方案还得仔细想想,不然很容易造成数据丢失)。
接着我们设想一下3)中的7002是由于网络分区,与集群大部分节点断连。那么也是15s之后,它才会拒绝客户端的写操作。15s内写入的数据将会全部丢失。
Redis 集群在拥有少数主节点和至少一个客户端的分区上,是容易丢失为数不少的写入操作。这其实是非常危险的,后面在主从复制切换丢失数据中,尝试提出一些方案来解决。
2、迁移过程中异常测试
1)迁移过程中,目标节点shutdown
执行:
redis-cli --cluster-from 72ef6eb3a168788dfa25033672118aafdfb27259
--cluster-to b646070182a9ec51fb8663658e5b816ea9ae09af
--cluster-slots 5461
将slot 0-5460从节点7005迁移到7003(7001为7003的从)。
执行 :
redis-cli -p 7003 shutdown
shutdown 主节点7003
此时迁移到slot 1084
image.png
7001提升为主,管理slot:5461-10922
各个节点标识的7005和7001键空间分布
image.png
统计一下:
image.png
由上面可以看出:
键空间分布式是很杂乱的。
由于7005是迁移的源节点,从它的键空间分布,我们可以得出。0-1083肯定是已经迁移出去,正在迁移1084的过程中被中断。
7002,7004,7005都更改了slot的分布,但是7003的从节点7001和7000,并未更新。导致集群键空间分布不一致。
此时集群虽然是可用的,但实际存在部分slot不能被访问的情况。
接着我们重启节点7003,各个节点的键空间分布一致。但是此时出现4个master,所以要恢复3主3从模式,还需要做手动维护。
如果在7003发生宕机事故,则需要使用cluster setslot强制将438-1083指向7001。
image.png
2)迁移过程中,源节点shutdown
执行:
redis-cli -p 7004 --cluster reshard 127.0.0.1:7004 --cluster-from cc2559271dcb9ffcd0c5bfe655e46736d6b78271 --cluster-to 2eef2e210a7a8bec4287904ae4c570b5c25e4a1a --cluster-slots 5462
将slot 0-5460从节点7005迁移到7003。
执行 :
redis-cli -p 7005 shutdown
关掉源节点7005。
通过cluster nodes查看键空间分布如下,键空间分布集群各个节点是一致的。
image.png
此时也需要通过setslot来修复。
我们注意到,迁移的时候,shutdown源节点与目标测试结果是不一致的。只有弄清楚这个问题,才好修复故障。
迁移一个槽的元命令见文档:https://moji.wemomo.com/doc#/detail/84254
迁移的最后一步CLUSTER SETSLOT <slot> NODE <destination-node-id>源或着目标。。
测试使用的是集成命令--cluster reshard做迁移,具体在Redis-cli.c中实现,CLUSTER SETSLOT <slot> NODE是在目标源上操作的。所以shutdown目标节点,会造成这个修改无法同步到其他节点。
迁移过程是一个挺复杂的过程。在其中发生宕机很容易造成集群部分slot不可用,出现各种情况,修复起来也不一样。
修复错误,很有可能发生丢失数据的情况。
最好还是能理解整个流程,知道如何做正确的修复。
3. 主从集群切丢失数据
在上文中的迁移和分区故障中都提及的数据丢失都是与主从有关。
1) 异步复制导致的丢失
手动故障切换(默认)是只有当从库和主库数据一致才能进行故障切换。
但自动故障中,切换的从是根据Rank选出的,是会造成数据丢失。
从节点在确认主节点failover 之后 , DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds +
SLAVE_RANK * 1000 milliseconds.
才会发起选举。
固定延时500ms,是为了留出时间,使主节点下线的消息能传播到集群中其他节点,这样集群中的主节点才有可能投票。随机延时是为了避免两个从节点同时开始故障转移流程。rank表示从节点的排名,排名是指当前从节点在下线主节点的所有从节点中的排名,排名主要是根据复制数据量来定,复制数据量越多,排名越靠前,因此,具有较多复制数据量的从节点可以更早发起故障转移流程,从而更可能成为新的主节点。
在redis的配置文件中有个参数我们可以设置:
min-slaves-max-lag 10
min-slaves-max-lag默认情况下是10。
以上面配置为例,这两个参数表示salve的与master的同步复制延迟不能超过10s,一旦所有的slave复制和同步的延迟达到了10s,那么此时master就不会接受任何请求。
就可以减小min-slaves-max-lag参数的值,这样就可以避免在发生故障时大量的数据丢失,一旦发现延迟超过了该值就不会往master中写入数据。
同时对于client,可以暂时将数据写入缓存或者磁盘,等redis恢复继续往里面写入。
2) 集群分区导致的丢失
Redis 集群在拥有少数主节点和至少一个客户端的分区上,最少15s才能发现自己是不可写的,那么至少在15s的写入操作都将被丢弃。
(可以想下,这块毕竟丢失数据比较多。目前没有什么好的想法。)
4.gossip通信
1) 集群间通信流量
为了支持1000节点规模的集群,在通信上作者做了相关优化。
a)每个节点1s向一个节点发送gossip信息,这个gossip只携带至少3个至多1/10个其他节点的信息。
每个gossip信息大小为sizeof(clusterMsg)-sizeof(union clusterMsgData) = 4352 - 2096 = 2256,大概2kb。
image.png
b)向server.cluster_node_timeout/2未接受到pong的节点发送gossip信息,同样这个gossip只携带至少3个至多1/10个其他节点的信息。
每个节点信息大小104 Byte
image.png
当集群较小时,发送的a)类消息占大多数。集群较大时,发送b)类信息占大多数。
一个700节点的集群通信带宽占 107.5MBit / s,这是非常可观的开销。这也是超大规模集群面临的问题。
2)gossip机制下判断主节点时效到传播到大部分节点,到底需要花多久时间。
1)某节点发现A节点不可达,并设置为pfail状态 :至少cluster_node_timeout
2)大多数节点发现A不可达:至多需要cluster_node_timeout/2(超过这个时间,一定会发送b)类消息)
3)接受大多数节点pfail,设置改节点fail。按 a)类传播,最大需要6s。
4)将 fail 状态发送到大部分节点。按 a)类传播,大概率需要6s
所以大概估算一下:
cluster_node_timeout + cluster_node_timeout/2 + 12s 大概率可以做自动故障切换。
至少也要等待
cluster_node_timeout + DELAY 时间才会发生故障切换。
这段时间业务是无法访问的。
DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + SLAVE_RANK * 1000 milliseconds.
总结
1.如果说Redis本身就是不适合做存储的,那么Redis Cluster在这个上面更是打折扣。尤其是不适合存储一些重要数据。
2.Redis Cluster中需要手动恢复的故障较多,需要熟练掌握细节并尽量自动化。
网友评论