Spring RedisConnectionFactory,事务不返回到池的连接,然后在用尽时阻塞

Posted

技术标签:

【中文标题】Spring RedisConnectionFactory,事务不返回到池的连接,然后在用尽时阻塞【英文标题】:Spring RedisConnectionFactory with transaction not returning connection to Pool and then blocks when exhausted 【发布时间】:2018-02-24 14:25:44 【问题描述】:

我使用连接池创建连接工厂的配置。我确实有一个连接池。大部分代码是从 Spring 的 RedisAutoConfiguration 复制而来的,我出于特殊原因禁用了它。

@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class JedisConfiguration implements RedisConfiguration 

    @Bean
    @Scope("prototype")
    @Override
    public RedisConnectionFactory connectionFactory(RedisProperties redisProperties) 
        return createFactory(redisProperties);
    

    private static JedisConnectionFactory applyProperties(RedisProperties properties, JedisConnectionFactory factory) 
        factory.setHostName(properties.getHost());
        factory.setPort(properties.getPort());
        factory.setDatabase(properties.getDatabase());
        return factory;
    

    private static JedisPoolConfig jedisPoolConfig(RedisProperties properties) 
        return Optional.ofNullable(properties.getPool())
                       .map(props -> 
                           JedisPoolConfig config = new JedisPoolConfig();
                           config.setMaxTotal(props.getMaxActive());
                           config.setMaxIdle(props.getMaxIdle());
                           config.setMinIdle(props.getMinIdle());
                           config.setMaxWaitMillis(props.getMaxWait());
                           return config;
                       )
                       .orElseGet(JedisPoolConfig::new);
    

    public static JedisConnectionFactory createFactory(RedisProperties properties) 
        return applyProperties(properties, new JedisConnectionFactory(jedisPoolConfig(properties)));
    

用例

我有字符串键 "A""B""C" 映射到具有字符串散列键和散列值 json 的散列映射,分别从类 ABC 序列化。

"A" -> A::toString -> json(A)BC 相同。

@Component
public final class UseCase implements InitializingBean 

    private static final String A_KEY = "A";
    private static final String B_KEY = "B";
    private static final String C_KEY = "C";

    private final RedisConnectionFactory factory;
    private final ObjectMapper objectMapper;
    private HashOperations<String, String, A> aMap;
    private HashOperations<String, String, B> bMap;
    private HashOperations<String, String, C> cMap;
    private RedisTemplate<String, ?> template;

    private UseCase(RedisConnectionFactory factory, ObjectMapper objectMapper) 
        this.factory = factory;
        this.objectMapper = objectMapper;
    

    private <T> RedisTemplate<String, ?> hashMap(Class<T> vClass) 
        RedisTemplate<String, ?> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(stringSerializer());
        redisTemplate.setHashKeySerializer(stringSerializer());
        redisTemplate.setHashValueSerializer(jacksonSerializer(vClass));
        return configure(redisTemplate);
    


    private <K, V> RedisTemplate<K, V> configure(RedisTemplate<K, V> redisTemplate) 
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    

    private <T> RedisSerializer<T> jacksonSerializer(Class<T> clazz) 
        Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<>(clazz);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    

    private RedisSerializer<String> stringSerializer() 
        return new StringRedisSerializer();
    

    @Override
    public void afterPropertiesSet() throws Exception 
        template = hashMap(String.class);
        aMap = hashMap(A.class).opsForHash();
        bMap = hashMap(B.class).opsForHash();
        cMap = hashMap(C.class).opsForHash();
    

    void put(A a, B b, C c) 
        template.multi();
        aMap.put(A_KEY, a.toString(), a);
        bMap.put(B_KEY, b.toString(), b);
        cMap.put(C_KEY, c.toString(), c);
        template.exec();
    

    A getA(String aKey) 
        return aMap.get(A_KEY, aKey);
    


期望

    put 操作仅使用一个连接执行,如果连接丢失或损坏,应该会失败。 即put操作,在multi调用时获取连接,exec调用后返回Pool。 即对于getA操作,执行后连接返回池中。

我有测试证明 1 有效,但我对此有点怀疑,但我的问题在于最后两个。经过调试,我观察到在任何操作后连接都没有返回到池,因此池在耗尽时被阻塞。

尝试返回但未在连接上调用,因为下面的两个分支失败。取自RedisConnectionUtils

// release transactional/read-only and non-transactional/non-bound connections.
// transactional connections for read-only transactions get no synchronizer registered
if (isConnectionTransactional(conn, factory)
        && TransactionSynchronizationManager.isCurrentTransactionReadOnly()) 
    unbindConnection(factory);
 else if (!isConnectionTransactional(conn, factory)) 
    if (log.isDebugEnabled()) 
        log.debug("Closing Redis Connection");
    
    conn.close();

问题

    我做错了什么? 为什么连接没有返回到池中? 如何解决此问题,以便将连接返回到池?

【问题讨论】:

【参考方案1】:

我认为问题在于调用 exec() 不会告诉模板您实际上已完成连接,因此无法将其返回到池中。

根据docs,您应该将代码包装在SessionCallback 中并使用RedisTemplate.execute(SessionCallback&lt;T&gt; callback) 执行它,这将在您的回调执行后返回到池的连接。

像这样:

template.execute(new SessionCallback<List<Object>>() 
    public List<Object> execute(RedisOperations operations) throws DataAccessException 
        operations.multi();
        aMap.put(A_KEY, a.toString(), a);
        bMap.put(B_KEY, b.toString(), b);
        cMap.put(C_KEY, c.toString(), c);
        return operations.exec();
    
);

Spring Data Redis 还支持 @Transactional,它会自动为您绑定/取消绑定连接,但需要您在可以拦截的 bean 中实现该方法(即它不能是final) 并且事务只有在从 bean 外部执行时才会启动(即不是从同一类或子/父类中的另一个方法)。

您已经使用 redisTemplate.setEnableTransactionSupport(true); 在模板上启用了事务支持,所以您应该一切顺利:

@Transactional
public void put(A a, B b, C c) 
    aMap.put(A_KEY, a.toString(), a);
    bMap.put(B_KEY, b.toString(), b);
    cMap.put(C_KEY, c.toString(), c);

【讨论】:

感谢您的回答。使用 SessionCallback 执行似乎确实将连接返回到池。您知道为什么只读操作不会将连接返回到池吗? 据我所知,除非您使用execute,否则永远不会调用返回池连接的方法。 非常感谢。我想我可以不必将每个调用都包含在 SessionCallback 中 很高兴能帮上忙!

以上是关于Spring RedisConnectionFactory,事务不返回到池的连接,然后在用尽时阻塞的主要内容,如果未能解决你的问题,请参考以下文章

Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC

学习笔记——Spring简介;Spring搭建步骤;Spring的特性;Spring中getBean三种方式;Spring中的标签

Spring--Spring入门

Spring框架--Spring事务管理和Spring事务传播行为

Spring框架--Spring事务管理和Spring事务传播行为

Spring框架--Spring JDBC