版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011039332/article/details/85381051
前言 
这个是接着 上次的 这篇文章 37 一次获取redis连接阻塞引起的 "Thread pool is EXHAUSTED 的疑问之一 "那么 redis 连接池 中的空闲链接 为什么会被占用满了呢?"

然后 另外一点疑问, "到底是谁中断了 dubbo 处理请求的线程的呢", 因为不好复现, 还是没有想明白

问题的发现
原因 确实是因为连接泄露了, 在我的 redis 操作工具类里面, 有下面这样的一个 工具方法, 是用于给常用的 set, get 方法提供支撑

然后 之前我检查的时候, 我发现 "doWithCallback" 方法只有 当前 RedisUtils 里面使用, 也只有这里拿了连接的

然后 我忽略了一点, "getResource" 方法本身是被外部引用了的, 外面的其他 有一个地方需要使用 Jedis 连接, 然后 就是这里的代码 获取了连接, 没有释放, 然后 导致了连接被泄露

public <T> T doWithCallback(IRedisClientCallback<T> callback) {
T result = null;
long start = System.currentTimeMillis();
try (ShardedJedis jedis = getResource()) {
if (jedis != null) {
result = callback.run(jedis);
}
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);

// todo for debug, remove this after stable
long spent = System.currentTimeMillis() - start;
JSONObject stat = new JSONObject();
stat.put("active", shardedJedisPool.getNumActive());
stat.put("idle", shardedJedisPool.getNumIdle());
stat.put("waiters", shardedJedisPool.getNumWaiters());
stat.put("maxBorrowWaitTimeMillis", shardedJedisPool.getMaxBorrowWaitTimeMillis());
stat.put("meanBorrowWaitTimeMillis", shardedJedisPool.getMeanBorrowWaitTimeMillis());
LOGGER.error(" [forDebug] [doWithCallback] stats : " + JSON.toJSONString(stat) + ", spent : " + spent + " ms ...");
}
return result;
}

此问题的几个阶段如下

1. 此问题 最开始的版本是 "getMaxWaitMillis" 为之前系统默认的 -1, 然后 之后出现了这个问题, 发现所有的线程 都阻塞在这里了, 然后 影响到了业务

2. 然后 之后看了一下 "GenericObjectPool. borrowObject" 的代码, 然后 发现可以配置 maxWiatMillis, 然后 为了不影响业务 配置了一个 maxWiatMillis, 然后 并加了一下 上面的这些调试代码

3. 然后 过了一段时间, 妈的 此问题又出现了, 然后 之后的时候, 继续看了一下 "GenericObjectPool. borrowObject" 的代码, 然后 发现 这样一段代码, 如果 资源吃紧的话 主动释放一部分的一段时间没有使用了的连接

AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() &&
(getNumIdle() < 2) &&
(getNumActive() > getMaxTotal() - 3) ) {
removeAbandoned(ac);
}
然后 但是 "redis.clients.jedis.ShardedJedis" [jedis-2.9.0] 里面是没有提供 这个 AbandonedConfig 的配置的入口的, 然后 只好自己 copy 下来 改改, 增加了一个 配置 AbandonedConfig 的入口, 用起来 
4. 然后 后来想想, 这么处理 也不是办法啊, 然后 使劲找代码里面的问题, 然后 终于找到了 redis 连接泄露的原因, 模拟了一下情况, 写了一点简单的测试 代码

@Test
public void test03ResourceLeak() {
for (int i = 0; i < 100; i++) {
consumeRedisResourceWithLeak();
// consumeRedisResource();
}

ShardedJedis shardedJedis = (ShardedJedis) redisBasedCacheService.getResource();
System.out.println(" end ... ");
}

// case of connection leak
private void consumeRedisResourceWithLeak() {
ShardedJedis shardedJedis = (ShardedJedis) redisBasedCacheService.getResource();
}

// case of connection created and closed normally
private void consumeRedisResource() {
try (ShardedJedis shardedJedis = (ShardedJedis) redisBasedCacheService.getResource()) {
}
}

ShardedJedisPool
继承自 Pool, 里面 主要是 重写了一个自定义的 ShardedJedisFactory 来创建 Jedis 连接 以及一个连接生命周期的业务处理

其业务方法 主要是继承自 Pool, Pool 内部委托给 GenericObjectPool

GenericObjectPool

1. borrowObject(long borrowMaxWaitMillis)

