Redis的基础事务与ABA问题
文章目录
Redis的基础事务
一,Redis的事务命令
命令 | 说明 | 备注 |
---|---|---|
multi | 使用该命令开启事务 | 该命令后的操作指令会进入队列,并不会直接开始执行 |
watch key1 [key2……] | 监听某些键 | 当被监听的键的事务执行前被修改,则事务发生回滚。使用乐观锁 |
unwatch key1 [key2……] | 取消监听某些键 | |
exec | 执行事务命令 | 如果被监听的键没有被修改,就执行事务,否则发生回滚 |
discard | 回滚事务命令 | 回滚进入队列的事务命令,之后就不能再用exec命令提交了 |
二,Redis事务的基本流程
- 1,开启事务。
- 2,命令进入队列。
- 3,执行事务
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
final RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
SessionCallback sessionCallback = new SessionCallback() {
@Override
public Object execute(RedisOperations ops) throws DataAccessException {
// 开启事务
ops.multi();
// 存储set指令,该指令会将进入队列中,queued
ops.boundValueOps("key1").set("value1");
String value = (String) ops.boundValueOps("key1").get();
System.out.println("在没有执行exec命名前是否执行存储操作:" + value);
// 执行事务,此时List会保存之前进入队列的所有命令执行的结果
List list = ops.exec();
value = (String) redisTemplate.opsForValue().get("key1");
return value;
}
};
String value = (String) redisTemplate.execute(sessionCallback);
System.out.println(value);
}
三,Redis不会产生ABA问题
Redis
的事务操作参考了多线程中的CAS(比较与交换,Compare And Swap)
去执行,这是一种乐观锁。
如:某些数据可能被多个业务所共享,当线程T1去执行业务逻辑时,其他共享的线程操作改数据,就可能导致多线程中数据的不一致情况。为了解决这一问题,线程在读取共享数据前,会对共享数据加上监视,保存到线程的副本中,这就是旧值。而在redis中使用了watch指令去实现这一功能,当事务执行时,去对比旧值,如果不一致就回滚。但是这种操作CAS在多线程中可能出现ABA问题
。
时间 | 线程1 | 线程2 | 操作 |
---|---|---|---|
T1 | X=A | - | 线程1加入监控X,此时共享数据X的值为A |
T2 | 执行复杂业务逻辑 | 修改X=B | 线程2修改X为B |
T3 | 执行复杂业务逻辑 | 执行业务逻辑 | 线程二执行本身的简单业务逻辑 |
T4 | 执行复杂业务逻辑 | 修改X=A | 线程2修改X为A |
T5 | 执行复杂业务逻辑 | 线程2退出 | 此时X仍然为A |
T6 | 与旧值进行比较X=A,旧值未改变 | - | CAS原理检查通过,旧值未发生变化 |
当执行到T6的时候,在T2—T5的时刻,线程2对于X的修改可能已经导致了线程1的业务运算发生了错误。但是CAS原理对于旧值的检查显示旧值未发生改变,因此仍然视作正确的。这就是CAS导致的ABA问题。在redis的机制中不会出现ABA的问题
。
自己看到了两个答案,
- Redis是单线程的。CAS出现于多线程中。(自己更认可该答案。)
- 还有一种说法是:Redis执行事务时的watch命令,对比的是数据的版本号,而不是值,通过对比版本来判断值是否发生过变化。