[Redis#16] 事务 | vs Mysql | 命令 | WATCH的实现

目录

什么是事务

实现事务的方式

[Redis 事务与 MySQL 事务的对比](#Redis 事务与 MySQL 事务的对比)

应用场景:防止超卖

[Lua 脚本增强](#Lua 脚本增强)

事务操作

[MULTI & EXEC](#MULTI & EXEC)

DISCARD

WATCH

[WATCH 的实现原理](#WATCH 的实现原理)


什么是事务

[MySQL#12] 事务(1) | ACID | commit | 回滚 | 常见操作

Redis 的事务和 MySQL 的事务概念上是类似的,都是把一系列操作绑定成一组,让这一组能够批量执行。然而,需要注意的是 Redis 事务与 MySQL 事务存在以下几点区别:

  • 弱化的原子性 : Redis 没有 "回滚机制"。它只能保证这些操作 "批量执行",但不能做到 "一个失败就恢复到初始状态"。
  • 不保证一致性: Redis 不涉及 "约束",也没有回滚机制。MySQL 的一致性体现在运行事务前后结果的合理性,不会出现中间非法状态。
  • 不需要隔离性 : Redis 事务没有隔离级别,因为 Redis 是单线程处理请求,不会并发执行事务。
  • 不需要持久性 : Redis 数据保存在内存中,是否开启持久化由 redis-server 自行决定,这与事务无关。

Redis 事务本质上是在服务器上创建了一个 "事务队列"。

每次客户端在事务中进行一个操作,都会先将命令发送给服务器并放入 "事务队列"(但并不会立即执行),而是在收到 EXEC 命令后,才真正执行队列中的所有操作。

  • 这么多特性Redis都不具备,那么Redis的事务到底有什么意义呢?
    Redis的事务最主要的意义就是为了"打包",避免其他客户端的命令插队到中间.

最开始官网还说原子性,后来就把这句话给删了,官方也是有点虚的


实现事务的方式

  • Redis 在实现事务时引入了队列机制。每个客户端都有一个独立的队列。
  • 当开启事务时,客户端输入的命令会发送给服务器并进入这个队列中,而不是立即执行。
  • 只有当遇到 "执行事务" 的命令(如 EXEC)时,才会把队列中的任务按照录入顺序依次执行。
  • 这些事务操作是在 Redis 主线程中完成的。
  • 主线程会确保将事务中的所有操作执行完毕后,再处理其他客户端的请求。
Redis 事务与 MySQL 事务的对比

虽然 Redis 事务的功能没有 MySQL 强大,但 MySQL 为了实现强大的事务功能也付出了不小的代价:

  • 空间上 :MySQL 需要花费更多空间来存储额外的数据,例如用于回滚段和多版本控制。
  • 时间上 :MySQL 的事务处理需要更大的执行开销,包括锁管理、日志记录等。

正因为 MySQL 事务存在上述问题,Redis 提供了一种轻量级的解决方案,适用于某些特定场景。

应用场景:防止超卖

在购物网站秒杀场景中,可能会出现超卖的情况,即商家放了5000个商品,但由于并发问题导致下单成功了5001个。为避免这种情况,在多线程环境中我们曾通过加锁的方式来解决。

如果引入 Redis,则可以直接使用其事务机制解决问题。

例如,考虑两个客户端几乎同时下单的情况:

  • 客户端1开启一个事务,该事务被放入 Redis 的事务队列中;
  • 客户端2同样开启一个事务,并被放入队列;
  • 当事务1收到执行命令被执行时,它会减少库存计数(count--);
  • 当轮到事务2执行时,由于此时 count>0 的条件不再满足,所以事务2将不会改变数据库状态,从而避免了超卖问题。
Lua 脚本增强

值得注意的是,尽管 Redis 本身不支持像编程语言那样的条件判断(如 if),但它可以引入 Lua 脚本来实现复杂的逻辑判断。通过 Lua 脚本,我们可以实现上述条件的判断逻辑,进一步强化 Redis 事务的应用能力。


事务操作

MULTI & EXEC
  • multi:开启事务
  • exec:提交事务

示例:

开启事务后,不论输入什么指令,都返回QUEUE,此时会在客户端维护一个队列,将所有指令入队列。最后执行exec提交事务,所有的指令再同时返回。

DISCARD
  • discard:取消事务

示例:

开启事务后,输入两个值,再通过discard取消事务,最后查询key1,发现插入失败,因为这个请求没有提交给客户端,而是直接被取消了。

另外的,如果在事务执行的过程中,Redis崩溃,恢复后效果和discard一样,就是事务内的操作都取消了。

WATCH

现有以下场景:

  • 从时间上来看,客户端1 是先发送了 set key 222,客户端2 是后发送了 set key 333
  • 由于 客户端1 中,得是 exec 执行了,才会真正执行 set key 222 这个操作
  • set key 222 变成了实际上更晚执行的操作!! 最终值就是 222

在事务执行过程中,客户端1无法感知外部的变化,客户端2的命令被 客户端 1 事务覆盖了。

  • watch:让事务可以监听外部的变化,如果监听的数据被外部改变,操作失效

语法:

左侧的终端,在执行事务前开启了watch key,开启事务后set key 222。在事务提交前,右侧终端修改了key 333,随后左侧终端提交事务时,返回了nil表示事务操作无效。

因为watch监听到了key被外部修改,此时自己的事务提交可能会影响其它客户端,于是取消该操作。

如果想要取消watch,可以使用unwatch指令:

复制代码
unwatch

这个指令没有参数,一次性取消所有key的监听。

WATCH 的实现原理

WATCH 的实现原理类似于我们在并发编程中学习的乐观锁机制,即解决 CAS(Compare-And-Swap)中的 ABA 问题的策略。乐观锁假设冲突很少发生,并在检测到冲突时采取相应的措施。与 ABA 问题中的实现策略相似,Redis 的 WATCH 基于版本号机制实现了乐观锁。

watch使用了一种版本号的机制,每个数据都有一个版本号,每次修改key的值时,都会修改其版本号。

  • 在watch key时,会记录当前的版本号。
  • 在事务提交时,检测当前的版本号是否与之前的版本号相同,如果相同那么提交成功,如果版本号不同,说明有别的用户修改了数据,导致版本号修改,当前事务将不会执行并返回失败。

WATCH 本质上是给 EXEC*++加上了一个对特定 key 的判定条件++:*只有当所有被 WATCH 的 key 自从 WATCH 开始以来没有被修改过的情况下,事务才会被执行;否则,事务将被取消。

举例说明

可以将 WATCH 比喻为判断老公是否出轨的情景:

  • 离开家之前:把枕头放到一个特定的位置,并拍下一张照片作为记录。
  • 回家之后:检查枕头的位置是否发生了改变。如果位置变了,则可以推测出在这段时间内有人移动了枕头(在这个比喻中意味着老公可能出轨了)。

同样的逻辑应用于 Redis 中:

  • WATCH key3:开始监视 key,此时记录下 key 的状态(版本号)。
  • 尝试执行事务 :如果在此期间 key 被其他客户端修改过,那么提交事务时就会发现 key 的状态已经改变,从而导致当前事务执行失败。如下就返回了 Nil

乐观锁与悲观锁

  • 乐观锁:乐观锁假设在加锁之前,锁冲突的概率较低。因此,在加锁之前不会进行任何检查,而是直接进行操作。++如果在操作完成后发现冲突,则会进行重试。++
  • 悲观锁:悲观锁假设在加锁之前,锁冲突的概率较高。因此,在加锁之前会进行检查,确保没有其他线程正在操作该资源。++如果发现冲突,则会阻塞等待。++

实现示例

  • 在 C++ 和 Linux 中涉及的锁,如 mutex 和 std::mutex,都是悲观锁。
  • Java 中的 synchronized 关键字则可以在悲观/乐观之间自适应。

事务命令 sum:

  • MULTI:开启一个事务,执行成功返回 OK。
  • EXEC:真正执行事务。每个操作加入事务时会提示 "QUEUED",表示命令已经进入客户端的队列,直到 EXEC 执行时才会真正发送给服务器。
  • DISCARD:放弃当前事务,清空事务队列,之前的操作都不会真正执行。
  • WATCH:用于监控一组具体的 key,在提交事务时如果发现 key 被其他客户端修改过,则事务执行失败。
  • UNWATCH:取消对 key 的监控,相当于 WATCH 的逆操作。
相关推荐
Asthenia041239 分钟前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom1 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide1 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