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"