笔者一直维护的稳定基础服务测试环境不稳定了,这能忍!盘他,虽然不一定能完全盘的了。
hrexternal 基础服务对外提供公司员工获取的多个接口,很多接口访问频率比较高,加了缓存,使用的是redis,但是redis最近2个月测试环境已经出问题了,时不时的报错,之前流程平台也报过错,只不过是随机的,不是必现的。当时也是没有具体原因,只是将底层的redis实例换掉了。然后就好了,这个服务呢由于历史原因还有很多其他服务是用的同一个redis实例,换的话需要好几个服务一起换,保障稳定性。
这次出现的问题更严重,因为每隔几分钟就会报错,get报错,put也会报错。所以就跟进排查了下。
Redis版本:3.0.7
Jedis版本:2.8.0
异常如下:
这俩异常不经常遇到,但是一旦遇到肯定是比较麻烦的。
笔者也是百度了很多,很多,从下面的链接中了解到一些信息:
https://blog.csdn.net/aubdiy/article/details/53511410
也是按照上面的思路进行排查:
1.找DBA帮忙看redis是否有改动配置,没有
2.看超时时间,客户端没有单独设置连接参数,默认超时时间应该是2秒。
3.可能是网络问题。但是实际上不是。
4.根据jedis github上面的issues讨论内容发现具体原因也没有说出来,但是出现这个问题的人确实挺多的。解决的人基本上都加了Jedis的连接配置了,刚好我们的没有加,还有可能解决。
这里就揭开了针对于Jedis配置的一场探索之路。
首先看这个hrexternal服务的jedis初始化代码:
/** * 初始化资源池 */ static { try { if (jedisSentinelPool ==null) { logger.info("init JedisSentinelPool is start...."); logger.info("redis_ip1:"+RedisConfig.redis_ip1+",redis_port1:"+RedisConfig.redis_port1); logger.info("redis_ip2:"+RedisConfig.redis_ip2+",redis_ip2:"+RedisConfig.redis_port2); logger.info("redis_ip3:"+RedisConfig.redis_ip3+",redis_ip2:"+RedisConfig.redis_port3); Set<String> sentinels = new HashSet<String>(); sentinels.add(new HostAndPort(RedisConfig.redis_ip1, Integer.parseInt(RedisConfig.redis_port1)).toString()); sentinels.add(new HostAndPort(RedisConfig.redis_ip2, Integer.parseInt(RedisConfig.redis_port2)).toString()); sentinels.add(new HostAndPort(RedisConfig.redis_ip3, Integer.parseInt(RedisConfig.redis_port3)).toString()); jedisSentinelPool = new JedisSentinelPool(RedisConfig.master, sentinels); logger.info(" init JedisSentinelPool is end...."); } }catch(Exception e){ logger.error("---->init JedisSentinelPool was failed,the msg is " + e.getMessage(), e); } } /** * 获取资源 * @return * @throws Exception */ public static synchronized Jedis getJedis() throws Exception { try { if(jedisSentinelPool != null) { Jedis e = jedisSentinelPool.getResource(); return e; } else { return null; } } catch (Exception e) { e.printStackTrace(); logger.error(e); return null; } }
使用的是Jedis哨兵模式进行Jedis初始化,同时使用Jedis连接池。出现上面的异常很多原因都跟连接池的连接有关。因此有必要分析一下Jedis的连接池和连接配置参数,如下图是Jedis连接配置参数和Jedis的连接池对象的类图:
其中只有GenericObjectPoolConfig,BaseObjectPoolConfig不是Jedis中的类,其他都是。这俩类是jedis依赖的另一个jar包:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.2</version> <type>jar</type> <scope>compile</scope> </dependency>
这个包是不是看着既熟悉又陌生。这个竟然是java对象池池化技术的一个实现,相关文章如下:
https://blog.51cto.com/andrewli/2148179
当然本文的分析内容也包括这个,其中Jedis的一些配置参数也跟这个池化对象配置有关。
下面是我整理的一个配置参数介绍:
由于上面代码的配置是使用默认的参数,也就是说当链接出现问题的时候你是不知道是客户端出的问题还是服务端出的问题,跟DBA确认了一些服务端的参数:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 512mb 128mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
timeout 60 配置的60s。
由于服务端没有动配置,客户端没有动配置,也没有动代码。封装Jedis操作的每个API都检查了,最后都有finally代码块保证jedis用完会close.
不存在链接泄露问题。那为啥上面的错会发生?为啥稳定运行了很长时间最近才报错。
当然几个可能的方向
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setTestOnBorrow(true); jedisPoolConfig.setTestOnReturn(true); jedisPoolConfig.setTestOnCreate(true); jedisPoolConfig.setMaxTotal(50); jedisPoolConfig.setMaxIdle(10); jedisPoolConfig.setMinIdle(1); jedisPoolConfig.setMaxWaitMillis(3000); jedisSentinelPool = new JedisSentinelPool(RedisConfig.master, sentinels,jedisPoolConfig);
部署完之后,发现异常不再出现。
虽然具体原因没有找到但是通过jedis开源代码和issues可以得到一些结论:
https://github.com/xetorthio/jedis/issues/932
https://blog.csdn.net/SakuraInLuoJia/article/details/89874287也就是说有2点建议
将客户端版本与服务端版本尽量保持一致。
当然如果你遇到这种问题的话,通过上面的方式还是搞不定,说明你没有找到正确的配置。即使有另一份配置放在你面前,它可能也不能解决你的问题,但至少是多了一种尝试。
本文由博客一文多发平台 OpenWrite 发布!
架构设计@工程设计@服务稳定性之路