对象池技术在服务器开发上应用广泛。在各种对象池的实现中,尤其以数据库的连接池最为明显,可以说是每个服务器必须实现的部分。
 
apache common pool 官方文档可以参考:https://commons.apache.org/proper/commons-pool/
 
结合JedisPool看Commons Pool对象池技术
 
结合JedisPool,我们来了解一下commons pool的整体设计:
 


 
 
面向用户的往往是ObjectPool,用户看到的是一个对象池,对于使用Redis连接的用户来说,就是JedisPool。对象池ObjectPool提供了借用对象,返还对象,验证对象等API,需要具体的配置GenericObjectPoolConfig来确定池的大小,以及创建具体池化对象的工厂接口PooledObjectFactory来根据需要创建,销毁,激活,钝化每个对象。
 
PooledObjectFactory接口,用来创建池对象(makeObject),将不用的池对象进行钝化(passivateObject),对要使用的池对象进行激活(activateObject),对池对象进行验证(valiateObject),将有问题的池对象销毁(destroyObject)。
 
如果需要使用commons-pool,那么就需要提供一个PooledObjectFactory接口的具体实现,一个比较简单的办法是使用BasePooledObjectFactory这个抽象类,只需要实现两个方法:create()和wrap(T obj)。JedisFactory也就是用来创建每个Jedis连接的对象工厂类,其中直接实现了PooledObjectFactory,makeObject的过程中,直接创建了PooledObject<Redis>。
 
当我们使用JedisPool.getResource(),用于返回jedis连接时,实际调用的是其中GenericObjectPool的borrowObject方法,在Jedis连接池中借用一个对象。
 
借用对象时,先去idleObjects(LinkedBlockingDeque<Pooled<Jedis>>)列表中查看是否有空闲的对象,如果存在则直接使用;如果不存在,则需要考虑在没有超出连接池最大数量的情况下,使用PooledObjectFactory进行初始化,这里使用的是JedisFactory.makeObject来创建连接,并将其激活。
 
 
对于Jedis对象,不能总是重用同一个对象,在使用一段时间后其就会产生失效,连接出现异常。此时就需要使用JedisPool来获取资源,注意在最后要回收资源,实际上就是returnObject,以下面的代码作为实例:
 

  1. Jedis jedis = jedisPool.getResource();
  2. try {
  3. while (true) {
  4. String productCountString = jedis.get("product");
  5. if (Integer.parseInt(productCountString) > 0) {
  6. if (acquireLock(jedis, "abc")) {
  7. int productCount = Integer.parseInt(jedis.get("product"));
  8. System.out.println(String.format("%tT --- Get product: %s", new Date(), productCount));
  9. // System.out.println(productCount);
  10. jedis.decr("product");
  11. releaseLock(jedis, "abc");
  12. return "Success";
  13. }
  14. Thread.sleep(1000L);
  15. } else {
  16. return "Over";
  17. }
  18. }
  19. } finally {
  20. jedis.close();
  21. }
 
 
 
JedisCluster的连接/执行源码研究
 
 
我们使用的JedisCluster(Redis集群模式)需要初始化并使用JedisCluster对象,通过该对象来进行Redis的相关操作,下面就列举出了JedisCluster的基本类图结构:
 


 
 
在执行任务BinaryJedisCluster的相关命令 set/get/exist 等redis命令时,都采用回调的方式:
 

  1. @Override
  2. public String set(final byte[] key, final byte[] value) {
  3. return new JedisClusterCommand<String>(connectionHandler, maxRedirections) {
  4. @Override
  5. public String execute(Jedis connection) {
  6. return connection.set(key, value);
  7. }
  8. }.runBinary(key);
  9. }
 
 
初始化一个JedisClusterCommand对象,执行runBinary方法,进行execute(Jedis connection)回调,其实可以看出执行回调之前的作用是将使用到的Jedis连接在内部统一管理起来。
 
可以猜想使用了JedisSlotBasedConnectionHandler中实现了父类定义的getConnection()获取Redis连接的方法:
 
 
  1. @Override
  2. public Jedis getConnection() {
  3. // In antirez's redis-rb-cluster implementation,
  4. // getRandomConnection always return valid connection (able to
  5. // ping-pong)
  6. // or exception if all connections are invalid
  7.  
  8. List<JedisPool> pools = getShuffledNodesPool();
  9.  
  10. for (JedisPool pool : pools) {
  11. Jedis jedis = null;
  12. try {
  13. jedis = pool.getResource();
  14.  
  15. if (jedis == null) {
  16. continue;
  17. }
  18.  
  19. String result = jedis.ping();
  20.  
  21. if (result.equalsIgnoreCase("pong")) return jedis;
  22.  
  23. pool.returnBrokenResource(jedis);
  24. } catch (JedisConnectionException ex) {
  25. if (jedis != null) {
  26. pool.returnBrokenResource(jedis);
  27. }
  28. }
  29. }
  30.  
  31. throw new JedisConnectionException("no reachable node in cluster");
  32. }
 
 
 
