Spring RedisConnectionFactory,事务没有返回到Pool的连接,然后在耗尽时阻塞

时间:2017-09-15 11:45:37

标签: java spring spring-boot redis spring-data-redis

我使用连接池创建连接工厂的配置。我有一个连接池。大多数代码都是从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"映射到带有字符串哈希键的哈希映射,以及从类A序列化的哈希值json,{{1}分别和B

那是C - > "A" - > A::toStringjson(A)以及B相同。

C

期望

  1. 只使用一个连接执行put操作,如果连接丢失或损坏,则应该失败。
  2. 对于put操作,连接是在多次调用时获得的,并在exec调用后返回到Pool。
  3. 对于getA操作,执行后连接将返回到池中。
  4. 我有测试证明1有效,但我对此有点怀疑但我的问题是最后两个。在调试之后,我观察到在任一操作之后连接都没有返回到池,因此当池耗尽时池被阻塞。

    尝试返回但未在连接上调用,因为下面的两个分支失败。取自@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); } }

    RedisConnectionUtils

    问题

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

1 个答案:

答案 0 :(得分:3)

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

根据docs,您应该将代码包装在SessionCallback中并使用RedisTemplate.execute(SessionCallback<T> 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中实现该方法(即它可以&#39) ; t是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);
}