7、Redis事务
7.1 背景
假如你给你朋友转账,此时你的账户会减少1bw,你朋友的账户会多1bw,此时如果你转账失败,但是你朋友的账户也多了1bw,此时这对于银行来说,这就是事故,说明你的程序存在很大漏洞,不能保证数据的原子性,此时就需要用到事务这个概念。
7.2 简介
Redis事务是一组有序的命令集合,所有命令都会按着顺序
序列化的执行,即在务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的执行主要分为两个阶段:组队阶段和执行阶段
- 组队阶段:主要将所有操作命令放入队列中
- 执行阶段:将放入队列中的命令按着顺序执行(FIFO),在执行的过程中不会被其他客户端发来的命令打断,确保数据的原子性
7.3、事务的命令
Redis事务的命令主要有五种,分别为
命令 | 格式 | 作用 | 返回值 |
---|---|---|---|
MULTI | MULTI | 显式开启Redis事务,后续命令将排队,等候使用EXEC进行原子执行 | always OK. |
EXEC | EXEC | 执行事务中的commands队列,恢复连接状态。如果WATCH在之前被调用,只有监测中的Keys没有被修改,命令才会被执行,否则停止执行 | 成功: 返回数组 失败: 返回NULL |
DISCARD | DISCARD | 清除事务中的commands队列,恢复连接状态。如果WATCH在之前被调用,释放监测中的Keys | always OK. |
WATCH | WATCH key [key …] | 将给出的Keys标记为监测态,作为事务执行的条件 | always OK. |
UNWATCH | UNWATCH | 清除事务中Keys的监测态,如果调用了EXEC或者 DISCARD,则没有必要再手动调用UNWATCH | always OK |
#监控key
watch k1
#开启事务
multi
# 写入值
set k1 v1
#取消事务
#discard
# 执行
exec
此时 key 正处于 watch 命令的监视之下,且事务块中有和这个( 或这些) key 相关的命令,那么 exec 命令只在这个( 或这些) key 没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort) 。事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。
7.4、案例详解
7.4.1、引入背景
假如在双十一期间,你和你女朋友同时用你的银行卡买东西,此时你支出1k,你女朋友支出1w,那么银行是怎么判断你账户的余额呢???
如果按着上述流程,肯定是存在问题的,因为按着上述流程都是在你原先账户余额的基础上进行判断的的,即相当于上述任务是并发执行的,但是大家都知道,当你支出后,下次在支出时,不可能会按着你原始的金额来进行判断,那么应该如何处理这个问题呢???此时我们可以考虑锁的概念,用锁来解决
7.4.2、乐观锁
乐观锁(Optimistic Lock):顾名思义就是很乐观,每次去拿数据的时候都会认为别人不会修改数据,所以不会上锁,但是当你在修改数据的时候,它就会首先判断在你修改数据之前是否有人修改过数据,此时可以用版本号等机制来实现。乐观锁适用于多读类型的业务,这样可以可以提高系统的吞吐量,redis就是使用该机制(check-and-set)来实现事务的 。
7.4.3、悲观锁
悲观锁(essimistic Lock):顾名思义就是很悲观,每次去拿数据的时候都会认为被人会修改数据,所以会上锁,这样只有当你拿到锁的时候才可以操作数据,如果拿不到锁,就会处于等待状态。悲观锁多用于关系型数据库中的表锁、行锁、并发环境中共享资源数据的一致性问题等。
7.4.4、Redis事务的特性
总所周知,在关系型数据库中事务具有ACID特性即:原子性、一致性、隔离性和持久性,但是redis数据库却并不全部支持ACID,在Redis数据库中事务支持以下几个特性
-
隔离性
在Redis事务中所有的命令都是按顺序序列化的执行,不会被其他客户端的命令所打扰,从而保证了事务的隔离性。虽然redis的事务具有隔离性,但是却没有隔离级别这个概念,也就是说redis事务中的隔离性都是在同一水平线上的,不存在隔离的级别。
-
一致性
Redis事务在执行期间会对被操作的数据进行锁定,确保其他客户端无法同时对同一数据进行操作,从而保证了事务的一致性。
-
伪原子性
redis事务中的原子性,是分阶段的,在组队阶段,如果命令错误,则会回滚之前的操作,也就是要么全成功,要么全失败,但是在执行阶段,就算某个命令执行失败,其他命令也不会收到影响。故redis’事务的原子性是不彻底的原子性,也叫分阶段原子性。
-
持久不彻底
虽然在redis中可以通过RDB或者AOF的方式进行数据的持久化,但是它的持久化缺失不彻底的,因为在遇到异常情况时,总会导致数据存在一些异常
7.4.5、解疑
1.Redis事务中的某个命令失败,会导致所有命令失败?
答:否,这个需要分阶段,一般情况下,在组队阶段时如果某个命令错误,则会导致所有命令失败,即保证了事务的原子性,但是在执行阶段时,如果某个命令失败,则不会影响其他命令的正常执行。
2.watch可以监控所有的key?
答:这个需要看你的配置文件,Redis 对于每个客户端对 WATCH 键的限制默认为 1024,可以通过修改配置文件中的 maxclients 参数进行调整。要确保在事务中监控的键的数量不超过这个限制,以避免 WATCH 键失败的情况。
3.使用事务一定很好?
答:不一定。由于 Redis 事务的执行是单线程的,事务中的命令会按顺序逐个执行。在某些情况下,使用事务可能会导致性能下降。对于一次性执行多个无关联的命令的情况,可以考虑使用批量命令(如管道 pipeline)来提高性能。
4.Redis事务在执行期间其他客户端不可以访问事务中涉及到键?
答:可以,Redis 事务不支持事务的隔离级别,事务执行期间其他客户端仍然可以访问和修改事务中涉及的键。因此,在处理事务时,要注意其他客户端对事务中键的读写操作可能导致数据不一致的情况。可以通过 WATCH 命令和乐观锁来处理并发访问的问题,以确保数据的一致性。