其中调用的方法 getShuffledNodesPool(),就是从JedisClusterInfoCache中包含的所有JedisPool,执行shuffle操作,随机拿到对应的JedisPool,去其中getResource拿到连接。
 
这属于随机去获取connection,但事实上并不是这样处理的,我们可以通过slot来获得其对应的Connection,在JedisClusterCommand.run方法的最后一行中,其中第三个参数为是否为tryRandomMode,调用方式显示为非random Mode。
 
  1. return runWithRetries(SafeEncoder.encode(keys[0]), this.redirections, false, false);
 
可以根据slot来定位到具体的JedisPool,getResource拿到对应的Jedis Connection,但该方法也标明了不能保证一定能够拿到可用的连接。
 
  1. @Override
  2. public Jedis getConnectionFromSlot(int slot) {
  3. JedisPool connectionPool = cache.getSlotPool(slot);
  4. if (connectionPool != null) {
  5. // It can't guaranteed to get valid connection because of node
  6. // assignment
  7. return connectionPool.getResource();
  8. } else {
  9. return getConnection();
  10. }
  11. }
 
 
在JedisClusterInfoCache缓存了Map<String,JedisPool>(host:port->JedisPool)和Map<Integer, JedisPool>(slot->JedisPool),用于查询连接,那么这两个缓存是如何查询出来的,这就需要用到Jedis.clusterNodes,它可以通过该Redis连接找到其他连接的相关配置,例如可以发现整个集群的配置,其中三个master,三个slave,并且能够识别出自身连接,可参考文档:http://redis.io/commands/cluster-nodes
 
 
  1. 5974ed7dd81c112d9a2354a0a985995913b4702c 192.168.1.137:6389 master - 0 1468809898374 26 connected 0-5640
  2. d08dc883ee4fcb90c4bb47992ee03e6474398324 192.168.1.137:6390 master - 0 1468809898875 25 connected 5641-11040
  3. ffb4db4e1ced0f91ea66cd2335f7e4eadc29fd56 192.168.1.138:6390 slave 5974ed7dd81c112d9a2354a0a985995913b4702c 0 1468809899376 26 connected
  4. c69b521a30336caf8bce078047cf9bb5f37363ee 192.168.1.137:6388 master - 0 1468809897873 28 connected 11041-16383
  5. 532e58842d001f8097fadc325bdb5541b788a360 192.168.1.138:6389 slave c69b521a30336caf8bce078047cf9bb5f37363ee 0 1468809899876 28 connected
  6. aa52c7810e499d042e94e0aa4bc28c57a1da74e3 192.168.1.138:6388 myself,slave d08dc883ee4fcb90c4bb47992ee03e6474398324 0 0 19 connected
 
 
分配slot只可能在master节点上发生,而不会在slave节点上发生,这意味着Redis集群并未进行类似读写分离的形式。当Redis集群的slot发生改变时,会重新初始化该Cache,重置slot。
 
而执行每个get/set等Redis操作时,真正的核心入口,其实是JedisClusterCommand.runWithRetries方法:
 
 
  1. private T runWithRetries(byte[] key, int redirections, boolean tryRandomNode, boolean asking) {
  2. if (redirections <= 0) {
  3. throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections?");
  4. }
  5.  
  6. Jedis connection = null;
  7. try {
  8.  
  9. if (asking) {
  10. // TODO: Pipeline asking with the original command to make it
  11. // faster....
  12. connection = askConnection.get();
  13. connection.asking();
  14.  
  15. // if asking success, reset asking flag
  16. asking = false;
  17. } else {
  18. if (tryRandomNode) {
  19. connection = connectionHandler.getConnection();
  20. } else {
  21. connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
  22. }
  23. }
  24.  
  25. return execute(connection);
  26. } catch (JedisConnectionException jce) {
  27. if (tryRandomNode) {
  28. // maybe all connection is down
  29. throw jce;
  30. }
  31.  
  32. // release current connection before recursion
  33. releaseConnection(connection);
  34. connection = null;
  35.  
  36. // retry with random connection
  37. return runWithRetries(key, redirections - 1, true, asking);
  38. } catch (JedisRedirectionException jre) {
  39. // if MOVED redirection occurred,
  40. if (jre instanceof JedisMovedDataException) {
  41. // it rebuilds cluster's slot cache
  42. // recommended by Redis cluster specification
  43. this.connectionHandler.renewSlotCache(connection);
  44. }
  45.  
  46. // release current connection before recursion or renewing
  47. releaseConnection(connection);
  48. connection = null;
  49.  
  50. if (jre instanceof JedisAskDataException) {
  51. asking = true;
  52. askConnection.set(this.connectionHandler.getConnectionFromNode(jre.getTargetNode()));
  53. } else if (jre instanceof JedisMovedDataException) {
  54. } else {
  55. throw new JedisClusterException(jre);
  56. }
  57.  
  58. return runWithRetries(key, redirections - 1, false, asking);
  59. } finally {
  60. releaseConnection(connection);
  61. }
  62. }
 
 
