项目背景
事情经过是这样的..........
我负责的项目又改了需求(难受 -_-),实现起来要在用户登录后存入缓存的数据中添加一项数据对用户进行标记,然而我加了这个字段之后测试就会报错(那肯定就是这个字段的问题啦~)
Java报错是这个样子的
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:103)
at redis.clients.jedis.JedisPool.returnBrokenResource(JedisPool.java:239)
at redis.clients.jedis.JedisPool.returnResource(JedisPool.java:255)
at redis.clients.jedis.JedisPool.returnResource(JedisPool.java:16)
at org.springframework.data.redis.connection.jedis.JedisConnection.close(JedisConnection.java:257)
at org.springframework.data.redis.core.RedisConnectionUtils.releaseConnection(RedisConnectionUtils.java:206)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:205)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:153)
at org.springframework.data.redis.cache.RedisCache.put(RedisCache.java:140)
at org.springframework.data.redis.cache.RedisCache.put(RedisCache.java:125)
at org.springframework.cache.interceptor.AbstractCacheInvoker.doPut(AbstractCacheInvoker.java:82)
at org.springframework.cache.interceptor.CacheAspectSupport$CachePutRequest.apply(CacheAspectSupport.java:651)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:358)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:299)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
报错倒是很简单,在使用Jedis之后不能正常释放这个Jedis资源,我们在使用一个Jedis资源之后如果不能把它放回Redis连接池,后续其他缓存操作就会创建新的连接,而我们这个没有正常释放的Jedis会一直被占用。
我的代码是这样的
//缓存session对应的用户
String redisPrefix = RedisPrefix.getSessionKey(session);
Map<String ,String> userInfo = new HashMap<>();
userInfo.put("userId", userId);
userInfo.put("pid", pid);
userInfo.put("channelUid", openId);
userInfo.put("BindUser", bindUid); //这个是我新加的
log.info("redis_key: "+redisPrefix+" ||userInfo: "+userInfo);
RedisUtil.hmsetex(CommonConfig.REDIS_DB, redisPrefix, userInfo, CommonConfig.SESSION_EXPIREDTIME);
log.info("save session");
RedisUtil.hmsetex方法
public static String hmsetex(int db, String key, Map<String, String> value, int seconds) {
Jedis jedis = null;
String result = null;
try {
jedis = getJedis();
if(jedis != null) {
jedis.select(db);
result = jedis.hmset(key, value);
jedis.expire(key, seconds);
}
return result;
} catch (Exception e) {
e.printStackTrace();
returnResource(jedis);
return result;
} finally {
returnResource(jedis);
}
}
释放资源
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedis.close();
//jedisPool.returnResource(jedis);
}
}
jedis的close方法在Java源码中可以找到,就是正常的释放资源,这里就不再贴出来了。
在网上看了诸多解决方法,然而并不对症,但是却提醒了我错误所在。有一篇文章分析原因是Jedis关闭了两次,第二次关闭的时候因为这个Jedis实例已经不存在了,所以无法把它放回连接池。我新加参数bindUid是从数据库中获取的,如果数据库中这个值为null,那么这里存入缓存的时候这个值也是null,所以我猜测很有可能这里报错导致Jedis异常关闭,之后调用returnResource的时候二次关闭导致报错。
为了证实这个猜测,我在本地起了一个Redis,自己测试结果如下
127.0.0.1:6379> hset search baidu "BAIDU"
(integer) 1
127.0.0.1:6379> hset search google "GOOGLE"
(integer) 1
127.0.0.1:6379> hgetall search
1) "baidu"
2) "BAIDU"
3) "google"
4) "GOOGLE"
127.0.0.1:6379> hset search sougou
(error) ERR wrong number of arguments for 'hset' command
可以看到当插入数据sougou的时候因为value是null,所以会出现报错,之后确认数据也没存入Redis,然后我又测试了其他几种存入方式
127.0.0.1:6379> hset search 360 null
(integer) 1
127.0.0.1:6379> hset search yahoo ""
(integer) 1
127.0.0.1:6379> hgetall search
1) "baidu"
2) "BAIDU"
3) "google"
4) "GOOGLE"
5) "360"
6) "null"
7) "yahoo"
8) ""
当我们存入360的value为null的时候,值并不是nil而是一个字符串"null",同样的,我存入值为nil的时候实际并不是空,而是字符串的"nil"。由此可得知使用Jedis操作Redis的时候并不支持存入空值,如果想要存入空值应该存入一个空字符串,所以我把我的代码改成了如下的样子
//缓存session对应的用户
String redisPrefix = RedisPrefix.getSessionKey(session);
Map<String ,String> userInfo = new HashMap<>();
userInfo.put("userId", userId);
userInfo.put("pid", pid);
userInfo.put("channelUid", openId);
userInfo.put("BindUser", bindUid == null ? "" : bindUid); //进行判断,存入值
log.info("redis_key: "+redisPrefix+" ||userInfo: "+userInfo);
RedisUtil.hmsetex(CommonConfig.REDIS_DB, redisPrefix, userInfo, CommonConfig.SESSION_EXPIREDTIME);
log.info("save session");
然后就不会报Could not return the resource to the pool了~~~
这里只是给大家提供另一种思路,如果你检查了你的Redis连接没有问题,而且之前的代码逻辑可以正常释放Jedis资源,那么你可以按照我的思路去检查下存入的value有没有可能是null
目前只发现Jedis会出现这种情况,Redistemplate还没有发现这种情况
网友评论