上个月线上生产环境有几个接口出现异常响应,查看生产日志后发现,如下错误
线上 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 分词检索
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.