redis 系列之——事物及乐观锁
Redis系列目录
redis系列之——数据类型geospatial:你隔壁有没有老王?
redis系列之——数据类型bitmaps:今天你签到了吗?
学习mysql的时候,我们常说mysql是有事物的,事物有ACID四个特性,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
redis有事物吗?是怎样的呢?下面就使用实际测试的情况,告诉大家结果。
事物 (multi / exec /discard)
在redis中,是有事物的。但是redis的事物是弱事物。事物没有隔离级别,事物中的多条命令也不是原子性的。正是这些原因,在实际的生产中,也很少用到redis的事物。
redis事物本质:一组命令的集合。事物中的所有命令都会被序列化,存放到队列中,事物执行过程中,命令按顺序往下执行。
redis单条命令保证原子性,多条命令不保证原子性。
1.正常事物执行
redis的事物使用有三步:
开启事物 (multi)
命令入队 (需要执行的命令写入队列,先进先出,队列中是一组命令。)
执行事物 (exec)
正常事物展示:
上面的事物提交后,会按顺序依次执行四个命令,执行完成后退出事物。
2.取消事物
事物开启后,也可以取消事物(discard):
3.事物报错
编译错误
编译时报错,是因为队列中的命令本身有问题,导致在命令入队的时候就报错;有编译错误的时候,执行exec会提示失败,所有的命令都不能执行。
运行错误
运行时错误,是入栈的命令本身没有错误,但是在出队执行的时候报错,比如下面对String做自增操作。
这里可以看出,运行时报错了,但是事物不会回滚,而且,出错后不会影响后续的命令执行,只会有出错的那一条命令执行失败。所以,对于队列中的命令,是不存在原子性的。
乐观锁 (watch)
1.乐观锁和悲观锁
悲观锁
认为出现并发问题的可能性比较大,比较悲观。这时需要真正的加锁处理。加锁会降低性能。
乐观锁
认为出现并发问题的可能性比较小,比较乐观。这时不需要加锁,只需要在执行修改操作的时候,比较一下原来的数据是否发生变化,如果没有变化就修改,有变化就不修改。在mysl中通常是使用version字段处理。
redis提供了watch
命令,可以监控修改数据时,数据是否被其他线程修改过,如果修改过,则本次修改失败,如果没有修改过,则修改成功。其实watch命令就可以看做是redis的乐观锁的实现。
2.转账模拟
下面,模拟的场景是两个账户转账的业务。
单线程模拟
正常转账过程:
上面单线程模拟转账后,付钱账户付完钱后还有900,收钱账户现在有100。这是正常过程。
并发模拟
在这个过程中,如果在执行exec
前,有人想acc1中充了1000元,这个时候就会出现并发问题,如果这时不使用锁,执行完成exec
后,结果会怎样呢?结果会是acc1有1900,acc1有100,这个结果也是正确的。为啥?因为redis的事物没有隔离性,两个事物会相互影响。
如果需要在执行exec
时,比较acc1有没有发生变化,如果变化了,就转账失败。该如何处理呢,这就可以使用redis的watch
做乐观锁。下面模拟两个客户端同时修改redis数据,使用watch做乐观锁的情况。
第一步:初始化两个账户的金额。acc1是付钱账户,acc2是收钱账户。
第二步:使用客户端一,开启watch监听acc1是否发生变化,同时开启事物,命令入队(转账100元),先不执行事物。
第三步:使用客户端二,修改acc1账户的金额。
这里可以看到,客户端二执行成功了!!!如果是mysql,这个时候,客户端二应该是被阻塞的,必须要等客户端一执行完成后,这里才能成功。这也就是上面说的redis的事物没有隔离性,会相互影响。
第四步:使用客户端一,执行事物。
这里可以看出由于客户端二修改了acc2的账户金额,在客户端一执行exec
前,watch
监控到acc1的金额发送了变化,所以客户端一的转账过程就失败了。这里其实就是使用watch
实现了一个乐观锁。
完成,收工!
【传播知识,共享价值】,感谢小伙伴们的关注和支持,我是【诸葛小猿】,一个彷徨中奋斗的互联网民工!!!
版权声明: 本文为 InfoQ 作者【诸葛小猿】的原创文章。
原文链接:【http://xie.infoq.cn/article/16864640dc10d8d96b5e7ed3b】。文章转载请联系作者。
评论