背景
  随着系统使用用户上升,我们也愈发多的使用到了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
  
  那么是什么情况导致我们可以连接到redis而又无法正常的ping该服务呢?
  怀疑如下:
     - 大型数据过期导致redis服务被占 比如某个hash过期
- 持久化失败使得写命令失败
我们知道 大key的过期或者试用类似于keys的指令时十分耗时 由于redis单进程的特性将会阻塞其他指令!
  来查询一下报错日志 在短短时间内迅速报错
  [root@iZ11to3arruZ logs]# grep "Unable to validate object" erp-error.log |wc -l 2357
  如果大key过期的话那么应当会出现一会之后系统正常可以使用!
  然而线上出现的问题是服务无法正常使用
  因此考虑问题2
  我们检查zabbix相关
  
  当redis无法使用的时候恰好是内存最少的时候 当重新启动服务器时系统又可以正常使用!
  那么考虑如下问题,是否是内存不足导致redis出现问题呢?
  带着上述疑问 找到了相关说明!
  查询到如下说明
  stop-writes-on-bgsave-error
  我们系统中使用 是yes 因此当内存不足的时候将无法序列化成文件
  但是我们的内存明明还有接近2G呢!
     Redis在保存数据到硬盘时为了避免主进程假死,需要Fork一份主进程,然后在Fork进程内完成数据保存到硬盘的操作,如果主进程使用了4GB的内存,Fork子进程的时候需要额外的4GB,此时内存就不够了,Fork失败,进而数据保存硬盘也失败了。
  
   
  因此重启后由于redis重新load 使得内存占用小于原先!
   解决方案
  将redis迁移到单独服务器上!!!将redis迁移到单独服务器上!!!将redis迁移到单独服务器上!!!