出现的Redis Retries问题
 
可以参考:http://carlosfu.iteye.com/blog/2251034,讲的非常好。同样,我们的出现的异常堆栈:
 
  1. - 2016-06-04 00:02:51,911 [// - - ] ERROR xxx - Too many Cluster redirections?
  2. redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirections?
  3. at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:97)
  4. at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:131)
  5. at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:152)
  6. at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:131)
 
直译过来就是出现过多的redirections异常,出现过JedisConnectionException,完整的堆栈内容:
 
 
  1. redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
  2. at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:198)
  3. at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40)
  4. at redis.clients.jedis.Protocol.process(Protocol.java:141)
  5. at redis.clients.jedis.Protocol.read(Protocol.java:205)
  6. at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:297)
  7. at redis.clients.jedis.Connection.getBinaryBulkReply(Connection.java:216)
  8. at redis.clients.jedis.Connection.getBulkReply(Connection.java:205)
  9. at redis.clients.jedis.Jedis.get(Jedis.java:101)
  10. at redis.clients.jedis.JedisCluster$3.execute(JedisCluster.java:79)
  11. at redis.clients.jedis.JedisCluster$3.execute(JedisCluster.java:76)
  12. at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:119)
  13. at redis.clients.jedis.JedisClusterCommand.run(JedisClusterCommand.java:30)
  14. at redis.clients.jedis.JedisCluster.get(JedisCluster.java:81)
  15. at redis.RedisClusterTest.main(RedisClusterTest.java:30)
 
 
 
调试状态下的异常信息:
 
  1. jce = {redis.clients.jedis.exceptions.JedisConnectionException@1014} "redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream."
  2. detailMessage = "Unexpected end of stream."
  3. cause = {redis.clients.jedis.exceptions.JedisConnectionException@1014} "redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream."
  4. stackTrace = {java.lang.StackTraceElement[0]@1017}
  5. suppressedExceptions = {java.util.Collections$UnmodifiableRandomAccessList@1018} size = 0
 
 
关于这个问题,可以参考:http://blog.csdn.net/jiangguilong2000/article/details/45025355
 
客户端buffer控制。在客户端与server进行的交互中,每个连接都会与一个buffer关联,此buffer用来队列化等待被client接受的响应信息。如果client不能及时的消费响应信息,那么buffer将会被不断积压而给server带来内存压力.如果buffer中积压的数据达到阀值,将会导致连接被关闭,buffer被移除。
 
 开发环境上执行查询该参数的命令:config get client-output-buffer-limit
 

  1. 1) "client-output-buffer-limit"
  2. 2) "normal 0 0 0 slave 268435456 67108864 60 pubsub 33554432 8388608 60"
 
关于Redis上的所有参数详解,可以参考:http://shift-alt-ctrl.iteye.com/blog/1882850
 
JedisMovedDataException
 
  1. jre = {redis.clients.jedis.exceptions.JedisMovedDataException@2008} "redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 8855 192.168.1.137:6390"
  2. targetNode = {redis.clients.jedis.HostAndPort@2015} "192.168.1.137:6390"
  3. slot = 8855
  4. detailMessage = "MOVED 8855 192.168.1.137:6390"
  5. cause = {redis.clients.jedis.exceptions.JedisMovedDataException@2008} "redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 8855 192.168.1.137:6390"
  6. stackTrace = {java.lang.StackTraceElement[0]@1978}
  7. suppressedExceptions = {java.util.Collections$UnmodifiableRandomAccessList@1979} size = 0
 
 
日志中出现超时异常:
 
  1. 4851:S 18 Jul 11:05:38.005 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
可以参考github上关于redis的讨论:https://github.com/antirez/redis/issues/641,关闭AOF,可以暂时解决问题。JedisCluster中应用的Apache Commons Pool对象池技术 
 

