从Redis连接池获取连接失败的原因说起

更新时间:2023-05-12 02:00:47 阅读: 评论:0

从Redis连接池获取连接失败的原因说起
问题描述
其他业务线的同学在测试环境发现应⽤程序⼀直不能获取redis连接,我帮忙看了下。 ⾸先看应⽤错误⽇志
Caud by: org.dis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.pti    at org.tion.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:97)
at org.tion.Connection(JedisConnectionFactory.java:143)
at org.tion.Connection(JedisConnectionFactory.java:41)
at org.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:85)
at org.Connection(RedisConnectionUtils.java:55)
at org.ute(RedisTemplate.java:169)
at org.ute(RedisTemplate.java:149)
... 76 more
Caud by: redis.ptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.Resource(Pool.java:22)
at org.tion.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:90)
... 83 more
Caud by: java.util.NoSuchElementException: Could not create a validated object, cau: ValidateObject failed
at s.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:871)
at redis.clients.Resource(Pool.java:20)
... 84 more
问题调查
确定环境
发现是使⽤spring-data-redis通过jedis连接的redis服务端。 这个系统的代码很久没动,已经忘记了。先看看使⽤的jar版本吧。 查看应⽤程序使⽤的相关jar:
lsof -p 19377 | grep -E "jedis|pool|redis"
发现输出的jar包含:commons-pool-1.3.jar、spring-data-redis-1.1.1.RELEASE.jar、jedis-2.1.0.jar 翻了下
try {
_factory.Pair().value);
if(_testOnBorrow &&
!_factory.Pair().value)) {
throw new Exception("ValidateObject failed");
}
synchronized(this) {
_numInternalProcessing--;
_numActive++;
}
Pair().value;
}
catch (Throwable e) {
PoolUtils.checkRethrow(e);
// object cannot be activated or is invalid
try {
_factory.Pair().value);
} catch (Throwable e2) {
PoolUtils.checkRethrow(e2);
// cannot destroy broken object
}
synchronized (this) {
_numInternalProcessing--;
if (!newlyCreated) {
<();
_allocationQueue.add(0, latch);
}
allocate();
}
if(newlyCreated) {
throw new NoSuchElementException("Could not create a validated object, cau: " + e.getMessage());
}
el {
continue; // keep looping
}
}
可见客户端应该是配置了testOnBorrow,在校验连接时失败了。
java操作redis有多种客户端,项⽬使⽤spring-data-redis操作redis,在spring-data-redis中也有不同的客户端实现如jedis,lettuce等。根据错误⽇志推断使⽤的redis客户端实现为jedis。 查看 在中定义了校验对象的代码。
public boolean validateObject(final Object obj) {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
try {
return jedis.isConnected() && jedis.ping().equals("PONG");
} catch (final Exception e) {
return fal;
}
} el {
return fal;
}
}
通过wireshark查看TCP包并确定问题原因
熟悉redis的同学都知道,redis客户端发送“PING”后服务端会返回⼀个“PONG“作为回应,⼀般会作为连接的检验⽅法。 既然校验报错,那抓包看看请求和响应吧!
⾸先查看⽹卡编号ip a 再使⽤tcpdump对eth1⽹卡的6379端⼝数据抓包。
tcpdump -i eth1 port 6379 -w target.cap
最后使⽤wireshark对target.cap进⾏分析,可借助进⾏分析。 根据应⽤错误⽇志打印的时间,查询到此时客户端(应⽤服务器)向服务端(redis服务器)发送了⼀个RST包。
感觉是有问题的。就往上查了下。
可以看到,箭头位置上⽅客户端发送了PING命令,箭头位置应该返回客户端⼀个PONG作为响应。⽽是返回了以下信息:
MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data t are disabled. Plea check Redis logs for details about the error.
意思是,redis服务端配置了RDB快照持久化,但当前不能进⾏持久化。有可能修改数据集的命令都被禁⽤了。(但是通过看源码发现,除了涉及修改的命令,PING也在禁⽤之列,,⽽读取涉及的命令应该不会受到影响) 以下代码是redis-3.2.9 rver.c中in processCommand(client *c)发⽣持久化异常后的处理代码
/* Don't accept write commands if there are problems persisting on disk
* and if this is a master instance. */
if (((rver.stop_writes_on_bgsave_err &&
rver.saveparamslen > 0 &&
rver.lastbgsave_status == C_ERR) ||
rver.aof_last_write_status == C_ERR) &&
rver.masterhost == NULL &&
(c->cmd->flags & CMD_WRITE ||
c->cmd->proc == pingCommand))
{
flagTransaction(c);
if (rver.aof_last_write_status == C_OK)
addReply(c, shared.bgsaveerr);
el
addReplySds(c,
sdscatprintf(sdmpty(),
"-MISCONF Errors writing to the AOF file: %s\r\n",
strerror(rver.aof_last_write_errno)));
return C_OK;
}
之后客户端发送QUIT命令退出,服务器返回OK响应退出成功。 那个返回的配置错误信息是说在持久化RDB时出现了问题。于是到redis服务器上看了下磁盘信息和redis的⽇志,果然,磁盘空间不⾜了。
到此,问题基本查明,是由于redis所在服务器磁盘不⾜导致,由于是测试服务器,也没有配置磁盘的监控。腾出空间后即可恢复。
对RST包的理解
但是我还有⼀个问题,那就是为什么会有⼀个RST包呢?如果没有那个RST包,其实问题还不好发现,虽然按照错误⽇志的时间,挨个查找Redis数据包的信息,能够查询出来,但是RST⽆疑从⼀开始就吸引了我的注意,让我能够更加快速的定位问题。
初识RST
那现在问题来了,为什么会有RST包呢? ⾸先了解⼀下RST。(可参考 , ) 归纳起来,当以下任⼀情况发⽣时,会产⽣RST包:到不存在的端⼝的连接请求
异常终⽌⼀个连接
检测半打开连接
jedis与redis的关闭机制
观察RST之前的⼏个包
使⽤wireshark的专家信息查看多个RST包,发现RST之前都会有QUIT,OK的交互。那看来应该是框架层⾯的问题。 再翻看上⾯GenericObjectPool的相关代码,在borrowObject时如果发⽣异常,会调⽤destroyObject()⽅法,这个destroyObject是延迟到⼦类实现的,也就是上⾯说到的JedisPool。
public void destroyObject(final Object obj) throws Exception {
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
if (jedis.isConnected()) {
try {
try {
jedis.quit();
} catch (Exception e) {
}
jedis.disconnect();
} catch (Exception e) {
}
}
}
}
最终调⽤redis.clients.jedis.Connection的disconnect,关闭输⼊输出流。
public void disconnect() {
if (isConnected()) {
try {
inputStream.clo();
outputStream.clo();
if (!socket.isClod()) {
socket.clo();
}
} catch (IOException ex) {
throw new JedisConnectionException(ex);
}
}
}
这也就解释了为什么会出现RST包: 客户端请求QUIT,服务端返回OK。(此时客户端在接收完quit返回后,调⽤了disconnect⽅法,导致连接断开)紧接着服务端发起TCP挥⼿,发送FIN包到之前交互的客户端51311端⼝,但调⽤完disconnect的客户端已经断开了和服务端的连接。客户端只能通过发送RST,通知服务端“你发送了⼀个到不存在的端⼝的关闭请求”。
翻看新版的jedis代码,除了将之前JedisPool中实现的代码挪到了JedisFactory中实现,⼤致逻辑依然没有改变()
// 2.10 JedisFactory
@Override
public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception {
final BinaryJedis jedis = Object();
if (jedis.isConnected()) {
try {
try {
jedis.quit();
} catch (Exception e) {
}
jedis.disconnect();
} catch (Exception e) {
}
}
}
@Override
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
final BinaryJedis jedis = Object();
try {
HostAndPort hostAndPort = ();
String connectionHost = Client().getHost();
int connectionPort = Client().getPort();
Host().equals(connectionHost)
&& Port() == connectionPort && jedis.isConnected()
&& jedis.ping().equals("PONG");
} catch (final Exception e) {
return fal;
}
}
⽽disconnect最终调⽤的Connection有变化。

本文发布于:2023-05-12 02:00:47,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/885814.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:客户端   问题   连接
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图