Redis学习记录:spring框架lettuce哨兵接入拓扑更新踩坑记录

  • 最近使用spring-data-redis接入容器化redis进行自测时,踩到一个坑。

  • 本篇文章简要记录一下排坑过程。

一、问题背景

此问题是自测时出现的,整体的流程如下:

  1. K8S容器化部署3实例哨兵集群 + 1主1从主从集群

  2. 业务客户端使用spring-data-redis库以哨兵模式接入,底层使用lettuce,使用容器化部署

  3. 使用delete --force删除主实例pod,模拟K8S节点宕机场景

  4. 业务客户端出现timeout报错,持续重试

  5. 观测哨兵信息,发现已经主从切换完成,哨兵信息中旧从节点已经提升为主

  6. 持续观察业务客户端,无法自动恢复,仍持续出现timeout报错;重启业务客户端,业务操作恢复正常

二、问题排查过程

此前已经看过lettuce的哨兵相关源码,可以确认lettuce原生逻辑,在这种情况下会监听导致提主事件刷新拓扑。

但是这次在spring框架内使用lettuce,却无法更新lettuce拓扑,仍在不断重试访问已经宕机的旧主。

把日志级别调整为debug模式,发现日志中并没有拓扑刷新的日志,说明拓扑并没有刷新。

这种情况我感觉是spring-data-redis库有点什么问题,故先去issue中搜索一下。

1
issue:  https://github.com/spring-projects/spring-data-redis/issues/1952

经过查询,果然找到一个类似情况的issue,核心信息汇总如下:

  1. lettuce设计上是异步连接,在模拟K8S节点宕机场景时,Fin/RST包无法正常发送,则异步连接短时间内不会自动断开。

  2. spring-data-Redis底层为lettuce时,会保持长连接。除非连接断开,否则拓扑不会主动刷新。

  3. 如果希望通过spring-data-Redis使用lettuce时持续追踪拓扑变化,则需要配置ReadFrom

所以最终可以确认,是lettuce设计上,默认配置就没兼容这种场景。

通过查看spring-data-redis相关源码,发现在配置ReadFrom后,每次执行操作前都会尝试按配置获取最新的主/从实例,这样就不会出现异步连接未断开导致拓扑无法刷新的问题了。

随后再次测试,在业务客户端中配置ReadFrom,注入故障后,发现业务操作可以恢复正常,由此问题解决。

三、小结

这个问题整体来看有点奇怪,核心是Fin/RST包无法正常发送,导致异步连接无法正常关闭刷新拓扑。

但是我感觉spring-data-redis理应处理这种场景,不应该基于连接来触发拓扑刷新,应该有个额外的刷新机制,结果却没有。

仔细看issue里的回复,开发人员认为额外的刷新可能会导致额外资源消耗。。。好吧,可能后续最佳实践里要提一嘴,spring-data-redis使用lettuce哨兵模式接入时,记得配置一下ReadFrom来避免这个问题。