Подтвердить что ты не робот

Spring RedisConnectionFactory с транзакцией, не возвращающей соединение с пулом, а затем блокируется при исчерпании

Моя конфигурация для создания соединения factory с пулом соединений. У меня есть пул соединений. Большая часть этого кода копируется из 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, сериализованными из классов A, B и C соответственно.

Это "A"A::toStringjson(A) и то же самое для B и C.

@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, соединение получается при многократном вызове и возвращается в пул после вызова exec.
  • Что для операции 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();
}

Вопросы

  • Что я делаю неправильно?
  • Почему соединение не возвращается в пул?
  • Как я могу исправить это, чтобы соединение было возвращено в пул?
4b9b3361

Ответ 1

Я думаю, что проблема заключается в том, что вызов 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, который может быть перехвачен (т.е. он не может быть 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);
}