主要供能为 从连接池里面获取一个连接

1. 如果配置了 AbandonedConfig, 并且当前连接池资源极度紧缺了, 移除 长期没有使用的链接[距离现在已经 removeAbandonedTimeout 秒没有使用的连接]

2. blockWhenExhausted 配置为 获取不到连接是否阻塞线程等待

borrowMaxWaitMillis 配置为获取连接 可以等待的毫秒数, 如果小于0 为阻塞直到获取到连接 [可能被中断]

如果不阻塞, 获取到连接直接返回, 获取不到 抛出异常

如果阻塞, 如果 borrowMaxWaitMillis 小于0, 阻塞直到 获取到连接,

否则 最大等待 borrowMaxWaitMillis 毫秒, 超时抛出异常

3. 获取到连接之后, 进行一些生命周期的操作, activateObject, validateObject 等等, 如果异常 有相应的补偿处理

public T borrowObject(long borrowMaxWaitMillis) throws Exception {
assertOpen();

AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() &&
(getNumIdle() < 2) &&
(getNumActive() > getMaxTotal() - 3) ) {
removeAbandoned(ac);
}

PooledObject<T> p = null;

// Get local copy of current config so it is consistent for entire
// method execution
boolean blockWhenExhausted = getBlockWhenExhausted();

boolean create;
long waitTime = 0;

while (p == null) {
create = false;
if (blockWhenExhausted) {
p = idleObjects.pollFirst();
if (p == null) {
create = true;
p = create();
}
if (p == null) {
if (borrowMaxWaitMillis < 0) {
p = idleObjects.takeFirst();
} else {
waitTime = System.currentTimeMillis();
p = idleObjects.pollFirst(borrowMaxWaitMillis,
TimeUnit.MILLISECONDS);
waitTime = System.currentTimeMillis() - waitTime;
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
if (!p.allocate()) {
p = null;
}
} else {
p = idleObjects.pollFirst();
if (p == null) {
create = true;
p = create();
}
if (p == null) {
throw new NoSuchElementException("Pool exhausted");
}
if (!p.allocate()) {
p = null;
}
}

if (p != null) {
try {
factory.activateObject(p);
} catch (Exception e) {
try {
destroy(p);
} catch (Exception e1) {
// Ignore - activation failure is more important
}
// 省略部分代码
}
if (p != null && getTestOnBorrow()) {
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = factory.validateObject(p);
} catch (Throwable t) {
PoolUtils.checkRethrow(t);
validationThrowable = t;
}
// 省略部分代码
}
}
}

updateStatsBorrow(p, waitTime);

return p.getObject();
}

2. returnObject(T obj)
回收给定的链接, 或者清理掉给定的链接

1. 首先校验 需要回收的链接是否正常

2. 执行生命周期相关操作 validateObject, passivateObject, deallocate 等等

3. 如果连接池正常运行, 并且 空闲的链接数量 大于 maxIdle, 取消注册 连接 并关闭这个 连接

否则, 根据 last in first out 的策略, 将连接 回收到 idleeObjects 队列

