解决springboot中链接redis会timeout的问题

共 3681字,需浏览 8分钟

 ·

2022-01-04 22:12

    之前写项目遇见一个问题,就是我的项目上线后,总是经常会报错timeout,我一看是redis链接timeout,首先我端口打开了,为啥还报错我就很纳闷,只得先重启一下防火墙。重启过后的确可以正常访问了,但是一段时间后依然还是会出现timeout的错误。只能在重启,这个问题始终没有得到根本性的解决!

    

    因为我使用的是springboot,所以一开始并没有往代码方向去考虑,直接怀疑服务器有问题,(比较springboot目前用的人比较多也没出啥问题也就比较相信它,同时我服务器又是最便宜的哪一款,就怀疑到服务器头上了,便宜没好货哈哈),但还是没有发现有啥问题,最后只能去看代码...


    后面我用jedis重新写了个小test,发现正常链接,但是springboot的redisTemplate就是有问题,那原因清楚了,可能真的是springboot的问题~

    我看了springboot内嵌实现链接redis的源码,发现springboot内嵌了Luttcure的技术,Luttuce和jedis pool都是一种池化技术,就和数据库的Durid、HkariaCP等这些链接池一样。

而Luttuce有个问题就是Luttuce不会自动刷新redis的拓扑结构的,所以会造成一段时间过后导致链接超时,毕竟链接池也是有最大等待时间的。

拓扑结构

拓扑结构也称为树状主从架构,咱们平常熟悉的主从架构便是通过读写分离来降低住服务器的压力,但是一般master节点进行写操作,slave节点进行读操作,在读多写少的场景,虽然降低了主服务器的压力,比如执行keys sort等操作保证了master的稳定性,但如果写并发某个时刻比较高时,主节点需要向挂载的多个slave节点发送写命令,这就又可能导致master的负载过高而影响服务的稳定性。

因此我们就产生了树状主从架构,slave节点可以复制主节点数据,也可以作为其他slave节点的master节点继续向下复制。解决了主从的不足

         master --> slave1 --> slave2 --> slave3

这降低了主节点的负载,所以如果master需要挂载多个slave时,而我们又想保证master的稳定性,就可以采用这种拓扑结构。


    而springboot内嵌的Luttuce链接池,默认不会改变redis集群的拓扑结构,当我们在超时时间触发后,springboot与redis的链接就断开了,而下次访问时还会访问这个已经超时的拓扑节点,导致访问不成功。这就是springboot操作redis偶尔会timeout的问题原因所在。

    解决方案呢有很多,大致就是及时更新这个拓扑节点就好了,目前可以通过在Luttuce留下的钩子函数中去自定义刷新的拓扑逻辑,比如启一个cron定时任务去做一个心跳检测去验证链接是否异常来完成。


第一种方案:

b2a46c1ef730a054e4980f34423aaa67.webp

springboot中预留的钩子函数中执行的customize的方法。

在springboot预留的接口中自定义类实现LettuceClientConfigurationBuilderCustomizer接口,实现customize方法,其中开启自定义刷新和定时刷新,而springboot实现的钩子函数中会自动去执行customize方法

/** * 通过ClusterTopologyRefreshOptions 开启定时刷新和自适应刷新 * @author azh */@Componentpublic class MyLuttuceCronTask implements LettuceClientConfigurationBuilderCustomizer {

@Override public void customize(org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigurationBuilder) { // ClusterTopologyRefreshOptions 用于开启自适应刷新和定时刷新 防止timeout 错误 ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() // 开始自适应刷新 .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT, ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS) .enableAllAdaptiveRefreshTriggers() .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10)) //开启定时刷新 时间间隔自己定义 .enablePeriodicRefresh(Duration.ofSeconds(15)) .build(); clientConfigurationBuilder.clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build());
}}

582427c8b484c1925156d4d9f1a1a696.webp


第二种方案:

    Lettuce提供了链接校验的方法,只是默认没有开启,我们可以开启它,然后通过一个定时任务来解决这个timeout的问题。

/** * 开启获取链接的校验 * @author azh */public class GetLettuceConnection implements InitializingBean {
@Autowired private RedisConnectionFactory redisConnectionFactory;
@Override public void afterPropertiesSet() throws Exception { if (redisConnectionFactory instanceof LettuceConnectionFactory) { LettuceConnectionFactory factory = (LettuceConnectionFactory) redisConnectionFactory; factory.setValidateConnection(true); } }}
/** * 每隔2s校验lettuce是否异常,解决lettuce因为不更新redis拓扑结构导致netty无法及时监控到导致timeout的问题 * @author azh */@Componentpublic class RedisCronTask {
@Autowired private RedisConnectionFactory redisConnectionFactory;
// 每2s执行一下 @Scheduled(cron = "0/2 * * * * ?") public void task() { if (redisConnectionFactory instanceof LettuceConnectionFactory) { LettuceConnectionFactory factory = (LettuceConnectionFactory) redisConnectionFactory; factory.validateConnection(); } }}

开启获取链接的校验

78ba3dfc1237ba5ecd4b52b5a28f9190.webp

定时任务去校验luttuce是否异常

6fc232f152ce64999c77a2bb887fbfb9.webp



     最后其实还有个比较简单但头疼的解决方案,那就是每隔一段时间都往redis的某个数据库中去set一个值,不过这种方案会耗费网络资源,但耗费也不是太大,算是一种中肯的解决办法吧。



浏览 309
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报