关于Jfinal2.2中的Redis插件

波总:

Jfinal2.2中的Redis插件在使用中有什么注意事项?我在Controller中使用它,直接作为类的私有变量:

public class Api extends Controller {
   Private Cache redis = Redis.use();
   public void update(){
       redis.get("xxx");
       ...
     

以后在各方法中直接使用就行。但我把它使用到非Controller中,应该注意什么,如:

public class CdnLoadUpdate implements Job{
   Private Cache redis = Redis.use();
   @Override
   public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException  {
       redis.get("xxx");

            ....

我按上述方式作,定义一个每分钟的Job,运行的结果是Redis连接数一直增加,最后撑爆花连接池而出错,redis连接数图如下:

图片.png

请问波总有何建议


评论区

JFinal

2017-07-27 10:58

这样用着就挺好,Redis.use() 方法并不会动用连接,只有实际去操作 redis 的时候才需要从连接池拿连接,而 redis.get(...) redis.set(...) 这所有的方法都在 finally 块中关闭过连接的,不会造成连接泄漏的情况

你的程序连接撑爆了,注意下面几点:
1:redis 服务端允许的最大连接数是否达到,例如,很多客户端同时连,而这些客户端初始化连接池时会首先创建很多连接到池里面

2:客户端配置的最大连接数是不是过小,造成并发稍多时就耗尽

3:自己的程序中是否自行通过 getJedis() 获取过连接,但是却没有在 finally 块中关闭连接,造成连接资源泄漏

JFinal

2017-07-27 11:15

刚刚注意到你的的程序是在 Job 中来使用 redis ,那么你的这个 Job 始终是在一个线程之中,注意看一下 com.jfinal.plugin.redis.Cache.java 源码中的 close(Jedis jedis) 方法,这个方法如果从 ThreadLocal 中获取不到连接时才去真的关闭连接,以此来实现 redis 的事务功能

所以,如果你使用的是 RedisInterceptor 或者调用 Redis.call(...) 方法,那么 Cache 的 ThreadLocal 中会有值,而 Job 是一直运行着的线程,所以会造成连接无法被关闭,多加两行代码,在 finally 块中关闭一下连接,例如:
try {
cache.get(...);
} finally {
cache.removeThreadLocalJedis();
}

注意前面有几个关键点:
1:你的程序直接或者间接的向 Cache 的 ThreadLocal 属性中放入了连接
2:使用 cache 的代码一直在一个线程之中不结束,例如 job 这样的

JFinal

2017-07-27 11:17

单步调试,观察一下 Cache.close(Jedis) 这个方法之中的细节看一下那个 if 语句是否为真,程序是否真的 close() 掉了这个连接,一切都清楚了

linuxea

2017-07-27 12:07

@JFinal
詹总早上好。看到你们讲到redis有感而发,早上闲着没事看了一下文档和源码。有几个问题需要向您请教一下。

第一:RedisInterceptor 是如何提升性能表现的?(我看到文档里面定着有助于性能提高。)可是发现就算你获取到了当前线程的jedis,可是在使用之后也还是会在finally中马上关闭的呀
(code:cache.removeThreadLocalJedis();jedis.close(););

第二:就算没有用到RedisInterceptor,在使用Redis.use()的使用,所得到的Cache也是从当前线程中获取的哩。
(code:public Jedis getJedis() {
Jedis jedis = threadLocalJedis.get();
return jedis != null ? jedis : jedisPool.getResource();
});

如果这样的话,使用不使用RedisInterceptor又有什么影响呢?
因为jfinal本质上对jedis对象是使用ThreadLocal模式,和数据库连接Connection是一样的哩。

Joph_csu

2017-07-27 12:44

感谢波总,后来我把Job的频次调到每秒一次,最终发现是Cache中没有setnx,就使用getJedis()取连接来用,却没有关闭连接。现在连接数曲线平坦了,再次感谢~~

JFinal

2017-07-27 12:46

@linuxea RedisInterceptor 会将 redis 连接放在 ThreadLocal 之中,那么当前线程如果多次使用 redis 的话,用的是同一个连接,不用再多次获取连接。 具体细节你得仔细调试Cache 类中的 getJedis() 与 close(Jedis jedis) 这两个方法,注意 ThreadLocal 的行为

JFinal

2017-07-27 12:47

@Joph_csu 注意在 finally 块中进行关闭,否在出现异常时仍然会造成连接资源泄漏

linuxea

2017-07-27 13:03

@JFinal 我想了一下有点想出来,这样确实如果当前线程多次调用set,hset等方法是同一个连接,也就是说使用RedisInterceptor的时候,其实是相当共用一个连接同时使用redis的事务功能了。之前我想错了一些地方。谢谢詹总

JFinal

2017-07-27 13:05

@linuxea 单步去调试比光去想要高效得多,而且不易出错

linuxea

2017-07-27 13:18

@JFinal 好的会的。昨天那个tx问题调试了一个多小时。还在探索中... ...

JFinal

2017-07-27 13:48

@linuxea 这么来添加拦截器,立即就能调试出来:
@Before({Tx.class, Tx.class})

也就是说,连续添加两个 Tx 拦截器

飞羽

2018-05-28 19:27

@JFinal 遇到获取Jedis的问题了,没有关闭链接导致链接都满了,哈哈