Redis 中的事务和锁机制

Redis 中的事务和锁机制

Redis 的事务机制是一种将多个命令打包执行的机制,可以确保这些命令要么全部执行成功,要么全部失败,没有中间状态。在 Redis 中,事务通过 MULTIEXECDISCARDWATCH 四个命令来实现。

  1. MULTI: 这个命令用于标记事务的开始。一旦调用了 MULTI,后续的命令不会立即执行,而是会被放入一个队列中等待执行。
  2. EXEC: 这个命令用于执行事务中的所有命令。一旦调用了 EXEC,Redis 会按照命令的顺序执行队列中的所有命令。
  3. DISCARD: 如果在执行 EXEC 之前需要取消事务,可以使用 DISCARD 命令。
    这会清空事务队列并取消事务。
  4. WATCH: 该命令用于监视一个或多个键,一旦有其他客户端对这些键进行了修改,当前事务就会被打断。WATCH 命令可以用于乐观锁的实现,即在事务执行前检查被监视键是否被其他客户端修改过。

multi、exec、discard

从输入 Multi 命令开始,输入的命令都会一次进入命令队列中,但不会执行,知道输入 Exec 后,Redis 会将之前的命令队列中的命令依次执行。
image.png

案例

image.png
组队阶段和执行阶段均未出错,执行成功!
image.png
组队阶段报错,执行失败!
image.png
执行阶段报错,部分执行成功!

为什么需要事务

经典的银行转账问题:
假设你有两个账户,分别是 account1 和 account2,你想要在它们之间进行资金转移。在这种情况下,你可以使用 Redis 事务确保转账是原子性的。
假设 account1 的余额为 100 元,account2的余额为50元。现在,你想要将10元从account1转移到 account2。以下是一个使用Redis事务的示例:

redis
MULTI
DECRBY account1 10  # 从 account1 中减少10元
INCRBY account2 10  # 在 account2 中增加10元
EXEC

上述例子中,DECRBY 和 INCRBY 命令分别用于在 account1 中减少10元和在 account2 中增加10元。这两个命令被包含在 MULTI 和 EXEC 之间,形成一个事务。如果执行事务时没有发生错误,那么转账将会以原子方式完成。如果在执行事务时发生了错误,整个事务会被回滚,确保不会发生部分转账。

事务冲突

先来看一个事务冲突的例子:

考虑一个简单的在线购物系统的场景,其中有一个库存系统来跟踪商品的库存量。在这个系统中,多个用户可能同时试图购买同一商品,这可能导致事务冲突。

  1. 初始状态: 商品A的库存为10。
  2. 用户A尝试购买:
  • User A检查商品A的库存,发现库存为10。
  • User A决定购买一个商品A,将库存减少1。
WATCH inventory:A
val = GET inventory:A
val = val - 1
MULTI
SET inventory:A $val
EXEC
  1. 用户B尝试购买(冲突发生):
  • 在User A执行事务的过程中,User B也尝试购买商品A。
  • User B检查商品A的库存,发现库存仍为10。
  • User B决定购买一个商品A,将库存减少1。
WATCH inventory:A
val = GET inventory:A
val = val - 1
MULTI
SET inventory:A $val
EXEC

在这个例子中,由于User B在User A的事务执行期间也尝试购买商品A,两个事务之间发生了冲突。当User B执行事务时,由于被 WATCH 监视的键(inventory:A)已经发生变化(被User A的事务修改了),User B的事务将会失败,避免了商品库存的错误减少。

悲观锁

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

  1. 频繁写入场景: 当对共享资源进行频繁的写入操作时,悲观锁可以有效地防止多个事务同时修改同一资源,避免并发冲突。这对于要求严格一致性的系统是很重要的。
  2. 长事务场景: 在长事务的场景中,如果一个事务在执行期间需要对一些共享资源进行多次读取和写入,悲观锁可以确保在事务执行期间资源不被其他事务修改。
  3. 对资源修改敏感的场景: 当对共享资源的修改对系统产生重大影响时,悲观锁可以用于确保在修改期间其他事务不会干扰。这可以防止在关键时刻出现数据不一致的情况。
  4. 独占资源场景: 当某个操作需要独占一个资源时,悲观锁是一种有效的方式。例如,某个线程正在修改一个文件,为了防止其他线程同时修改,可以使用悲观锁。
  5. 避免并发冲突的场景: 在某些情况下,虽然悲观锁可能导致一定的性能损失,但为了避免并发冲突和保证数据的完整性,悲观锁仍然是一个合适的选择。

需要注意的是,悲观锁可能会引入性能开销,因为它在操作前会尝试获取锁,而其他事务可能需要等待。因此,在选择是否使用悲 观锁时,需要权衡一致性和性能之间的取舍,具体取决于应用程序的要求。

乐观锁

**乐观锁(Optimistic Lock), **顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,如 watch 就是一个轻量级的乐观锁操作,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
image.png
乐观锁适用于以下场景:

  1. 高并发读取场景: 在读取操作比写入操作更频繁的情况下,乐观锁是一种较为适用的机制。多个事务可以同时读取相同的资源,而在写入时会检查是否有冲突。
  2. 短事务场景: 在事务执行时间较短,对共享资源的写入操作不频繁的情况下,乐观锁可以降低锁的争用,提高并发性能。
  3. 无锁算法: 一些分布式系统中采用无锁算法,其中乐观锁是一种常见的实现方式。在这种情况下,系统更侧重于尽量避免使用显式锁,而是通过版本号、时间戳等方式来处理并发访问。
  4. 数据冲突较少的场景: 如果在应用程序中,对于共享资源的写入冲突相对较少,并且系统可以通过检测冲突并进行重试来处理,那么乐观锁是一个合适的选择。
  5. 支持冲突检测和重试的场景: 乐观锁适用于能够检测到冲突,并在发生冲突时进行适当处理(例如,回滚事务或重新尝试操作)的场景。

总体来说,乐观锁更适合处理读操作频繁、写操作较少,并且系统能够容忍一定程度的冲突和重试的场景。在这样的环境中,乐观锁可以提高并发性能,避免了悲观锁可能引入的性能开销。

watch key [key …]

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
举例:

127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby balance 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 11
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 10
QUEUED
127.0.0.1:6379> exec
(nil)

WATCH 在 Redis 中通常被归类为乐观锁

  • 乐观锁: 在乐观锁的情况下,系统假定并发冲突的发生是不太可能的。因此,它允许多个事务同时访问资源,并在执行事务时检查是否有冲突。如果发现冲突,系统会回滚事务,要求重新尝试。
  • 在使用 WATCH 时,Redis并不立即锁定资源。相反,它会监视一个或多个键,而在事务执行之前,不会阻止其他客户端对这些键进行修改。只有在执行事务时,Redis会检查被监视的键是否发生了变化,如果有变化,事务将被取消,需要重新尝试。

unwatch

取消WATCH命令对所有key 的监视。
如果在执行WATCH命令之后,EXEC命令或DISCARD命令先被执行了的话,那么就不需要再执行UNWATCH了。

Redis 事务的三大特性

  1. 单独的隔离操作

事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

  1. 没有隔离级别的概念

队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。

  1. 不保证原子性

事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

  • 10
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*Soo_Young*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值