Redis 事务与锁 机制

Redis的事务定义

Redis主要使用MULTI, EXEC, DISCARD 和 WATCH 命令来实现事务功能。

事务可以一次执行多个命令,并带有两个重要的保证:

  • 事务中的所有命令都被序列化并按顺序执行。Redis执行事务期间,不会被其它客户端发送的命令打断,事务中的所有命令都作为一个隔离操作顺序执行。
  • Redis事务是原子操作,或者执行所有命令或者都不执行。 EXEC 命令触发一个事务中所有命令的执行,所以,如果一个客户端断在调用EXEC 命令前丢失连接,那么所有的命令不会被执行,相反,如果EXEC 被调用,那么所有命令会被执行。

Multi、Exec、discard

Redis使用 MULTI 命令标记事务开始,它总是返回OK。MULTI执行之后,客户端可以发送多条命令,Redis会把这些命令保存在队列当中,而不是立刻执行这些命令。所有的命令会在调用EXEC命令之后执行。保存命令的过程中(没有调用EXEC命令之前)可以通过DISCARD来放弃这部分操作。

redission 锁 事件 redis 锁机制_Redis

案例:

下面是提交成功的情况:

redission 锁 事件 redis 锁机制_Redis_02

假如在MULTI阶段发生错误,那么整个提交也不会成功:

redission 锁 事件 redis 锁机制_Redis_03

假如MULTI阶段成功后,EXEC阶段出现错误,那么成功的部分将会被提交,而失败部分(语义错误)将不会被执行:

redission 锁 事件 redis 锁机制_redission 锁 事件_04

上面的错误是由incr m1所触发的,因为不能对字符串进行incr操作。

事务的错误处理

从上面的使用案例中可以看到,Redis事务对于错误的回滚,所采用的方式跟以往的关系型数据库不一致。

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。

redission 锁 事件 redis 锁机制_redission 锁 事件_05

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

redission 锁 事件 redis 锁机制_redis_06

事务冲突的问题

例子

想想一个场景:有很多人拥有同一个的账户,他们尝试使用同一个账户,同时去参加双十一抢购。

假如此时账户只有10000块钱,而一个请求想买8000的商品、一个请求想买5000的商品、一个请求想买1000的商品。

他们的之间的请求几乎是同时进行。那所有人都能实现自己的想法吗?很显然,这在现实中是不被允许的:

redission 锁 事件 redis 锁机制_EXEC_07

于是我们要想办法限制某些人的行动,以达成符合正常事务逻辑的目的。对于程序员来说也就是给用户的操作”上锁“。

悲观锁

redission 锁 事件 redis 锁机制_Redis_08

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它能重新拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁

redission 锁 事件 redis 锁机制_redission 锁 事件_09

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新数据之前会判断一下在此期间该数据是否被被人更新。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

可以使用版本号等机制实现乐观锁

WATCH

在 Redis 中使用 watch 命令可以决定事务是执行还是回滚。一般而言,可以在 multi 命令之前使用 watch 命令监控某些键值对,然后使用 multi 命令开启事务,执行各类对数据结构进行操作的命令,这个时候这些命令就会进入队列。

当 Redis 使用 exec 命令执行事务的时候,它首先会去比对被 watch 命令所监控的键值对,如果没有发生变化,那么它会执行事务队列中的命令,提交事务;如果发生变化,那么它不会执行任何事务中的命令,而去事务回滚。无论事务是否回滚,Redis 都会去取消执行事务前的 watch 命令.

可以看到的是,这其实就是乐观锁

这个过程如图所示:

redission 锁 事件 redis 锁机制_redission 锁 事件_10

为了更直观的演示,首先打开两个命令行界面,同时连接上redis。随后使用watch观察同一个变量,在分别multi后,对其执行incrby n 20的操作,随后观察两个界面分别返回的结果:

redission 锁 事件 redis 锁机制_数据库开发_11

redission 锁 事件 redis 锁机制_EXEC_12

可以看到的是,先执行exec的客户端返回了正常的返回追,而后执行的客户端则返回了nil(空)。

unwatch

取消 WATCH 命令对所有 key 的监视。

如果在执行WATCH命令之后,EXEC命令或DISCARD命令先被执行,那么就不需要再执行UNWATCH了。

http://doc.redisfans.com/transaction/exec.html

Redis事务三特性

从上面介绍的几小节知识来看,Redis对于事务的支持不用于以往使用的关系型数据库,其主要有如下三个特点:

  • 单独的隔离操作
  • 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念
  • 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
  • 不保证原子性
  • 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