Redis事务及锁应用
一,事务的应用
事务: 即逻辑上的一组操作,要么全部成功,要么全部失败。
参考mysql中的事务,redis为了处理实际业务同样提供了事务操作,下面我们参照mysql中的事务学习redis事务。
MySQL Redis
开启事务 begin multi
语句/命令 sql语句 普通命令
失败 rollback 回滚 discard 取消
关闭事务 commit exec
通过比较我们可以看到redis的事务命令,我们先参照mysql中的事务
模拟tom给jerry转账业务
begin;
update account set money=money-1000 where username='tom';
update account set money=money+1000 where username='jerry';
commit;
redis事务
- 启动事务
multi - 回滚事务,在redis中准确的说是取消事务。而不是回滚事务
discard - 提交事务
exec
案例
模拟tom给jerry转账业务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set tom 300
QUEUED
127.0.0.1:6379> set jerry 300
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
在这里我们可以看到,当执行开始事务指令后,执行set tom 300指令后,得到QUEUED,为队列的意思。
意为,当事务开启后,输入的每一条命令都被保存到一个队列中,当事务提交后按队列的顺序执行每一条指令。
那当队列中出现错误会怎样呢?
分为两种情况:
1)语句有语法错误,它会将所有的事务取消。
127.0.0.1:6379> set tom 300
QUEUED
127.0.0.1:6379> asdasdsa
(error) ERR unknown command `asdasdsa`, with args beginning with:
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get tom
"300"
2)语法本身没错,但适用对象有问题. 比如 zadd 操作list对象,Exec之后,会执行正确的语句,并跳过有不适当的语句.(如果zadd操作list这种事怎么避免? 这一点,由程序员负责)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd transfer jerry
QUEUED
127.0.0.1:6379> lpush transfer zhangsan
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379>
二,redis中锁的讲解及应用
常见的锁有乐观锁,悲观锁,排它锁,共享锁等。
这里着重介绍乐观锁和悲观锁。
- 乐观锁 : 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
- 悲观锁: 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
在redis中,它默认使用的是乐观锁。只负责监控key是否被改动。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set lisi:money 300
QUEUED
127.0.0.1:6379> set ticket 1
QUEUED
127.0.0.1:6379> exec
如上指令:模拟李四有300元钱,想要买一张车票。假设此时张三也有300元钱和李四共同抢最后一张piao。
则另开启一个进程
李四
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr ticket
QUEUED
127.0.0.1:6379> decrby lisi 200
QUEUED
127.0.0.1:6379> exec
1) (integer) -1
2) (integer) -200
127.0.0.1:6379>
张三
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set zhangsan:money 300
QUEUED
127.0.0.1:6379> decr ticket
QUEUED
127.0.0.1:6379> decrby zhangsan:money 200
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 0
3) (integer) 100
如果只是单单上面的代码,我们会发现ticket变为-1,这是绝对不允许发生的,该怎么办呢?这是就要用到redis的乐观锁,它使用watch命令监控指定key,如果key被改动了,当我们提交事务后,如果发生异常就会取消当前事务,所以我们只需在上面代码中开启事务前加入watch语句就可以避免上面的问题了。
watch 指令
127.0.0.1:6379> watch ticket ##添加锁
127.0.0.1:6379> unwatch ##释放锁
李四
127.0.0.1:6379> watch ticket
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr ticket
QUEUED
127.0.0.1:6379> decrby lisi:money 100
QUEUED
127.0.0.1:6379> exec
1) (integer) 0
2) (integer) 200
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> get ticket
"0"
张三
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr ticket
QUEUED
127.0.0.1:6379> decrby zhangsan:money 100
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> get ticket
"0"
127.0.0.1:6379> get zhangsan:money
"100"