public void returnObject(T obj) {
PooledObject<T> p =www.michenggw.com allObjects.get(obj);

if (!isAbandonedConfig(www.meiwanyule.cn)) {
if (p == null) {
throw new IllegalStateException(
"Returned object not www.dasheng178.com currently part of this pool");
}
} else {
if (p == null) {
return; // Object was abandoned and removed
} else {
// Make sure object is not being reclaimed
synchronized(p) {
final PooledObjectState state = p.getState();
if (state == PooledObjectState.ABANDONED ||
state == PooledObjectState.INVALID) {
return;
} else {
p.markReturning(); // Keep from being marked abandoned
}
}
}
}

long activeTime = p.getActiveTimeMillis();

if (getTestOnReturn()) {
if (!factory.validateObject(p)) {
try {
destroy(p);
} catch (Exception e) {
swallowException(e);
}
updateStatsReturn(activeTime);
return;
}
}

try {
factory.passivateObject(p);
} catch (Exception e1) {
swallowException(e1);
try {
destroy(p);
} catch (Exception e) {
swallowException(e);
}
updateStatsReturn(activeTime);
return;
}

if (!p.deallocate()) {
throw new IllegalStateException(
"Object has already been retured to this pool or is invalid");
}

int maxIdleSave = getMaxIdle();
if (isClosed() |www.gcyL157.com | maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
try {
destroy(p);
} catch (Exception e) {
swallowException(e);
}
} else {
if (getLifo()) {
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
}
updateStatsReturn(activeTime);
}

3. evict()

在 GenericObjectPool 的通用构造方法 GenericObjectPool(PooledObjectFactory<T> factory, GenericObjectPoolConfig config)

里面, 启动了一个 timer, 来定时清理, 校验 idleObjects 队列里面的连接, 并且需要 确保连接池 最少保持minIdle 个连接

numTestsPerEvictionRun 大于等于0表示, 最多清理, 校验 numTestsPerEvictionRun  个连接, 小于0 表示 最多清理校验 (idleObjeects.size / (-numTestsPerEvictionRun) ) 个连接

每次定时任务 处理[清理, 校验] numTestsPerEvictionRun 个连接

1. 初始化迭代器, 如果 迭代器为空 或者迭代到了最后, 重新初始化迭代器

2. 获取下一个待处理的链接, 如果状态不对 说明被其他的线程取走了, continue

3. 先根据策略 判断当前连接是否应该被 evict, 如果应该 那就清理当前连接

否则 对当前连接进行校验, 处理生命周期相关操作 activateObject, validateObject, passivateObject

4. 最后, 如果 配置了 AbandonedConfig, 并且有 removeAbandonedOnMaintenance 配置, 移除 长期没有使用的链接[距离现在已经 removeAbandonedTimeout 秒没有使用的连接]

public void evict() throws Exception {
assertOpen();

if (idleObjects.size() > 0) {
PooledObject<T> underTest = null;
EvictionPolicy<T> evictionPolicy = getEvictionPolicy();

synchronized (evictionLock) {
EvictionConfig evictionConfig = new EvictionConfig(
getMinEvictableIdleTimeMillis(),
getSoftMinEvictableIdleTimeMillis(),
getMinIdle());

boolean testWhileIdle = getTestWhileIdle();

for (int i = 0, m =www.mhylpt.com getNumTests(); i < m; i++) {
if (evictionIterator == null || !evictionIterator.hasNext()) {
if (getLifo()) {
evictionIterator = idleObjects.descendingIterator();
} else {
evictionIterator = idleObjects.iterator();
}
}
if (!evictionIterator.hasNext()) {
// Pool exhausted, nothing to do here
return;
}

try {
underTest = evictionIterator.next();
} catch (NoSuchElementException nsee) {
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
i--;
evictionIterator = null;
continue;
}
if (!underTest.startEvictionTest()) {
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
i--;
continue;
}

if (evictionPolicy.evict(evictionConfig, underTest,
idleObjects.size())) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
} else {
if (testWhileIdle) {
boolean active = false;
try {
factory.activateObject(underTest);
active = true;
} catch (Exception e) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
}
if (active) {
if (!factory.validateObject(underTest)) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
} else {
try {
factory.passivateObject(underTest);
} catch (Exception e) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
}
}
}
}
if (!underTest.endEvictionTest(idleObjects)) {
// TODO - May need to add code here once additional
// states are used
}
}
}
}
}

AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
removeAbandoned(ac);
}
}
void ensureMinIdle() throws Exception {
int minIdleSave = getMinIdle();
if (minIdleSave < 1) {
return;
}

while (idleObjects.size() < minIdleSave) {
PooledObject<T> p = create();
if (p == null) {
// Can't create objects, no reason to think another call to
// create will work. Give up.
break;
}
if (getLifo()) {
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
}
}

4. 默认的 evict 策略

还是比较好理解, 两个维度, idleEvictTime, idleSoftEvictTime 的判断处理

public boolean evict(EvictionConfig config, PooledObject<T> underTest,
int idleCount) {

if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
config.getMinIdle() < idleCount) ||
config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
return true;
}
return false;
}

5. getMinIdle

不仅仅是简单的 return, 还有一些逻辑

public int getMinIdle() {
int maxIdleSave = getMaxIdle();
if (this.minIdle > maxIdleSave) {
return maxIdleSave;
} else {
return minIdle;
}
}

