背景
随着系统使用用户上升,我们也愈发多的使用到了redis组件!
比如在做session共享时 tomcat使用redis做session 参考https://github.com/jcoleman/tomcat-redis-session-manager
当然关于session共享在tomcat中使用存在一些限制
- tomcat指定版本
- web应用使用jedis和common pools 需要指定版本 容易出现jar冲突
- 运维配置对应redis信息 连接池开发无感
因此更多可以考虑使用spring-session通过redis来管理session
当然我们目前的场景使用shiro做session管理【shiro可以委托给容器或者第三方组件】
那么当使用redis组件多了的场景我们就极容易碰到如下的错误
Could not get a resource from the pool
简要分析一下我们系统中发生该错误的场景
分析
我们在某些场景下报错如下
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:442)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1082)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:623)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1756)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1715)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:50)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)
at net.oschina.j2cache.redis.support.RedisSingleFactory.getResource(RedisSingleFactory.java:23)
at net.oschina.j2cache.redis.support.RedisSingleFactory.getResource(RedisSingleFactory.java:12)
at net.oschina.j2cache.redis.RedisCacheProxy.getResource(RedisCacheProxy.java:47)
at net.oschina.j2cache.redis.RedisCacheProxy.hset(RedisCacheProxy.java:68)
at net.oschina.j2cache.redis.RedisCache.put(RedisCache.java:158)
... 79 common frames omitted
Caused by: java.util.NoSuchElementException: Unable to validate object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:506)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)
at redis.clients.util.Pool.getResource(Pool.java:48)
... 85 common frames omitted
一个关键词出现在了‘Unable to validate object’堆栈中!
在对应的连接池代码中可以看到
if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = factory.validateObject(p);
} catch (Throwable t) {
PoolUtils.checkRethrow(t);
validationThrowable = t;
}
if (!validate) {
try {
destroy(p);
destroyedByBorrowValidationCount.incrementAndGet();
} catch (Exception e) {
// Ignore - validation failure is more important
}
p = null;
if (create) {
NoSuchElementException nsee = new NoSuchElementException(
"Unable to validate object");
nsee.initCause(validationThrowable);
throw nsee;
}
}
}
}
由于我们redis连接池配置了validate
所以会在borrow之后执行validate操作
我们查看具体的validate操作代码
@Override
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
final BinaryJedis jedis = pooledJedis.getObject();
try {
HostAndPort hostAndPort = this.hostAndPort.get();
String connectionHost = jedis.getClient().getHost();
int connectionPort = jedis.getClient().getPort();
return hostAndPort.getHost().equals(connectionHost)
&& hostAndPort.getPort() == connectionPort && jedis.isConnected()
&& jedis.ping().equals("PONG");
} catch (final Exception e) {
return false;
}
}
很明显当拿到redis的连接之后需要执行ping指令
而当服务器正常的场景 redis将返回PONG
222627_pT5j_871390.png那么是什么情况导致我们可以连接到redis而又无法正常的ping该服务呢?
怀疑如下:
- 大型数据过期导致redis服务被占 比如某个hash过期
- 持久化失败使得写命令失败
我们知道 大key的过期或者试用类似于keys的指令时十分耗时 由于redis单进程的特性将会阻塞其他指令!
来查询一下报错日志 在短短时间内迅速报错
[root@iZ11to3arruZ logs]# grep "Unable to validate object" erp-error.log |wc -l
2357
如果大key过期的话那么应当会出现一会之后系统正常可以使用!
然而线上出现的问题是服务无法正常使用
因此考虑问题2
我们检查zabbix相关
222658_JMA6_871390.png当redis无法使用的时候恰好是内存最少的时候 当重新启动服务器时系统又可以正常使用!
那么考虑如下问题,是否是内存不足导致redis出现问题呢?
带着上述疑问 找到了相关说明!
查询到如下说明
stop-writes-on-bgsave-error
我们系统中使用 是yes 因此当内存不足的时候将无法序列化成文件
但是我们的内存明明还有接近2G呢!
222733_EZeP_871390.pngRedis在保存数据到硬盘时为了避免主进程假死,需要Fork一份主进程,然后在Fork进程内完成数据保存到硬盘的操作,如果主进程使用了4GB的内存,Fork子进程的时候需要额外的4GB,此时内存就不够了,Fork失败,进而数据保存硬盘也失败了。
因此重启后由于redis重新load 使得内存占用小于原先!
解决方案
将redis迁移到单独服务器上!!!将redis迁移到单独服务器上!!!将redis迁移到单独服务器上!!!
网友评论