一、背景介绍
Redis
Pipeline是一种高效的命令批量处理机制,可以在Redis中大幅度降低网络延迟,提高读写能力。Redis Cluster
Pipeline是基于Redis Cluster的pipeline,通过将多个操作打包成一组操作,一次性发送到Redis
Cluster中的多个节点,减少了通信延迟,提高了整个系统的读写吞吐量和性能,适用于需要高效处理Redis Cluster命令的场景。
本次使用到pipeline的场景是批量从Redis
Cluster批量查询预约游戏信息,项目内使用的Redis Cluster
Pipeline的流程如下,其中的JedisClusterPipeline是我们内部使用的工具类,提供Redis
Cluster模式下的pipeline能力:
JedisClusterPipeline使用
JedisClusterPipline jedisClusterPipline = redisService.clusterPipelined();
ListObject response;
try {
for (String key : keys) {
jedisClusterPipline.hmget(key, VALUE1, VALUE2);
}
// 获取结果
response = jedisClusterPipline.syncAndReturnAll();
} finally {
jedisClusterPipline.close();
}
二、故障现场记录
某天,收到了Dubbo线程池耗尽的告警。查看日志发现只有一台机器有问题,并且一直没恢复,已完成任务数也一直没有增加。
查看请求数监控,发现请求数归零,很明显机器已经挂了。
使用arthas查看Dubbo线程,发现400个线程全部处于waiting状态。
三、故障过程分析
Dubbo线程处于waiting状态这一点没有问题,Dubbo线程等待任务的时候也是waiting状态,但是查看完整调用栈发现有问题,下面两张图里的第一张是问题机器的栈,第二张是正常机器的栈,显然问题机器的这个线程在等待Redis连接池里有可用连接。
使用jstack导出线程快照后发现问题机器所有的Dubbo线程都在等待Redis连接池里有可用连接。
调查到这里,能发现两个问题。
- 线程一直等待连接而没有被中断。
- 线程获取不到连接。
3.1 线程一直等待连接而没有被中断原因分析
Jedis获取连接的逻辑在
org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)方法下。
public T borrowObject(long borrowMaxWaitMillis) throws Exception {
...
PooledObjectT p = null;
// 获取blockWhenExhausted配置项,该配置默认值为true
boolean blockWhenExhausted = getBlockWhenExhausted();
boolean create;
long waitTime = System.currentTimeMillis();
while (p == null) {
create = false;
if (blockWhenExhausted) {
// 从队列获取空闲的对象,该方法不会阻塞,没有空闲对象会返回null
p = idleObjects.pollFirst();
// 没有空闲对象则创建
if (p == null) {
p = create();
if (p != null) {
create = true;
}
}
if (p == null) {
// borrowMaxWaitMillis默认值为-1
if (borrowMaxWaitMillis 0) {
// 线程栈快照里所有的dubbo线程都卡在这里,这是个阻塞方法,如果队列里没有新的连接会一直等待下去
p = idleObjects.takeFirst();
} else {
// 等待borrowMaxWaitMillis配置的时间还没有拿到连接的话就返回null
p = idleObjects.pollFirst(borrowMaxWaitMillis,
TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
if (!p.allocate()) {
p = null;
}
}
...
}
updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
return p.getObject();
}