前言
事务这个在任何数据库中都会有的概念,比如熟悉的mysql,事务的四大特性ACID更是面试中出场率百分之八十的选手,在Redis中的事务和mysql的事务不太一样,比如说原子性,在mysql中保证了事务的原子性,也就是说整个事务要不成功,要不全部失败。而Redis并不保证事务原子性,Redis只保证单条命令的原子性。
忘记提到了一个重要的概念,其实事务就是一组命令集合,并且是进行了序列化,在事务执行中保证了执行顺序;
Redis的事务是没有隔离性的概念的,所以也就没有实际的锁
在Redis中,开始事务后是将一系列命令放到queue中,然后等待执行
基本操作
涉及到的事务的概念,已经在前言中把握理解的都讲了,事务所涉及的命令只有三个:multi、exec、discard
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> discard # 中断事务
OK
127.0.0.1:6379> get k1
(nil)
# 下面展示了在执行事务的过程中发生了编译错误,Redis整个事务的执行命令都将失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> excu
(error) ERR unknown command `excu`, with args beginning with:
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 下面展示了在执行事务的过程中发生了运行时错误,这个时候Redis只有错误的命令运行失败,其他命令依然成功,这点就和mysql有很大的不同了
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
4) "v1"
乐观锁
既然有了乐观锁,那必然有悲观锁,这两个锁的含义,按照我的理解
悲观锁:在多线程下,认为做什么都会出现问题,所以做什么都会加锁
乐观锁:在多线程下,认为做什么都不会出现问题,所以不会加锁,但这并不是意味着就不处理多线程情况下的问题,乐观锁主张记录一个version状态,当更新的时候会先判断一个version状态是否改变,如果改变则更新失败,如果未改变则修改成功,并且修改version状态
这个有redis的乐观锁和悲观锁在秒杀场景下的应用,我截取两个和本文相关的话
乐观锁的实现:
乐观锁实现中的锁就是商品的键值对。使用jedis的watch方法监视商品键值对,如果事务提交exec时发现监视的键值对发生变化,事务将被取消,商品数目不会被改动。
悲观锁实现:
悲观锁中的锁是一个唯一标识的锁lockKey和该锁的过期时间。首先确定缓存中有商品,然后在拿数据(商品数目改动)之前先获取到锁,之后对商品数目进行减一操作,操作完成释放锁,一个秒杀操作完成。这个锁是基于redis的setNX操作实现的阻塞式分布式锁。
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视 money,记录money当前状态
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby money 10
QUEUED
127.0.0.1:6379> exec # 因为在执行过程中,其他线程没有修改money的值,所以执行成功
1) (integer) 90
2) (integer) 100
127.0.0.1:6379> mget money out
1) "100"
2) "0"
# 下面展示不成功的例子,当在事务未提交前,其他线程修改了money的值:set money 1000,所以事务执行失败
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec # 事务执行失败
(nil)
127.0.0.1:6379> unwatch # 这个是取消监视的意思
OK
自己写这个命令就会发现,watch只是监视一次事务,并不是一直监视这个元素,也就是说当我们一次事务执行后,无论是否成功,watch都结束了。下次发生的事务,如果没有监视的话,那么无论其他线程是否修改元素,都会提交成功!因此需要乐观锁的话,需要在每次事务开启前,监视某些可能会变化的值
结语
Redis的事务相关暂时就理解到这,可能随着继续深入会有更深的理解!
下一节是springboot操作redis(Jedis)