• Sping Data Redis 使用事务时,不关闭连接的问题


    项目中使用到了Redis,框架为springMVC+tomcat+Redis+Mysql

    最后决定用spring-data-redis来开发,配置好连接池,进入使用,似乎一切正常。

     配置了两块redis,一个专门做读,一个专门做些, 配置的XML文件如下,这是一个专做写的redis配置:

    [html] view plain copy
    1. <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
    2.         <property name="maxIdle" value="${redis.maxIdle}" />  
    3.         <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />  
    4.         <property name="maxTotal" value="${redis.maxTotal}"></property>  
    5.         <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
    6.         <property name="minIdle" value="${redis.minIdle}"></property>  
    7.         <property name="timeBetweenEvictionRunsMillis" value="60000"></property>  
    8.         <property name="minEvictableIdleTimeMillis" value="1800000"></property>  
    9.         <property name="numTestsPerEvictionRun" value="3"></property>  
    10.     </bean>  
    11.     <bean id="connectionFactory"  
    12.         class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"  
    13.         p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}"  
    14.         p:pool-config-ref="poolConfig">  
    15.     </bean>  
    16.   
    17.     <!-- 配置redisTemplate -->  
    18.     <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">  
    19.         <property name="connectionFactory" ref="connectionFactory" />  
    20.         <property name="keySerializer">  
    21.             <bean  
    22.                 class="org.springframework.data.redis.serializer.StringRedisSerializer" />  
    23.         </property>  
    24.         <!-- 采用json序列化 -->  
    25.         <property name="valueSerializer">  
    26.             <bean  
    27.                 class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer">  
    28.             </bean>  
    29.         </property>  
    30.         <!-- 开启REIDS事务支持 -->  
    31.         <property name="enableTransactionSupport" value="true" />   
    32.     </bean>  

    配置好以后编码使用,发现一切正常。但是有一次一个现象引起了注意,同事在编码的时候,出现了“无法从连接池获取redis连接“的错误(Could not get a resource conneciton from the pool)。
    于是调用端口查看,发现连接池被占满了,redisTemplate似乎没有释放掉连接,于是进一步查看,发现只有启用了事务的连接没有被释放,记得以前听人说过 exec和discard会自动关闭连接,于是往配置上去寻找问题,多番寻找和尝试无果,后来又有人说据说用@transactional或spring的事务AOP可以控制和传播事务,并控制连接,但是我们的需求是自己做,所以只好去看redisTemplate源码。从exec处入手,很快找到了最终执行的源码,RedisTemplate的所有操作执行,大部分都是通过这个方法来回调action执行的,大家可以感受感受:
    [java] view plain copy
    1. public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {  
    2.     Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");  
    3.     Assert.notNull(action, "Callback object must not be null");  
    4.   
    5.     RedisConnectionFactory factory = getConnectionFactory();  
    6.     RedisConnection conn = null;  
    7.     try {  
    8.   
    9.         if (enableTransactionSupport) {  
    10.             // only bind resources in case of potential transaction synchronization  
    11.             conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);  
    12.         } else {  
    13.             conn = RedisConnectionUtils.getConnection(factory);  
    14.         }  
    15.   
    16.         boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);  
    17.   
    18.         RedisConnection connToUse = preProcessConnection(conn, existingConnection);  
    19.   
    20.         boolean pipelineStatus = connToUse.isPipelined();  
    21.         if (pipeline && !pipelineStatus) {  
    22.             connToUse.openPipeline();  
    23.         }  
    24.   
    25.         RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));  
    26.         T result = action.doInRedis(connToExpose);  
    27.   
    28.         // close pipeline  
    29.         if (pipeline && !pipelineStatus) {  
    30.             connToUse.closePipeline();  
    31.         }  
    32.   
    33.         // TODO: any other connection processing?  
    34.         return postProcessResult(result, connToUse, existingConnection);  
    35.     } finally {  
    36. lt;span style="white-space:pre">         </span>//这里是控制是否释放或归还连接的代码  
    37.         if (!enableTransactionSupport) {  
    38.             RedisConnectionUtils.releaseConnection(conn, factory);  
    39.         }  
    40.     }  
    41. }  
    关键代码是这一句,RedisConnectionUtils.releaseConnection里是归还/释放连接的代码,很显然,只要你设置了enableTransactionSupport为true,或用mulit开启了事务,调用redisTemplate的回调函数时是永远不会归还/释放连接。
    也不要尝试设置enableTransactionSupport为false去释放连接,因为他只会获取一个新的连接然后关闭,代码是这句:
    [java] view plain copy
    1. if (enableTransactionSupport) {  
    2.                 conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);  
    3.             } else {//这里会获取一个新的连接  
    [java] view plain copy
    1. conn = RedisConnectionUtils.getConnection(factory);  

    但知道连接是怎么处理的就好办了,我们手动释放即可,好在redisTemplate提供了获取getFactory的方法,但是还需要获取到当前线程的connection,这个不难,追踪RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);可以发现,这句话就是绑定/返回当前线程的connection的,如果当前线程还不存在connection,则创建一个。于是以下代码就可以做到返回连接给连接池。
    [java] view plain copy
    1. redisTemplate.exec();     
    2. // 获取当前线程绑定的连接  
    3. RedisConnection conn = RedisConnectionUtils.bindConnection(redisTemplate.getConnectionFactory(), true);  
    4. //返还给连接池  
    5. conn.close();  
    但是很快又发现另外一个问题,尝试大量访问的时候会报出这个异常:

    Could not release connection to pool 

    很明显是无法将连接返还给连接池,一番追踪后发现,第一次是永远不会报这个错的,只在访问达到一定次数的情况下才会比较频繁的出现,这个不难理解,应该与会话缓存。显然是我们归还过一次连接池,第二次归还时,连接池抛出了错误,也就是说,被返还的连接还绑定在会话缓存上,于是去看底层是如何bindConnection的,跟踪到这段代码。
    [java] view plain copy
    1. public static RedisConnection doGetConnection(RedisConnectionFactory factory, boolean allowCreate, boolean bind,  
    2.             boolean enableTransactionSupport) {  
    3.   
    4.         Assert.notNull(factory, "No RedisConnectionFactory specified");  
    5.   
    6.         RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);  
    7.   
    8.         if (connHolder != null) {  
    9.             if (enableTransactionSupport) {  
    10.                 potentiallyRegisterTransactionSynchronisation(connHolder, factory);  
    11.             }  
    12.             return connHolder.getConnection();  
    13.         }  
    14.   
    15.         if (!allowCreate) {  
    16.             throw new IllegalArgumentException("No connection found and allowCreate = false");  
    17.         }  
    18.   
    19.         if (log.isDebugEnabled()) {  
    20.             log.debug("Opening RedisConnection");  
    21.         }  
    22.   
    23.         RedisConnection conn = factory.getConnection();  
    24.   
    25.         if (bind) {  
    26.   
    27.             RedisConnection connectionToBind = conn;  
    28.             if (enableTransactionSupport && isActualNonReadonlyTransactionActive()) {  
    29.                 connectionToBind = createConnectionProxy(conn, factory);  
    30.             }  
    31.   
    32.             connHolder = new RedisConnectionHolder(connectionToBind);  
    33. //这句就是执行了将连接绑定到线程的代码  
    34.             TransactionSynchronizationManager.bindResource(factory, connHolder);  
    35.             if (enableTransactionSupport) {  
    36.                 potentiallyRegisterTransactionSynchronisation(connHolder, factory);  
    37.             }  
    38.   
    39.             return connHolder.getConnection();  
    40.         }  
    41.   
    42.         return conn;  
    43.     }  

    注意看我的注释。内部是通过TransactionSynchronizationManager.bindResource的,这下就好处理了,TransactionSynchronizationManager里还提供了一个unbindResource的方法,调用方法是这样的:
    TransactionSynchronizationManager.unbindResource(redisTemplate.getConnectionFactory());
    于是修改代码为以下:
    [java] view plain copy
    1. redisTemplate.exec();     
    2. // 获取当前线程绑定的连接  
    3. RedisConnection conn = RedisConnectionUtils.bindConnection(redisTemplate.getConnectionFactory(), true);  
    4. //返还给连接池  
    5. conn.close();  
    [java] view plain copy
    1. //解绑线程连接  
    [java] view plain copy
    1. TransactionSynchronizationManager.unbindResource(redisTemplate.getConnectionFactory());  
    [java] view plain copy
    1. </pre><pre>  
    再次测试,没问题,再没有任何错误,连接数与响应速度也非常快。最后
    修改代码,通过一个AOP来进行处理。


    2016-8-10更新。
    在重新看代码的时候,发现了一个更简单的处理。
    在这个方法里已经做了上面的事情,调用这个解绑的方式,更简洁。
    修改代码
    redisTemplate.exec();
    RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
    两句就可以了。



    2017-8-16更新

    收到了很多朋友的私信,都在问我关于redis连接的问题,发现有不少朋友实际上是不存在这个问题的,请不用担心。

    所以我在文章最后再说明一下,以免造成误解:
    如果采用spring的事务管理的话,spring的事务管理会处理好链接的问题。
    我们因为没有使用spring的事务管理,所以才需要自己编码去解决.

  • 相关阅读:
    公约数
    [TJOI2017]异或和
    [HNOI2014]世界树
    小清新数据结构题
    CF809E 【Surprise me!】
    [JXOI2017]颜色
    docker swarm集群
    jenkins构建maven项目
    jenkins软件工具部署
    jenkins持续集成
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13317517.html
Copyright © 2020-2023  润新知