转载

Redis之坑:spring-data-redis中的Redis事务

Redis 通过 multi , exec , 或 discard 命令来提供事务支持,这些操作在 RedisTemplate 中同样是可用的。但是, RedisTemplate 默认 使用 RedisCallBack 接口,并不能保证使用同一连接来执行同一事务中的所有操作(此时 Transaction 是无效的)。

又但是, Spring Data Redis 提供了 SessionCallback 接口,以便在需要保证 同一连接 执行多个操作时使用,比如“需要使用 Redis事务 时”。 我们能够看到:

public <T> T execute(SessionCallback<T> session) {
		Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
		Assert.notNull(session, "Callback object must not be null");

		RedisConnectionFactory factory = getConnectionFactory();
		// bind connection
		RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);//第8行
		try {
			return session.execute(this);
		} finally {
			RedisConnectionUtils.unbindConnection(factory);
		}
	}
复制代码
  • RedisTemplate.execute(SessionCallback<T> session) 方法的 第8行 已经做了 连接绑定

使用方式如下:

//execute a transaction
List<Object> txResults = redisTemplate.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 ops in the transaction
    return operations.exec();
  }
});
System.out.println("Number of items added to set: " + txResults.get(0));
复制代码

在返回之前,RedisTemplate将使用它的value, hash key和hash value 序列化器来反序列化exec的所有结果。 另外一个额外的exec方法,允许您为事务结果传递自定义序列化器。

@Transactional支持

上面我们能够看到,可以通过 SessionCallback 绑定连接 ,并且实现 multi , exec ,或 discard ,从而支持 Redis事务 ,但是这样就显得很复杂而且 Redis操作(opsXXX.X) 执行的位置也变得有局限性(尽管不影响功能)。  然而, Spring 下我们可以更加简单,只需两步:

  • method 添加注解**@Transactional 或者 Xml配置**(< tx:method />),注册 事务切点 。相当于调用了 TransactionSynchronizationManager.setActualTransactionActive(true);
  • 通过 setEnableTransactionSupport(true) 显式启用 RedisTemplate 实例的 事务支持默认被禁用
/** Sample Configuration **/
@Configuration
public class RedisTxContextConfiguration {
  @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);
    return template;
  }
}
复制代码

redisTemplate实例默认调用 execute(RedisCallback action) ,方法内容如下:

public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline){
		/**
		 * 变量声明等操作……
		 */
		try {
			if (enableTransactionSupport) {
				// only bind resources in case of potential transaction synchronization
				conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
			} else {
				conn = RedisConnectionUtils.getConnection(factory);
			}
		/**
		 * 其他操作……
		 */
}

public static RedisConnection bindConnection(RedisConnectionFactory factory, 
			boolean enableTransactionSupport) {
		/**
		 * 不用管……
		 */
		RedisConnection conn = factory.getConnection();
		RedisConnection connectionToBind = conn;
		//redisTemplate开启事务支持,同时transactionManager非只读的实际事务被激活
		if (enableTransactionSupport && isActualNonReadonlyTransactionActive()) {
			connectionToBind = createConnectionProxy(conn, factory);
		}
		/**
		 * 不用管……
		 */
		return conn;
}
复制代码

可以看到, enableTransactionSupport = true 将会促使当前 Thread 尝试绑定 RedisConnection ,仅当也 isActualNonReadonlyTransactionActive = true ,连接才会成功绑定。

连接绑定成功,同时将会触发 MULTI 。一旦 MULTI 被调用:

  • 当前 RedisConnection 将会排队 write操作
  • 所有 readonly操作 ,例如 KEYS 将会被分发给一个全新的 (非 Thread 绑定)的 RedisConnection
  • 命令 EXECDISCARD 将交由 SpringAOP动态代理对象 去调用:
  • 如果 事务构建 过程中没有 异常抛出 (默认 RuntimeException 及其子类),则 EXEC 被调用,执行命令队列;
  • 否则 DISCARD ,清除命令队列。

开启事务支持后:

/** Usage Constrainsts **/
// executed on thread bound connection
template.opsForValue().set("foo", "bar");

// read operation executed on a free (not tx-aware)
connection template.keys("*");

// returns null as values set within transaction are not visible
template.opsForValue().get("foo");
复制代码

上面的样例代码是 Spring官网 给出的, 第三个 显然是 WATCH 命令开启 乐观锁 后的结果。然而至少在本人正在使用的 spring-data-redis-1.8.10.RELEASE.jar 中,

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>1.8.10.RELEASE</version>
</dependency>
复制代码

WATCH 命令并没有被使用, 亲测 第三种 效果并不存在(你可以根据自己的依赖版本尝试一下),此处亮出代码。

  • org.springframework.data.redis.core.RedisConnectionUtils.potentiallyRegisterTransactionSynchronisation
private static void potentiallyRegisterTransactionSynchronisation(RedisConnectionHolder connHolder,
			final RedisConnectionFactory factory) {

		if (isActualNonReadonlyTransactionActive()) {

			if (!connHolder.isTransactionSyncronisationActive()) {
				connHolder.setTransactionSyncronisationActive(true);

				RedisConnection conn = connHolder.getConnection();
				conn.multi();//在此之前conn.watch()未被调用

				TransactionSynchronizationManager.registerSynchronization(new RedisTransactionSynchronizer(connHolder, conn,
						factory));
			}
		}
	}
复制代码

声明 两个 RedisTemplate实例

两个 RedisTemplate 实例?

  • 支持 事务: commands 要么 统一执行 ,要么 都被清除 ,维护数据完整性;
  • 不支持 事务, command 立即执行,即时返回 执行结果 并且更 高效
/** Sample Configuration **/
@Configuration
public class RedisTxContextConfiguration {
  @Bean
  public StringRedisTemplate redisTransactionTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);
    return template;
  }
 @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    return template;
  }
}
复制代码
原文  https://juejin.im/post/5da961de518825200b2d578d
正文到此结束
Loading...