redis & 主从同步 & 总结_redis 主从同步,-爱代码爱编程
前言
相关系列
- 《Redis & 目录》(持续更新)
- 《Redis & 主从同步 & 源码》(学习过程/多有漏误/仅作参考/不再更新)
- 《Redis & 主从同步 & 总结》(学习总结/最新最准/持续更新)
- 《Redis & 主从同步 & 问题》(学习解答/持续更新)
参考文献
概述
简介
主从同步的本质是数据复制机制。主从同步机制用于将master @ 主机的数据同步到一/多台slave/replica @ 从机中,从而达到提升数据可用性/可靠性/读写性能的效果。当主机因为各项原因而宕机时,从机可以代替主机提供服务以保证程序的可用性。但遗憾的是该故障转移行为需要开发者手动执行,即需要人为修改/启动使用方的配置/服务连接从机,除非能够接入Redis的Sentinel @ 哨兵来实现故障转移的自动化…该知识点会在哨兵的专项文章中详述。
主从同步机制无法避免在主机宕机时丢失数据。主从同步是由内存/网络/磁盘共同协作运行的复合过程,即主从同步的过程并不具备原子性,因此主机的部分数据可能在尚未发送至从机的情况下因为宕机而丢失。这种丢失可以说是完全无法避免的,因为即使是将持久化机制调整至最高安全等级也无法保证完整持久化所有数据,因此开发者能做的只是通过Redis提供各项配置/指令来尽可能减少这种数据丢失的量。
Redis存在“一主多从/级联”两种主从同步架构。一主多从机构拥有更高的同步效率,但从机数量较多时会对主机造成较大的同步压力;而级联架构则通过允许从机继续携带从机的方式来缓解这种压力,但缺点是整体同步效率的降低…该知识点会在下文详述。
不足
- 不具备自动容错/恢复功能,主机宕机时需要人为将从机提升为主机;
- 主机宕机会不可避免的丢失部分数据;
- 主机唯一,写入/存储能力都存在一定程度地限制;
- 全量同步时会对Redis的整体性能造成影响,并且程序与数据量正相关。
架构
一主多从
一台主机携带多台从机的主从结构被称为一主多从架构。一主多从架构中的主/从机会分别负责写入/读取,从而得以将主机的读取压力彻底剥离并平均分担至多台从机。一主多从是现实开发中最常见的主从同步架构,具有同步效率高的优点。但缺点是较多的从机会对主机造成一定的同步压力,因此该情况下更推荐使用级联架构。
一主多从架构有“静态/动态”两种搭建方案。所谓静态搭建是指通过添加/修改{slaveof/replicaof}配置项的方式来为从机指定主机,该方案的特点在于主从关系不会随从机的宕机而丢失,因为从机每次启动时都会重新与主机构建连接。而动态搭建则是指通过执行{SLAVEOF/REPLICAOF}指令来为从机实时指定/变更/取消主机,但该方案的特点是其执行效果会在宕机后丢失,即重启后的从机依然只会根据{slaveof/replicaof}配置项来指定主机…静态/动态搭建的相关指令/配置具体如下:
名称:slaveof/replicaof
作用:在5.0.0版本前/后指定数据同步的主机
---- 名称:masterip @ 主机IP
---- 作用:主机的IP。
---- 默认:无
---- 名称:masterport @ 主机端口
---- 作用:主机的端口。
---- 默认:无
- SLAVEOF <masterip> <masterport>:5.0.0版本前用于指定/变更/取消主机的指令。
------------------------- 入参 -------------------------
masterip @ 主机ID:主机的ID。解除主从关系时传NO;
masterport @ 主机端口:主机的端口。解除关系时传ONE。 - REPLICAOF :5.0.0版本时/后用于指定/变更/取消主机的指令。
------------------------- 入参 -------------------------
masterip @ 主机ID:主机的ID。解除主从关系时传NO;
masterport @ 主机端口:主机的端口。解除关系时传ONE。
// ---- 将当前Redis的主机设置为127.0.0.1:6379的Redis,并返回成功。
> REPLICAOF 127.0.0.1 6379
OK
// ---- 将当前Redis的主机取消,并返回成功。
> REPLICAOF NO ONE
OK
主从关系建立完成后,可通过{INFO REPLICATION}指令查看建立情况…具体如下:
- INFO REPLICATION:查看主从同步信息。
------------------------- 出参 -------------------------
role @ 角色:主从同步时的角色(master:主机,slave/replica:从机);
connected_slaves @ 已连接从机:已连接的从机总数;
slave… @ 从机集:从机集,展示每台从机的IP/端口/状态/偏移量/延迟;
master_failover_state @ 主机故障转移状态:主机故障转移状态(no-failover:无故障转移);
master_replid @ 主机复制ID:未知;
master_replid2 @ 主机复制ID2:未知;
master_repl_offset @ 主机复制偏移量:变相代表主机的总数据量;
second_repl_offset @ 二级复制偏移量:未知;
repl_backlog_active @ 复制积压活动:未知;
repl_backlog_size @ 复制积压大小:复制积压缓冲区的大小(bytes);
repl_backlog_first_byte_offset @ 复制积压首个字节偏移量:复制积压缓冲区首个未覆盖未知的索引,从机偏移量大于该值理论上可执行增量同步;
repl_backlog_histlen @ 复制积压长度:未知;
// ---- 查询当前Redis的从机同步信息。
> INFO REPLICATION
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=231,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=231,lag=1
master_failover_state:no-failover
master_replid:e7e4331043fe242af513e70dbd9baff1414fbf73
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:231
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:231
级联
级联架构在一主多从机架构的基础上添加了等级概念。相比一主多从架构而言级联架构的最大区别在于其允许从机继续携带从机,从而得以有效分担主机的同步压力。但缺点是数据在多级结构中同步可能会产生相对较为严重的延迟,即在上游从机已完成数据同步的情况下下游从机可能尚未开始同步。此外级联架构下宕机的后果也相对严重许多,因为机宕机会导致后续一系列的从机无法继续同步数据,因此其对于故障转移的实时性要求也相对更高。
级联架构的搭建方案与一主多从完全相同。Redis并没有提供特殊的配置/指令来快速实现级联架构的搭建,因此其主从关系的建立方式与一主多从架构完全是相同的,只不过其需要将N级从级陆续指向N-1集从机而已,因此级联机构的搭建工作相对而言会繁琐许多。
同步
全量同步
将主机数据全量同步至从机的主从同步被称为全量同步。全量同步通常发生于主/从机“每次”启动且与对方建立“首次”长连接时,因为该情况下从机要么从未向主机请求同步过数据;要么其数据已因为“任意一方的”重启而难以/无法与主机对齐,因此在该情况下使用全量同步是保证主从同步被快速/完整进行的最佳方案。而除此以外全量同步还可能于少数特殊的场景中发生,例如从机偏移量无效/增量同步效率较低等…这些知识点会在下文讲解增量同步时详述。
全量同步分为“阶段性同步&实时性同步”两个步骤。所谓阶段性同步是指令从机数据与主机某时刻的数据达成一致,而实时性同步则是指在阶段性同步的基础上通过持续性的数据补充/追加而令从机数据能够与主机的最新数据无限接近乃至达成一致,而这两者分别通过快照/指令同步方案实现。在这两个步骤中阶段性/快照同步是值得讨论的,因为其存在在整体流程中显得可有可无。即Redis即使只使用实时性/指令同步也能够彻底完成全量同步,只需主机将已执行的写指令全量发送至从机执行即可…那为什么还要设计/选择阶段性同步/快照同步这一步骤存在呢?实际上这是Redis出于性能/开销方面上的双重考量。
阶段性同步用于为实时性同步构建更贴近实际数据的数据基础。Redis单纯基于实时性/指令同步是可以实现全量同步的,这一点并无需任何质疑。但如此一来从机就会将“空”作为实时性/指令同步的数据基础,即在自身无任何数据的情况下进行实时性/指令同步,从而使得主机必须保存已执行的所有写指令,否则就会因为指令缺失导致主/从机数据无法达成最终一致。写指令的全量保存在技术实现上并没有什么难度,但可以预想的是该写指令集必然会随主机的不断运行而愈加庞大,并最终对性能/开销造成严重影响。因为庞大的写指令集必将占用大量的内存/磁盘资源,而指令的大量执行也会导致同步效率的低下,因此Redis出于性能/开销上的双重考量选择加入阶段性同步以优化这一点。阶段性同步的作用是为实时性同步构建更贴近实际数据的数据基础,即令实时性/指令同步在已基本具备实际数据的从机上执行,使得主/从机只需保存/执行极少数的追加指令便可支持主/从数据的快速贴近,从而获得性能/开销上的双重收益。
数据基础需具备时刻强一致性。所谓时刻强一致性是指数据基础必须与主机某时刻的数据完全相同,因为Redis需要基于该时刻确定主/从机需要保存/执行的追加指令。而如果数据基础是指某时段的数据,那么该时段内追加指令的重复执行就大概率会造成同步错误。例如主机准备将T1时刻的数据拷贝发送至从机作为数据基础,但由于数据拷贝直到T2时间才彻底完成,并且在拷贝期间还存在指令的并发写入,因此该拷贝/数据基础实际代表的是T1 ~ T2时段数据。但由于Redis只会将T1时间作为追加指令的判断点,因此从机就相当于会将T1 ~ T2时段的追加指令重复执行一遍,故而就可能出现同步错误。而由于Redis在生成RDB快照文件时特意通过进程资源的独立性来保证了这种时刻强一致性,并且其本身还具备文件体量小/加载速度快等特点,因此RDB快照文件是Redis实现阶段性同步的最佳选择。
全量同步通过从机向主机发送{SYNC/PSYNC}指令触发执行。关于{SYNC/PSYNC}指令的详解具体如下:
- SYNC:Redis早期只存在全量同步概念时用于请求主从同步的指令,并且该指令在后续版本中也因为兼容性等原因而未被废除。
- PSYNC <master_runid> <master_offset>:Redis在2.8版本提供的SYNC指令增强版,不仅具备SYNC指令的全量同步能力,还在此基础上针对“数据进度可对齐”的相应情况设计了增量同步功能,因此成为了后续版本的默认主从同步指令。
------------------------- 入参 -------------------------
master_runid @ 主机运行ID:运行ID是Redis实例的标记ID,会在Redis启动时基于UUID原理随机生成,本质为40个字符长度的十六进制字符串。Redis不会持久化运行ID,因此相同Redis实例的多次启动会产生多个运行ID。而由于运行ID的长度足够长且生成随机,因此运行ID理论上虽然可能重复,但实际情况中这种重复基本不可能存在。主机在接收到从机发送的{PSYNC}指令时其会根据携带的主机运行ID判断是否对从机进行全量同步,因为携带的主机运行ID不存在(从机启动会导致记录的主机运行ID丢失)或与实际的主机运行ID不符合(主机启动会导致自身运行ID发生变化)都意味着这是主/从机至少一方启动后与对方建立的首次长连接。由于这种情况下从机要么从未向主机请求同步过数据;要么其数据已因为“任意一方的”重启而难以/无法与主机对齐,因此为了保证主从同步的快速/完整执行主机会选择采用全量同步方案。当从机启动并首次与主机建立长连接时,由于此时其并未记录主机运行ID,因此其会将?作为象征主机运行ID不存在的特殊值随{PSYNC}指令发送;
master_offset @ 主机偏移量:偏移量分为“主机/从机”两种:前者表示主机的整体数据量;后者则表示从机对主机数据的同步进度。而由于{PSYNC}指令携带的主机偏移量被用于向主机报告从机的数据同步进度,因此该“主机”偏移量实际指的是从机偏移量。“主机”偏移量被用于在replication_backlog_buffer @ 复制积压缓冲区上作为索引,而复制积压缓冲区则是主机用于记录已执行写指令的“数组”结构。由于“主机”偏移量的对应位置便是从机首个未/最后已执行写指令的所在位置,因此从机记录了“主机”偏移量就相当于记录了对主机数据的同步进度,因为主机可以根据“主机”偏移量将复制积压缓冲区上未执行的指令发送至从机中执行以实现两者数据的同步。“主机”偏移量只在主机运行ID存在&符合的情况下生效,因为主机运行ID不符合意味着主机经历了重启。而由于复制积压缓冲区会在主机启动后新建而重新记录写指令,因此旧复制积压缓冲区的索引在新复制积压缓冲区无效,这也是主机运行ID不存在/不符合时主/从机难以/无法数据对齐而只能进行全量同步的核心原因。而当从机启动并首次与主机建立长连接时,由于此时其逻辑上尚未开始同步数据,因此其会将-1作为象征完全未同步的特殊值随{PSYNC}指令发送。
// ---- 向主机请求全量/增量同步。
> PSYNC ? -1
> PSYNC 1234567890123456789012345678901234567890 343234
// ---- 表示主机会进行全量同步。前参数是主机的运行ID,后参数是主机偏移量,即快照同步结
// 束后进行快照同步的数据起点。
+FULLRESYNC 1234567890123456789012345678901234567890 343234
// ---- 表示主机会进行增量同步。
+CONTINUE
// ---- 表示版本错误或其他错误。
-ERR
通过{SYNC}指令触发的全量同步流程具体如下:
- 长连接建立,从机向主机发送{SYNC}指令以请求数据的全量同步;
- 主机接收{SYNC}指令,随后异步执行{BGSAVE}指令生成RDB快照文件,并在成功生成后将之发送至从机。而从RDB快照文件开始生成起,主机后续执行的所有写指令都会被记录在从机私有的replication_buffer @ 复制缓冲区中,用于后续对从机进行持续性的指令同步。复制缓冲区的大小默认为1MB,当满溢时其会阻塞内存读写;
- 从机接收RDB快照文件并保存至磁盘,随后清空原数据再异步加载其中数据至内存以完成快照同步。该异步会在一定程度上阻塞主进程,因为主进程虽然会在此期间并发执行某些操作,但其对数据的读/写却依然需要等到子进程完成对RDB快照文件的加载后才能执行,否则就无法保证数据的正确性/安全性。该加载行为与Redis启动时加载RDB快照文件的行为一致;
- 主机完成对RDB快照文件的发送后,开始向从机发送复制缓冲指令以进行持续性的指令同步;
- 从机持续接收复制缓冲指令,并在确保RDB快照文件已加载完毕的情况下依次执行,从而确保与主机达成数据的最终一致性。
通过{PSYNC}指令触发的全量同步流程具体如下:
- 长连接建立,从机向主机发送{PSYNC}指令以请求数据的全量同步;
- 主机接收{PSYNC}指令,随后向从机返回+FULLRESYNC @ 全量同步回应告知将进行全量同步。随后主机会异步执行{BGSAVE}指令并根据配置生成RDB快照文件/RDB快照数据并发送至从机。而从RDB快照文件/RDB快照数据开始生成起,主机后续执行的所有写指令都会被记录在从机私有的复制缓冲区中,用于后续对从机进行持续性的指令同步。此外写指令还会被记录在公有的复制积压缓冲区中,用于在支持的情况下对从机进行增量同步…但这在全量同步流程中并无作用。
- 从机接收全量同步回应,并保存其携带的主机运行ID/主机偏移量。其中主机会被作为从机偏移量;
- 从机接收RDB快照文件并保存至磁盘/RDB快照数据,随后清空原数据再异步加载其中数据至内存以完成快照同步。该异步会在一定程度上阻塞主进程,因为主进程虽然会在此期间并发执行某些操作,但其对数据的读/写却依然需要等到子进程完成对RDB快照文件/RDB快照数据的加载后才能执行,否则就无法保证数据的正确性/安全性。该加载行为与Redis启动时加载RDB快照文件的行为一致;
- 主机完成对RDB快照文件/RDB快照数据的发送后,开始向从机发送复制缓冲指令以进行持续性的指令同步,已发送的复制缓冲指令会从复制缓冲区中清除;
- 从机持续接收复制缓冲指令,并在确保RDB快照文件/RDB快照数据已加载完毕的情况下依次执行并更新从机偏移量,从而确保与主机达成数据的最终一致性。
无盘同步
主机在执行快照同步期间会产生很重的磁盘I/O,因为其需要遍历数据来生成RDB快照文件以供向从机发送。这些磁盘I/O一方面会拖慢快照同步的效率;另一方面也会对主机本身持久化乃至整体的执行效率造成严重影响,因此Redis从2.8.18版本起便开始支持无盘同步。所谓无盘同步是指主机不会再生成RDB快照文件,而是会直接将遍历数据生成的序列化快照数据通过socket的方式持续发送至从机,从而得以令内存I/O替换掉原本的磁盘I/O。而从机在接收到序列化快照数据后通常也不会再存于RDB快照文件,而是会出于性能上的考量直接加载至内存中。当然,不排除从机出于检查完整/合法性等原因而先将序列化快照数据完整存于RDB快照文件后再一次性加载的可能,这具体取决于Redis的实现/配置…关于无盘同步的部分配置如下:
名称:repl-diskless-sync
作用:设置Redis是否在全量同步的快照同步时使用无盘同步。
默认:no
增量同步
只同步从机缺失部分数据的主从同步被称为增量同步。Redis的增量同步功能在2.8版本随{PSYNC}指令一同提供,通过该指令触发主从同步的从机会记录自身对主机数据的同步进度,也就是上文所说的从机偏移量。此外由于该版本后主机同样也会记录自身的偏移量,即整体的数据量,因此主/从机只要未曾因为重启等原因丢失各自的偏移量,那么主机就可以做到只对从机缺失的部分数据进行增量同步,从而避免全量同步的巨大开销。增量同步通常会发生于网络分区的场景,通俗的说就是主/从机断线重连的时候。由于该情况下主/从机都完好保存着各自的偏移量,因此当从机发送{PSYNC 主机运行ID 从机偏移量}指令时主机便可以轻松找到数据同步的中断点并恢复对后续指令的发送。注意!网络分区并不意味着必然会触发增量同步,因为主机为了节省开销并不会记录所有的写指令,即主机偏移量并不绝对可靠,从而导致从机偏移量也存在失效可能。因此这种情况下从机就会重新执行全量同步…该知识点会在下文讲解复制积压缓冲区时详述。
复制积压缓冲区是定长的环形数组。复制积压缓冲区是Redis设计保存已执行写指令的“数组”结构,当某Redis实例被作为主机启动时,无论其是否已被从机请求过同步,其主进程都会将已执行的写指令写入复制积压缓冲区中保存,用于为可能发生的增量同步提供指令支持。当主/从机发生断线重连后,从机会再次向主机发送{PSYNC}指令,并带上首次全量同步时获取的主机运行ID及后续不断更新的从机偏移量。而主机接收到{PSYNC 主机运行ID 从机偏移量}指令后如果确定主机运行ID与实际相符,那么就会将从机偏移量作为索引在复制积压缓冲区上定位从机首个未/最后已执行写指令的位置,从而将后续未执行的指令继续发送至从机执行以实现两者数据的同步。
需要注意的是:出于节省内存开销上的考量,复制积压缓冲区并不会将主机执行的写指令全量保存,而是只会保存指定数量的最新一批写指令,因此复制积压缓冲区被设计为可实现快速覆盖的环形数组结构。而当写指令的数量超出复制积压缓冲区的存储上限时,主进程会以覆盖最旧指令槽位的方式来保存最新指令。故而如果主/从机断线的时间过长或增量同步速度低于指令写入速度而导致从机偏移量在复制积压缓冲区上(因为指令覆盖)而失效,那么主机便只能再次使用全量同步来对从机进行数据同步了。因此为复制积压缓冲区设置一个合适的大小是相当重要的,关于其合适大小的计算公式/配置参数具体如下:
- 最小大小(bytes) = 断线重连的平均时间(s) * 单位时间的平均写指令数据量(bytes/s);
- 建议大小 = 最小大小 * 2。
名称:repl-backlog-size
作用:设置复制积压缓冲区的大小。
默认:1mb
一个值得思考的问题是:为什么Redis不直接使用复制缓冲区/复制积压缓冲区来进行全量/增量同步呢?因为仅从实现角度上看的话两者的任意一个都足以同时完成全量/增量同步两项行为。就比如复制缓冲区既然会在全量同步时保存从机尚未执行指令,那么增量同步时主机直接恢复对复制缓冲指令的发送不就可以了吗?这甚至都不需要再进行偏移量的索引定位;又比如复制积压缓冲区既然会保存最新一批写指令,那么全量同步时主机直接定位/发送复制积压缓冲指令不就好了么?又何必再发送复制缓冲指令呢?实际上这是Redis为了保证主从同步的内存低耗性/执行稳定性。
首先对于复制缓冲区来说,不直接用于增量同步是为了保证主从同步的内存低耗性。由于主/从机都无法保证缓冲复制指令不会在发送时因为断线而丢失,因此偏移量的索引定位依然必不可少,而这就要求复制缓冲区必须进行两项改进:一是复制缓冲区的结构必须如复制积压缓冲区一样支持索引,否则便无法通过偏移量进行定位;二是已发送的复制缓冲指令需要暂时保留,因为断线重连后主机可能需要再次发送丢失指令,而缺失的话便只能使用全量同步来处理。这两项改进先不提实现上是否困难,单单对已发送复制缓冲指令的保留而言就难以令人接收。因为复制缓冲区是私有的,即每个从机在主机上都有独立的复制缓冲区,因此保留将导致内存开销大幅增长,但不保留又需要使用开销更大的全量同步来进行处理,因此其相比全局/公有的复制积压缓冲区来说并不是实现增量同步的良好方案。
其次对于复制积压缓冲区来说,不直接用于全量同步是为了保证主从同步的执行稳定性。由于RDB快照文件的发送/网络传输相当耗时,因此复制积压缓冲区指令完全可能在RDB快照文件发送尚未结束的情况下就出现覆盖情况,从而因为指令缺失而再次触发全量同步,并极易进入不断触发的死循环中,因此其相比不覆盖且满溢时会阻塞内存读/写的复制缓冲区来说也并不是实现全量同步的好方案。也因此Redis会同时使用两者来各自作为全量/增量同步的底层实现。
通过{PSYNC}指令触发的增量同步流程具体如下:
- 长连接建立,从机向主机发送{PSYNC}指令以请求数据的增量同步;
- 主机接收{PSYNC}指令,随后向从机返回+CONTINUE @ 继续/增量同步回应告知将进行增量同步。并在确定主机运行ID/从机偏移量存在/符合/可用后向从机发送未执行的复制积压缓冲区指令以进行持续性的指令同步;
- 从机接收继续/增量同步回应,准备进行增量同步;
- 从机持续接收复制积压缓冲指令,并在确保RDB快照文件已加载完毕的情况下依次执行并更新从机偏移量,从而确保与主机达成数据的最终一致性。
阻塞
Redis支持在从机断线时阻塞。由于异步执行的原因,主机无法保证在宕机时已将所有数据同步至从机,因此数据丢失在主从同步中是不可避免的。此外数据丢失的量会与主/从机间的延迟正相关,即延迟越大则主机宕机时丢失的数据越多。因此Redis提供了以下配置来控制数据丢失的量处在允许范围内,其本质是令主机在从机数量不足/延迟过大的情况下阻塞内存读/写。
名称:min-slaves-to-write/min-replicas-to-write
作用:在5.0.0版本前/后设置正常同步的从机最小数量,当正常同步的从机小于该数量时主机的内存读/写会被阻塞。
默认:3
名称:min-slaves-max-lag/min-replicas-max-lag
作用:在5.0.0版本前/后设置从机的最大延迟时间,超出该时间将被判定为异常同步。
默认:10(s)
Redis支持令主从同步同步执行。Redis在3.0版本时提供通过{WAIT}指令为主从同步提供了同步执行策略。该指令下Redis会阻塞主机的内存读/写,直至其上游所有写指令在指定时间被同步至指定数量的从机为止,从而增强数据的安全性。但话虽如此,{WAIT}指令也无法保证主/从数据的强一致性,因为其不会在某从机同步失败的情况下回滚已同步的主/从数据。此外{WAIT}指令还会导致Redis性能的下降,根据测试所知在使用{WAIT}指令后Redis的TPS(每秒事务数)可能会下降超过50%,因此在具体使用{WAIT}指令时应当在数据安全/系统性能之间有所权衡。
- WAIT <numreplicas> <timeout>:阻塞内存读/写,直至上游写指令在指定时间内被同步至指定数量的从机为止。超出指定时间则取消阻塞,并返回实际同步成功的从机数量。
------------------------- 入参 -------------------------
numreplicas @ 复制数量:需要同步的从机数量;
timeout @ 超时时间:等待同步的具体时间(ms),为0时表示无限等待,这可能会导致Redis因为永久阻塞而丧失可用性。