JedisCluster中应用的Apache Commons Pool对象池技术的更多相关文章

  1. 对象池化技术 org.apache.commons.pool

    恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率.Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现,可以 ...

  2. Apache Commons Pool 故事一则

    Apache Commons Pool 故事一则 最近工作中遇到一个由于对commons-pool的使用不当而引发的问题,习得正确的使用姿势后,写下这个简单的故事,帮助理解Apache Commons ...

  3. 池化 - Apache Commons Pool

    对于那些创建耗时较长,或者资源占用较多的对象,比如网络连接,线程之类的资源,通常使用池化来管理这些对象,从而达到提高性能的目的.比如数据库连接池(c3p0, dbcp), java的线程池 Execu ...

  4. apache commons pool

    apache commons下的pool 其中的borrowObject函数源代码显示其产生可用对象的过程: 如果stack中有空闲的对象,则pop对象,激活对象(activate函数),验证对象(v ...

  5. Apache Commons Pool 故事一则 专题

    Apache Commons Pool 故事一则 最近工作中遇到一个由于对commons-pool的使用不当而引发的问题,习得正确的使用姿势后,写下这个简单的故事,帮助理解Apache Commons ...

  6. Tomcat 开发web项目报Illegal access: this web application instance has been stopped already. Could not load [org.apache.commons.pool.impl.CursorableLinkedList$Cursor]. 错误

    开发Java web项目,在tomcat运行后报如下错误: Illegal access: this web application instance has been stopped already ...

  7. NoClassDefFoundError: org/apache/commons/pool/impl/GenericObjectPool

    错误:Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/pool/impl ...

  8. Spring + Tomcat 启动报错java.lang.ClassNotFoundException: org.apache.commons.pool.impl.GenericObjectPool

    错误如下: -- ::,-[TS] INFO http-- org.springframework.beans.factory.support.DefaultListableBeanFactory - ...

  9. org/apache/commons/pool/impl/GenericObjectPool异常的解决办法

    org/apache/commons/pool/impl/GenericObjectPool异常的解决办法 webwork+spring+hibernate框架的集成, 一启动Tomcat服务器就出了 ...

随机推荐

  1. 2019.1.11 EDVT

    Processing Gain and Occupied Bandwidth ESA Basic Setup (11b)Span 110MHzRBW 100kHzVBW 100kHzSweep Tim ...

  2. koa 框架 介绍 -- 待续

    对比 express  更小  更健壮 解决繁琐的回调函数嵌套, 并极大地提升错误处理的效率 Koa 的核心设计思路是为中间件层 提供高级语法糖封装, (其实就是用了 ES6的生成器, 能中断函数的执 ...

  3. Linux上安装编译工具链

    在Linux上安装编译工具链,安装它会依赖dpkg-dev,g++,libc6-dev,make等,所以安装之后这些依赖的工具也都会被安装.ubuntu软件库中这么描述 Informational l ...

  4. Linux:SSH服务配置文件详解

    SSH服务配置文件详解 SSH客户端配置文件 /etc/ssh/ssh——config 配置文件概要 Host * #选项“Host”只对能够匹配后面字串的计算机有效.“*”表示所有的计算机. For ...

  5. python3:cmd运行python脚本,提示 No module named 'xxx'

    问题:cmd窗口运行python脚本,报错 C:\Users\xxx\Documents\GitHub\python3\main>python run_test.pyTraceback (mos ...

  6. C++基础之 成员变量初 始化赋值

    摘要: C++成员变量 初始化赋值 你都清楚吗?还有好多坑,好多细节也不知道... 今天在写一个类的时候,由于该类的一个成员变量是引用,所以初始化出现了问题,查了一下之后,才发现,原来引用的初始化和c ...

  7. 51Nod 1058: N的阶乘的长度(斯特林公式)

    1058 N的阶乘的长度  基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 输入N求N的阶乘的10进制表示的长度.例如6! = 720,长度为3. Inp ...

  8. hdu5230

    bc41第三题: 由 1 - n-1 这 n-1 个数组成 l - c 到 r - c 闭区间内的数共有多少种组合方法: 据称本来应该也比较简单吧,xiaoxin说了个五边形数,然后纷纷找了五边形数的 ...

  9. java泛型学习(1)

    java泛型(Generices Type) --->概念:泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和 ...

  10. LG1801 【黑匣子_NOI导刊2010提高(06)】

    看到各路dalao用平衡树的做法,表示本人不才,并不会. 然而我会优先队列_huaji_,并且发现用堆解题的dalao们并没有基于在线的做法 于是我的showtime到了 评测结果:https://w ...