Spring RedisConnectionFactory,事务未返回与池的连接,然后在耗尽时阻止

我用于使用连接池创建连接工厂的配置。我有一个连接池。这些代码大部分是从Spring的复制而来的,由于特殊原因,我禁用了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)));
    }
}

用例

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

这是 -> -> 和 相同。"A"A::toStringjson(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);
    }

}

期望值

  1. 放置操作仅使用一个连接执行,如果连接丢失或损坏,则放置操作应失败。
  2. 对于 put 操作,连接在多重调用时获取,并在执行调用后返回到池。
  3. 对于 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. 我做错了什么?
  2. 为什么连接未返回到池?
  3. 如何解决此问题,以便将连接返回到池?

答案 1

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

根据文档,您应该将代码包装在SessionCallback中,并使用RedisTemplate.execute(SessionCallback<T>回调)执行它,这将在执行回调后将连接返回到池中。

喜欢这个:

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中实现该方法(即它不能),并且只有在从bean外部执行时才会启动事务(即不是从同一类中的另一个方法或子/父类)。final

您已经在 模板上启用了事务支持,因此您应该可以放心: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);
}

答案 2

推荐