写点什么

生产环境 Redis 连接,长时间无响应被服务器断开问题

  • 2022 年 4 月 10 日
  • 本文字数:2035 字

    阅读完需:约 7 分钟

生产环境Redis连接,长时间无响应被服务器断开问题

上个月线上生产环境有几个接口出现异常响应,查看生产日志后发现,如下错误


线上 Redis 客户端使用的是SpringBoot默认的Lettuce客户端,并且没有指定连接池,connection reset by peer这个错误是当前客户端连接在不知情的情况下被服务端断开后产生,也就是说当前客户端 Redis 连接已经在服务端断开了,但是客户端并不知道,当请求进来时,Lettuce继续使用当前 Redis 连接请求数据时,就会提示connection reset by peer


一般情况下服务端断开连接都会发送FIN包通知客户端,但是当我在用tcpdump监控服务端 tcp 传输后,发现 Redis 服务端 tcp 连接在无活动一段时间,比如 10 分钟后会收到来自客户端的RST包,然而我的客户端也在使用 wireshark 抓包中,并没有发送给服务端RST包,这就很奇怪了,猜测这里是可能是服务器对 tcp 连接的限制导致,对长时间无活动的 tcp 连接强制断开处理。所以这里线上环境 Redis 连接偶尔产生connection reset by peer错误是被我复现出来了。


既然这里知道是 Redis 连接长时间无活动后被断开导致的 bug,那怎么解决?


博主一开始以为重试可以解决,但是发现事情没有想象的简单。上代码


   // 查询Redis    public <T> T getCacheObject(final String key) {        try {            ValueOperations<String, T> operation = redisTemplate.opsForValue();            return operation.get(key);        } catch (Exception e) {            log.error(e.getMessage(), e);            return retryGetCacheObject(key, 3);        }    }   // 重试查询Redis    public <T> T retryGetCacheObject(final String key, int retryCount) {        try {            log.info("retryGetCacheObject, key:{}, retryCount:{}", key, retryCount);            if (retryCount <= 0) {                return null;            }            Thread.sleep(200L);            retryCount--;            ValueOperations<String, T> operation = redisTemplate.opsForValue();            return operation.get(key);        } catch (Exception e) {            log.error(e.getMessage(), e);            return retryGetCacheObject(key, retryCount);        }    }
复制代码


上面代码的意思是第一次查询 Redis 发生异常后,每隔 200 毫秒在查 3 次。当实际运行时,发现这里会提示三次connection reset by peer错误,一直没有取到新的 Redis 连接。


到这里这个问题的我的解决思路其实就是怎么在 Redis 连接发生异常后,怎么创建一条新的连接进行代替。


不多说直接上代码:


    // Lettuce连接工厂    @Autowired    private LettuceConnectionFactory lettuceConnectionFactory;
/** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public <T> T getCacheObject(final String key) { try { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } catch (Exception e) { log.error(e.getMessage(), e); return retryGetCacheObject(key, 1); } }
public <T> T retryGetCacheObject(final String key, int retryCount) { try { log.info("retryGetCacheObject, key:{}, retryCount:{}", key, retryCount); if (retryCount <= 0) { return null; } lettuceConnectionFactory.resetConnection(); Thread.sleep(200L); retryCount--; ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } catch (Exception e) { log.error(e.getMessage(), e); return retryGetCacheObject(key, retryCount); } }
复制代码


在用当前 Redis 连接获取数据发生异常超过timeout间隔后,抛出异常,进入重试方法,使用 lettuceConnectionFactory.resetConnection()方法进行连接重置,创建一条新的连接后,继续获取数据,从而正常响应客户端。lettuceConnectionFactory对象是对Lettuce无池化连接的工厂实现,提供了 lettuceConnectionFactory.getConnection(); lettuceConnectionFactory.initConnection(); lettuceConnectionFactory.resetConnection();等获取、初始化、重置连接的方法


配合springboot配置timeout将获取数据的超时时间设置为 2 秒,从而将接口请求耗时也控制在 2 秒左右


  redis:    xx: xx    timeout: 2000
复制代码


到此生产环境这里SpringBoot项目下Lettuce客户端无池化连接偶尔断开的 bug 算是解决了


最后贴一下实战项目地址newbeemall,newbee-mall 商城的 mybatis plus 版本 实现了优惠卷领取, 支付宝沙箱支付,后台添加搜索,RedisSearch 分词检索

发布于: 刚刚阅读数: 5
用户头像

waynaqua 2020.03.10 加入

java开发工程师

评论

发布
暂无评论
生产环境Redis连接,长时间无响应被服务器断开问题 审核中_redis_越长大越悲伤_InfoQ写作平台