Redis 事务
事务的基础概念
关于事务最常见的例子就是银行转账,A 账户给 B 账户转账一个亿 (T1),买一块地盖房子。在这种交易的过程中,有几个问题值得思考:
- 如何同时保证上述交易中,A账户总金额减少一个亿,B账户总金额增加一个亿? A
- A账户如果同时在和C账户交易(T2),如何让这两笔交易互不影响? I
- 如果交易完成时数据库突然崩溃,如何保证交易数据成功保存在数据库中? D
- 如何在支持大量交易的同时,保证数据的合法性(没有钱凭空产生或消失) ? C
要保证交易正常可靠地进行,数据库就得解决上面的四个问题,这也就是事务诞生的背景,它能解决上面的四个问题,对应地,它拥有四大特性(ACID)。
-
原子性(Atomicity): 事务要么全部完成,要么全部取消。 如果事务崩溃,状态回到事务之前(事务回滚)。
-
隔离性(Isolation): 如果2个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1和T2谁先结束。
-
持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。
-
一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。
Redis 中的事务
# 标记一个事务块的开始
# 事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行
MULTI
# 执行所有事务块内的命令。
EXEC
# 取消事务,放弃执行事务块内的所有命令。
DISCARD
示例:
SET Jack 10
SET Rose 20
# Jack 给 Rose 转账 5 块钱
# 开启事务
MULTI
DECRBY Jack 5
INCRBY ROSE 5
EXEC
上面的代码演示了事务的使用方式。
(1)开始事务:首先使用 MULTI 命令告诉 Redis:“下面我发给你的命令属于同一事务,你先不要执行,而是把它们暂时存起来”。Redis 回答:“OK”
(2)命令入队:而后我们发送了两个命令来实现相关操作,可以看到 Redis 遵守了承诺,没有执行这些命令,而是返回 QUEUED 表示这两条命令已经进入等待执行的事务队列中了
(3)执行事务:当把所有要在同一事务中执行的命令都发给 Redis 后,我们使用 EXEC 命令告诉 Redis 将等待执行的事务队列中的所有命令按照发送的顺序依次执行。EXEC 命令的返回值就是这些命令的返回值组成的列表,返回值顺序和命令的顺序相同。
(4)如果想要取消事务,则执行 DISCARD 命令。
Redis 保证了一个事务中的所有命令要么都执行,要么都不执行。如果在发送 EXEC 命令前客户端掉线了,则 Redis 会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了 EXEC 命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为 Redis 中已经记录了所有要执行的命令。
除此之外,Redis 的事务还能保证一个事务内的命令依次执行而不被其它命令插入。试想客户端 A 需要执行几条命令,同时客户端 B 发送了一条命令,如果不适用事务,则客户端 B 的命令可能会插入到客户端 A 的几条命令中执行。如果不希望发送这种情况,也可以使用事务。
事务中的错误处理
如果一个事务中的某个命令执行出错,Redis 会怎么处理呢?要回答这个问题,首先需要知道什么原因导致命令执行出错。
(1)语法错误。语法错误指命令不存在或命令参数的个数不对。比如:
MULTI
# 正确的命令
SET key value
# 错误的命令
SET key
ERRORCOMMAND key
EXEC
跟在 MULTI 命令后执行了 3 个命令:
- 一个正确的命令,成功的加入了事务队列
- 其余两个命令都有语法错误
而只要有一个命令有语法错误,执行 EXEC 命令后 Redis 就会直接返回错误,连语法正确的命令也不会执行。
(2)运行错误。运行错误指在命令执行时出现的错误,比如使用散列类型的命令操作集合类型的键,这种错误在实际执行之前 Redis 是无法发现的,所以在事务里这样的命令是会被 Redis 接受并执行的。如果事务里的一条命令出现了运行错误,事务里其它的命令依然会继续执行
MULTI
SET key 1
SADD key 2
SET key 3
EXEC
Redis 事务没有关系数据库事务提供的回滚(rollback)功能。为此开发者必须在事务执行出错后自己收拾剩下的摊子(将数据库复原回事务执行前的状态等)。
不过由于 Redis 不支持回滚功能,也使得 Redis 在事务上可以保持简洁和快速。此外回顾刚才提到的会导致事务执行失败的两种错误,其中语法错误完全可以在开发时找出并解决,另外如果能够很好的规划数据库的使用,是不会出现如命令与数据类型不匹配这样的运行时错误的。
事务中的 WATCH 命令
关于 WATCH 命令,我们来一个生活中的例子比较好理解。
假设我的银行卡有 100 元,此时我去商店买东西
# 开启事务
MULTI
# 假设里面有 100 元
SET balance 100
# 拿了瓶水
SET balance 3
# 拿了包烟
SET balance 20
我的银行卡除了我自己消费使用,还绑定了我媳妇儿的支付宝,如果我在消费的时候,她也消费了会怎么样?
# 开启事务
MULTI
# 买了 10 斤苹果
SET balance 100
EXEC
这时候我媳妇在超市直接刷了 100,此时余额不足的我还在挑口香糖…
针对于上面的场景,我们可以使用 Redis 事务中提供的 WATCH 功能来解决这个问题。
WATCH 定义:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
WATCH 相关命令如下:
# 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
WATCH key [key ...]
# 取消 WATCH 命令对所有 key 的监视。
# 如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
UNWATCH
SET balance 100
WATCH balance
DECRBY balance 30
MULTI
DECRBY balance 10
EXEC
GET balance # 70
如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令被执行了的话,那么会自动取消 WATCH。
如果需要手动停止 WATCH 则可以可以使用 UNWATCH 命令,UNWATCH 命令会取消 WATCH 命令对所有 key 的监视。
参考链接
- https://zhuanlan.zhihu.com/p/43493165
- https://xie.infoq.cn/article/84baa7fa9c2c3d3698a601def
Redis持久化
Redis 的强劲性能很大程度上是由于其将所有数据都存储在内存中,然而当 Redis 重启或宕机后,所有存储在内存中的数据就会丢失。在一些情况下,我们会希望 Redis 在重启后能够保证数据不丢失。
这时我们希望 Redis 能将数据从内存中以某种形式同步到硬盘中,使得重启后可以根据硬盘中的记录恢复数据。这一过程就是持久化。
Redis 提供了两种持久化方案:
- RDB 持久化,根据指定的规则“定时”将内存中的数据存储在硬盘上,在重启之后读取硬盘上的 .rdb 快照文件将数据恢复到内存中。
- AOF 持久化:AOF 持久化记录服务器执行的所有写操作命令形成 .aof 日志文件保存到硬盘中,并在服务器启动时,通过重新执行这些命令来还原数据集。