38 一次 redis 连接泄露的原因 以及 ShardedJedisPool的更多相关文章

  1. redis连接超时问题排查

    连接池无法获取到连接或获取连接超时redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource f ...

  2. go的mgo,连接未释放问题,连接泄露。

    api启动几天后,卡住(连接失败,超时) 异常原因 mongo连接被占满,无法建立mgo连接,返回信息 查询点用端口可知,97%的连接被api项目占用. api项目的mongodb连接“泄露”,某处的 ...

  3. Java内存泄露的原因

    Java内存泄露的原因 1.静态集合类像HashMap.Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector ...

  4. 牛客网Java刷题知识点之内存溢出和内存泄漏的概念、区别、内存泄露产生原因、内存溢出产生原因、内存泄露解决方案、内存溢出解决方案

    不多说,直接上干货! 福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号:   大数据躺过的坑      Java从入门到架构师      人工智能躺过的坑          ...

  5. java原生程序redis连接(连接池/长连接和短连接)选择问题

    最近遇到的连接问题我准备从重构的几个程序(redis和mysql)长连接和短连接,以及连接池和单连接等问题用几篇博客来总结下. 这个问题的具体发生在java原生程序和redis的交互中.这个问题对我最 ...

  6. 【Redis连接超时】记录线上RedisConnectionFailureException异常排查过程

    项目架构: 部分组件如下: SpringCloudAlibaba(Nacos+Gateway+OpenFeign)+SpringBoot2.x+Redis 问题背景: 最近由于用户量增大,在高峰时期, ...

  7. Redis 连接问题

    .NET 中使用 StackExchange.Redis 我为什么想写这个,总感觉很多介绍相应技术的博客,只是把内容从官网搬到自己的博客中,没有任何的实践,这样会给想学的人,没有任何好处,也可能我是自 ...

  8. Redis 连接池的问题

      目录 Redis 连接池的问题    1 1.    前言    1 2.解决方法    1     前言 问题描述:Redis跑了一段时间之后,出现了以下异常. Redis Timeout ex ...

  9. PHP- 深入PHP、Redis连接

    pconnect, phpredis中用于client连接server的api. The connection will not be closed on close or end of reques ...

随机推荐

  1. Visual studio 2010 TFS地址解析,让团队资源管理器不再显示IP地址

    第一步: 找到名为hosts的配置文件(路径C:\Windows\System32\drivers\etc\hosts)用记事本打开并写入需要的配置,例如我用到的是TFS服务器的IP地址为192.16 ...

  2. NavRouter

    使用方法只需要跟vue-router一样正常使用即可,这里我们新加了一个路由跳转方法nav: router.nav()//参数同router.replace一样. 路由跳转策略 首先说下路由跳转过程, ...

  3. Python-S9——Day82-CRM项目实战

    1.权限的概念: 2.RBAC的设计: 3.注册登录用户所有权限到session中: 4.权限的校验: 5.基于中间件的权限校验: 1.权限的概念: 1.1 项目与应用: Project App 1. ...

  4. 一段代码-Java

    在打算写这么一篇文章的时候,想到很多,觉得要是全都写下来的话,估计BZ也不知道要写多少,总之,好多吧!那么,就让BZ一切从简... 我们知道java它的特殊性在于,用它所写代码的运行是依靠自己的一套j ...

  5. TensorFlow | ReluGrad input is not finite. Tensor had NaN values

    问题的出现 Question 这个问题是我基于TensorFlow使用CNN训练MNIST数据集的时候遇到的.关键的相关代码是以下这部分: cross_entropy = -tf.reduce_sum ...

  6. 局部加权回归(LWR) Matlab模板

    将百度文库上一份局部加权回归的代码,将其改为模板以便复用. q2x,q2y为数据集,是n*1的矩阵: r是波长参数,就是对于距离的惩罚力度: q_x是要拟合的数据横坐标,是1*n的矩阵: 得到的q_y ...

  7. codeforces 319B Psychos in a Line(模拟)

    There are n psychos standing in a line. Each psycho is assigned a unique integer from 1 to n. At eac ...

  8. Python中的赋值语法

    Python中复制语法有6种 Basic Form >>>spam = 'spam' Tuple assignment >>>spam, ham = 'spam', ...

  9. Python 服务器端表单验证插件

    Python格式验证库 Cerberus 作者 MrStranger 关注 2016.08.02 14:44 字数 2140 阅读 79评论 0喜欢 1 Cerberus是一个验证Python对象.M ...

  10. iOS如何做出炫酷的翻页效果

    详情链接http://www.jianshu.com/p/b6dc2595cc3e https://github.com/schneiderandre/popping