启用事务支持时,spring-data-redis 连接是不是未正确释放?

Posted

技术标签:

【中文标题】启用事务支持时,spring-data-redis 连接是不是未正确释放?【英文标题】:Are spring-data-redis connections not properly released when transaction support is enabled?启用事务支持时,spring-data-redis 连接是否未正确释放? 【发布时间】:2014-09-20 17:23:13 【问题描述】:

在我们的 Spring 4 项目中,我们希望拥有涉及 Redis 和 Hibernate 的数据库事务。每当 Hibernate 失败时,例如由于乐观锁定,Redis 事务也应该中止。

这似乎适用于

    单线程事务执行。 多线程事务执行,只要事务只包含一个 Redis 调用即可。 如果从我们的配置中排除 Hibernate,则使用多个 Redis 调用执行多线程事务。

只要一个事务包含多个 Redis 调用,并且 Hibernate 被配置为参与事务,连接绑定和多线程似乎就会出现问题。线程卡在RedisConnectionUtils.bindConnection(),可能是因为JedisPool 连接不足。

这可以复制如下。

@Service
public class TransactionalService 

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String, Object> redisTemplate;

    @Transactional
    public void processTask(int i)

        redisTemplate.convertAndSend("testChannel", new Message());
        redisTemplate.convertAndSend("testChannel", new Message());
    

我们使用核心池大小为 50 的 ThreadPoolTaskExecutor 来模拟多线程事务。

@Service
public class TaskRunnerService 

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private TransactionalService transactionalService;

    public void runTasks() 

        for (int i = 0; i < 100; i++) 

            final int j = i;

            taskExecutor.execute(new Runnable() 

                @Override
                public void run() 
                    transactionalService.processTask(j);
                
            );
        
    

运行这个会导致所有 taskExecutor 线程挂在 JedisPool.getResource() 中:

  "taskExecutor-1" - Thread t@18
   java.lang.Thread.State: WAITING
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for <1b83c92c> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:524)
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:438)
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:361)
    at redis.clients.util.Pool.getResource(Pool.java:40)
    at redis.clients.jedis.JedisPool.getResource(JedisPool.java:84)
    at redis.clients.jedis.JedisPool.getResource(JedisPool.java:10)
    at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:90)
    at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:143)
    at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:41)
    at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
    at org.springframework.data.redis.core.RedisConnectionUtils.bindConnection(RedisConnectionUtils.java:66)
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:175)
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152)
    at org.springframework.data.redis.core.RedisTemplate.convertAndSend(RedisTemplate.java:675)
    at test.TransactionalService.processTask(TransactionalService.java:23)
    at test.TransactionalService$$FastClassBySpringCGLIB$$9b3de279.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:708)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
  at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
  at test.TransactionalService$$EnhancerBySpringCGLIB$$a1b3ba03.processTask(<generated>)
  at test.TaskRunnerService$1.run(TaskRunnerService.java:28)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - locked <7d528cf7> (a java.util.concurrent.ThreadPoolExecutor$Worker)   

Redis 配置

@Configuration
public class RedisConfig 

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() 
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setPoolConfig(new JedisPoolConfig());
        return jedisConnectionFactory;
    

    @Bean
    public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() 
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new     Jackson2JsonRedisSerializer(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper());
        return jackson2JsonRedisSerializer;
    

    @Bean
    public StringRedisSerializer stringRedisSerializer() 
        return new StringRedisSerializer();
    

    @Bean
    public RedisTemplate<String, Object> redisTemplate() 
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        redisTemplate.setKeySerializer(stringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
        redisTemplate.setEnableTransactionSupport(true);
        return redisTemplate;
    

    @Bean
    public ObjectMapper objectMapper() 
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        return objectMapper;
    

休眠配置

@EnableTransactionManagement
@Configuration
public class HibernateConfig 

    @Bean
    public LocalContainerEntityManagerFactoryBean admin() 

        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new     LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        entityManagerFactoryBean.setPersistenceUnitName("test");

        return entityManagerFactoryBean;
    

    @Bean
    public JpaTransactionManager transactionManager(
            @Qualifier("admin") LocalContainerEntityManagerFactoryBean     entityManagerFactoryBean) 

        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
        transactionManager.setDataSource(entityManagerFactoryBean.getDataSource());

        return transactionManager;
    

这是 spring-data-redis 的 bug 还是我们的配置有问题?

【问题讨论】:

你有没有弄清楚是什么导致了这种行为。目前我在仅 redis 的环境中面临同样的问题。 【参考方案1】:

在我使用 opsForHAsh 并放置许多键遇到完全相同的问题之前,我发现了您的问题(巧合)。线程转储证实了这一点。

我发现帮助我前进的是增加 JedisPoolConfig 中的线程池。我将其设置如下,设置为 128,这让我再次上路。

@Bean
JedisPoolConfig jedisPoolConfig() 
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    jedisPoolConfig.setMaxTotal(128);
    return jedisPoolConfig;

我认为在我的情况下池太小,并且所有线程都在用于我的事务,因此无限期地等待。将总数设置为 128 让我可以继续。尝试将您的配置设置为对您的应用有意义的 maxTotal。

【讨论】:

【参考方案2】:

我遇到了一个非常相似的问题,但是如果线程真的没有被释放,那么碰到 maxTotal 线程会让我感到困扰。相反,我有一些代码可以快速执行一次获取,然后执行一次设置。我把它放在一个 SessionCallback 中,它的表现要好得多。希望对您有所帮助。

【讨论】:

以上是关于启用事务支持时,spring-data-redis 连接是不是未正确释放?的主要内容,如果未能解决你的问题,请参考以下文章

spring-data-redis读写分离

用spring-data-redis实现类似twitter的网站(转)

spring-data-redis 使用过程中踩过的坑

redis集群 与spring-data-redis 集成

Spring Data JPA事务支持

spring-data-redis时效设置