Redis的事务及Spring Data Redis的实现
Redis的事务及Spring Data Redis的实现
Redis的事务
简介
Redis的事务基于MULTI,EXEC,DISCARD和WATCH这四个命令实现。这四个命令组合允许在一步中同时执行多个命令。
Redis的事务主要保证了一下两个方面:
- 事务中的多个命令是按照添加到队列中的顺序执行的。并且在事务进行中,不会被另一个客户端的请求打断。也就是说,事务中的多个命令可以看作单个隔离的操作。
- 事务可以保证原子性。事务的执行由EXEC命令触发,当客户端在发送EXEC命令前丢失了该事务的连接,该事务中的命令将不会被执行。如果在客户端在调用EXEC命令后丢失连接,事务的命令队列依然会被执行。Redis事务会写入磁盘文件。若Redis在事务中异常停止,在重启时会报错,可以使用redis-check-aof工具修复后再重新启动。
- 从2.2版本开始,可以使用乐观锁对上述两种情况添加额外保证。类似与CAS操作。
事务的使用
- 通过MULTI进入一个事务。这个命令总是返回OK。
- 客户端发起多个命令。Redis将这些命令依次放入队列,并不会执行。所有命令都会返回字符串QUEUED回复。
- 当客户端执行EXEC命令后,开始执行事务。EXEC命令会返回多个结果的数组,每个元素对应事务中的一个命令,且顺序与命令顺序一致。
- 当客户端调用DISCARD命令后,清空事务队列并退出当前事务。
事务对于错误的处理
事务中可能会碰到的错误如下:
- 命令排队失败。例如:命令的语法错误或者该命令可能导致一些危险,例如当设置Redis可以使用的最大内存时可能碰到内存不足的情况。
- 调用EXEC后命令可能会执行失败。例如,因为我们对一个具有错误值的键执行了一个操作(比如对一个字符串值调用一个列表操作)。
对于第一种错误,客户端应该判断返回值是否返回字符串QUEUED来处理,例如中止并丢弃该事务。
从Redis 2.6.5开始,Redis服务器会记住在命令排队时的错误,并且拒绝执行事务,在EXEC期间返回错误,并自动丢弃事务。
在2.6.5之前,如果客户端不处理错误,继续执行EXEC,服务器将会仅执行命令队列中的正确命令。
对于第二种错误,Redis服务器不会做特殊处理,所有的命令都将会执行,即使某些命令执行错误。对于出错命令返回-ERR。
重要:在EXEC之后,事务开始执行队列中所有命令,即使碰到错误也不会中止!只会对发生执行错误的命令返回-ERR
重要:Redis事务不支持回滚!
check-and-set 乐观锁
乐观锁通过WATCH命令实现。WATCH可以用来监视一个key的变化。如果在EXEC执行前,至少一个被监视的key被修改,事务将会中止,EXEC命令返回Null来通知事务失败。
当返回事务失败时可以重复操作,直到成功。这种锁即为乐观锁。
Redis脚本和事务
Redis脚本是事务性的,因此可以使用脚本执行操作,通常脚本将更简单,更快速。
Spring Data Redis(2.1)对Redis事务的支持
RedisTemplate提供了对multi、exec和discard命令的支持。但是,RedisTemplat不会在一个连接上执行多个redis命令。Spring Data Redis提供了SessionCallback接口,支持一个连接执行多个redis命令。
因此,Spring Data Redis支持redis的事务需要依赖SessionCallback接口。
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 operations in the transaction
return operations.exec();
}
});
System.out.println("Number of items added to set: " + txResults.get(0));
使用@Transactional
默认情况下,事务支持是禁用的,必须通过设置setEnableTransactionSupport(true)为正在使用的每个RedisTemplate显式启用。这样做会强制将当前RedisConnection绑定到触发MULTI的当前线程。如果事务顺利完成,则调用EXEC。否则调用DISCARD。进入MULTI后,RedisConnection会将写操作排队。所有只读操作(例如KEYS)都传递到新的(非线程绑定)RedisConnection。
以下示例显示如何配置事务管理:
@Configuration
@EnableTransactionManagement
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// 启用事务
template.setEnableTransactionSupport(true);
return template;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// jedis || Lettuce
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public DataSource dataSource() throws SQLException {
// ...
}
}
配置说明:
- @EnableTransactionManagement: 注解开启了声明式事务管理
- template.setEnableTransactionSupport(true): 明确开启redis事务
- return new DataSourceTransactionManager(dataSource()): 事务管理需要一个PlatformTransactionManager。Spring Data Redis不包含PlatformTransactionManager实现。假设应用程序中使用了JDBC,Spring Data Redis可以使用现有的事务管理器参与事务。
需要注意的是,通过这种方式使用事务,所有的只读操作会在另一个不与线程绑定的连接中完成。并且这种方式使用的是复合事务。