Redis Transactions

Redis 通过 multiexecdiscard 命令提供对 transactions 的支持。这些操作在 RedisTemplate 中可用。但是,RedisTemplate 不能保证使用相同的连接运行事务中的所有操作。

Redis provides support for transactions through the multi, exec, and discard commands. These operations are available on RedisTemplate. However, RedisTemplate is not guaranteed to run all the operations in the transaction with the same connection.

Spring Data Redis 提供 SessionCallback 接口,可在需要使用相同的 connection 执行多个操作时使用,例如在使用 Redis 事务时。以下示例使用 multi 方法:

Spring Data Redis provides the SessionCallback interface for use when multiple operations need to be performed with the same connection, such as when using Redis transactions.The following example uses the multi method:

//execute a transaction
List<Object> txResults = redisOperations.execute(new SessionCallback<List<Object>>() {
  public List<Object> execute(RedisOperations operations) throws DataAccessException {
    operations.multi();
    operations.opsForSet().add("key", "value1");

    // This will contain the results of all operations in the transaction
    return operations.exec();
  }
});
System.out.println("Number of items added to set: " + txResults.get(0));

在返回之前,RedisTemplate 使用其值、哈希键和哈希值序列化器反序列化 exec 的所有结果。还有一个其他 exec 方法,允许你为事务结果传递自定义序列化器。

RedisTemplate uses its value, hash key, and hash value serializers to deserialize all results of exec before returning. There is an additional exec method that lets you pass a custom serializer for transaction results.

值得一提的是,如果在 multi()exec() 之间发生异常(例如在 Redis 未在超时内做出响应时发生超时异常),则连接可能会陷入事务状态。为了防止这种情况,需要丢弃事务状态以清除连接:

It is worth mentioning that in case between multi() and exec() an exception happens (e.g. a timeout exception in case Redis does not respond within the timeout) then the connection may get stuck in a transactional state. To prevent such a situation need have to discard the transactional state to clear the connection:

List<Object> txResults = redisOperations.execute(new SessionCallback<List<Object>>() {
  public List<Object> execute(RedisOperations operations) throws DataAccessException {
    boolean transactionStateIsActive = true;
	try {
      operations.multi();
      operations.opsForSet().add("key", "value1");

      // This will contain the results of all operations in the transaction
      return operations.exec();
    } catch (RuntimeException e) {
	    operations.discard();
		throw e;
    }
  }
});

@Transactional Support

默认情况下,RedisTemplate 不参与托管的 Spring 事务。如果你希望 RedisTemplate 在使用 @TransactionalTransactionTemplate 时使用 Redis 事务,则需要通过设置 setEnableTransactionSupport(true) 明确地启用每个 RedisTemplate 的事务支持。启用事务支持会将 RedisConnection 绑定到由 ThreadLocal 支持的当前事务。如果事务顺利完成,Redis 事务将使用 EXEC 提交,否则将使用 DISCARD 回滚。Redis 事务是面向批处理的。在进行事务期间发出的命令将被排队,仅在提交事务时应用。

By default, RedisTemplate does not participate in managed Spring transactions. If you want RedisTemplate to make use of Redis transaction when using @Transactional or TransactionTemplate, you need to be explicitly enable transaction support for each RedisTemplate by setting setEnableTransactionSupport(true). Enabling transaction support binds RedisConnection to the current transaction backed by a ThreadLocal. If the transaction finishes without errors, the Redis transaction gets commited with EXEC, otherwise rolled back with DISCARD. Redis transactions are batch-oriented. Commands issued during an ongoing transaction are queued and only applied when committing the transaction.

Spring Data Redis 区分正在进行的事务中的只读和写命令。只读命令(如 KEYS)被传送到新的(非线程绑定的)RedisConnection 以允许读取。写命令由 RedisTemplate 排队并应用于提交。

Spring Data Redis distinguishes between read-only and write commands in an ongoing transaction. Read-only commands, such as KEYS, are piped to a fresh (non-thread-bound) RedisConnection to allow reads. Write commands are queued by RedisTemplate and applied upon commit.

以下示例展示如何配置事务管理:

The following example shows how to configure transaction management:

Example 1. Configuration enabling Transaction Management
@Configuration
@EnableTransactionManagement                                 1
public class RedisTxContextConfiguration {

  @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);              2
    return template;
  }

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    // jedis || Lettuce
  }

  @Bean
  public PlatformTransactionManager transactionManager() throws SQLException {
    return new DataSourceTransactionManager(dataSource());   3
  }

  @Bean
  public DataSource dataSource() throws SQLException {
    // ...
  }
}
1 Configures a Spring Context to enable {spring-framework-docs}/data-access.html#transaction-declarative[declarative transaction management].
2 Configures RedisTemplate to participate in transactions by binding connections to the current thread.
3 Transaction management requires a PlatformTransactionManager. Spring Data Redis does not ship with a PlatformTransactionManager implementation. Assuming your application uses JDBC, Spring Data Redis can participate in transactions by using existing transaction managers.

以下每个示例都演示了一个使用限制:

The following examples each demonstrate a usage constraint:

Example 2. Usage Constraints
// must be performed on thread-bound connection
template.opsForValue().set("thing1", "thing2");

// read operation must be run on a free (not transaction-aware) connection
template.keys("*");

// returns null as values set within a transaction are not visible
template.opsForValue().get("thing1");