cluster 的集群模式实际中落地的较少,目前个人觉得 cluster 集群模式存在两个明显的缺点:
cluster 集群模式中,迁移使用到了一个命令 migrate。redis 官方文档 对 migrate 命令的说明:
migrate 命令是一个原子操作,它在执行的时候会阻塞进行迁移的两个实例,直到以下任意结果发生:迁移成功,迁移失败,等待超时。
命令的内部实现是这样的:它在当前实例对给定 key 执行 DUMP 命令 ,将它序列化,然后传送到目标实例,目标实例再使用 RESTORE 对数据进行反序列化,并将反序列化所得的数据添加到数据库中;当前实例就像目标实例的客户端那样,只要看到 RESTORE 命令返回 OK ,它就会调用 DEL 删除自己数据库上的 key 。
migrate 这个操作,会阻塞 redis 实例的主线程,期间此实例无法进行读写操作。因为 migrate 需要在主线程将 key-value 进行序列化,然后传送到目标端,目标端再导入,所以是阻塞的。
但是这样的话,为什么 migrate 不用从线程去做迁移呢,这样不就不用占用主线程,避免阻塞 redis 实例吗?
redis 只有主线程能做这件事,没有【从线程】,思考过 redis 这样的设计是否存在缺陷呢?我最初的理解,如果用子线程去执行 migrate 命令,就能避免阻塞 redis 实例了。比如 RDB 备份就是用子线程去做的。
关于这个问题,咨询了专业的 DBA 同事(保护同事隐私没有贴出姓名),得到的回复如下:
要看当初作者设计的意图。如果用子线程去执行 migrate,那么 redis 就是多线程操作数据,这样会引入线程之间的锁竞争和并发控制,这就违背了最初的设计。当然目前这样的实现也是一个缺点。此外,RDB 的生成是 fork 了主进程,生成快照,不是子线程。
我的理解,migrate 之所以需要占用主线程,是因为涉及了"对 key 做操作"。redis 的设计初衷,“对 key 做操作” 是单线程去处理的。通过单线程的设计,避免了线程之间的锁竞争和并发控制。
RDB 备份是调用了操作系统的 fork 能力生成了快照。并没有"对 key 做操作"。RDB 备份不涉及多线程操作数据,没有违背设计初衷。
使用 migrate 命令做迁移,会阻塞 redis 实例的主线程,期间此实例无法进行读写操作。
咨询了 DBA 同事,解决方案是用 DBA 自己的工具(这个工具可以根据具体情况去开发),将数据拆分迁移到其他分片,并没有使用 redis 官方的命令 migrate。
cluster 中的节点是去中心化的结构。以 cluster 模式的故障监测与转移举例,在《redis 设计与实现》17.6 集群 – 复制与故障转移,提到了:
可以看出,cluster 模式去中心化的架构,比较复杂,且没有一个可以统一管理的组件。这是不利于根据实际情况,做一些定制化操作,以及做集中监控和管理的。
如果想要一个中心化的组件去管理集群,可以考虑引入 sentinel 哨兵的思想。