Redis 事务揭秘:如何确保数据一致性

Redis 事务揭秘:如何确保数据一致性

  • 一 . 认识 Redis 事务
  • 二 . 事务相关的命令
  • 三 . watch 的实现原理

Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 ! 本篇文章给大家讲解的是 Redis 中的事务 . Redis 的事务是一个用于将多个命令打包在一起执行的特性 , 它可以保证这些命令要么全部执行 , 要么全部不执行 , 从而确保数据的一致性 . 我们可以看一下 Redis 中的事务是如何实现的

在这里插入图片描述

本专栏旨在为初学者提供一个全面的 Redis 学习路径,从基础概念到实际应用,帮助读者快速掌握 Redis 的使用和管理技巧。通过本专栏的学习,能够构建坚实的 Redis 知识基础,并能够在实际学习以及工作中灵活运用 Redis 解决问题 .
专栏地址 : Redis 入门实践

一 . 认识 Redis 事务

我们之前学习过 MySQL 的事务

  1. 原子性 : 把多个操作打包成一个整体
  2. 一致性 : 事务执行之前和事务执行之后数据要一直正确
  3. 持久性 : 事务做出的修改都会保存到硬盘中
  4. 隔离性 : 事务并发执行涉及到的一些问题

相比于 MySQL 来说 , Redis 的事务就简单了很多

  1. 弱化了原子性 : Redis 有无原子性存在争议 , 是因为 MySQL 将原子性提高了门槛

原子性最原本的含义指的是把多个操作打包成一个整体 , 要么全部执行 , 要么全部不执行 .

那 Redis 也确实做到了这样的要求 , 但是相比之下 MySQL 的原子性要求更加严格 , MySQL 要求要么全部执行成功 , 要么全都不执行 .

Redis 是不能保证执行成功或者执行正确的 , 如果事务中若干个操作存在失败的事务 , 那就这么地了 , 而 MySQL 中有事务执行操作失败 , 那就需要进行回滚 , 将已经执行的操作也需要全部进行回退

  1. 不具有一致性 : 由于 Redis 并没有回滚机制 , 也没有相关约束 , 那事务执行过程中如果某个修改操作出现失败 , 就可能引起不一致的情况
  2. 不具有持久性 : Redis 是一个内存数据库 , 虽然也有持久化机制 , 但是 Redis 可以禁用持久化机制
  3. 不涉及隔离性 : Redis 是一个单线程模型的服务器程序 , 所有的请求都是串行执行的

那 Redis 的事务存在的意义 , 就是为了 “打包” 操作 , 避免其他客户端的命令插队执行 . 在 Redis 中实现事务 , 是引入了一个队列 , 这个队列是每个客户端都有的 .

开启事务的时候 , 此时客户端输入的命令 , 就会发送给服务器并且保存到队列中 (并不是立即去执行)

当执行事务的时候 , 此时就会把队列中的这些任务按照顺序依次执行 (依次执行都是通过 Redis 主线程中完成的 , 主线程会把事务中的操作全部执行完 , 再去处理别的客户端的操作)

二 . 事务相关的命令

开启事务 : multi

执行事务 : exec

放弃事务 : discard

我们具体操作一下

127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 111 # 设置键值对
# 但是目前并不是真正的保存到了 Redis 中
# 只是存放到了服务器存放事务的队列中 , 保存了一系列请求
QUEUED 
127.0.0.1:6379> set k2 222
QUEUED
127.0.0.1:6379> set k3 333
QUEUED

那此时我们再开启另外一个客户端 , 是查询不到数据的

# 此时在另外一个客户端是查询不到数据的
# 这也就说明我们的数据目前还未被执行
127.0.0.1:6379> keys * 
(empty array)
127.0.0.1:6379> mget k1 k2 k3
1) (nil)
2) (nil)
3) (nil)

那我们客户端 1 执行了 exec 操作 , 也就是执行事务操作

127.0.0.1:6379> exec # 执行事务
# 返回三个操作的执行结果
1) OK
2) OK
3) OK

我们也演示一下

那我们还可以取消事务 , 使用 discard 命令

127.0.0.1:6379> multi # 重新开启一个事物
OK
# 修改之前的键值对的值
127.0.0.1:6379> set k1 aaa
QUEUED
127.0.0.1:6379> set k2 bbb
QUEUED
127.0.0.1:6379> set k3 ccc
QUEUED

此时我们 k1 k2 k3 的值仍然是 111 222 333

127.0.0.1:6379> mget k1 k2 k3
1) "111"
2) "222"
3) "333"

那我们执行 discard 命令来去取消当前事务

127.0.0.1:6379> discard # 取消本次事务
OK

然后再去观察 k1 k2 k3 的值 , 依然没有任何问题

127.0.0.1:6379> mget k1 k2 k3
1) "111"
2) "222"
3) "333"

我们也演示一下


❓ 那假如说我们比较点背 , 当我们开启事务并且已经添加若干个指令之后 , 此时服务器重启 , 那这个事务怎样处理呢 ?

✔️ 那很遗憾了 , 此时的效果就相当于 discard 了

我们也能看到 , 当服务器重启之后想要执行事务 , 但是得到的回复却是 (error) ERR EXEC without MULTI , 意思是没有对应的 multi , 这也就说明了之前开启的事务已经失效了 . 而且再去获取 k1 k2 k3 的值 , 也没有发生变化 , 这也印证了事务执行失败 .

那还有一个命令 , 叫做 watch . 他可以监控某个 key 是否在事务的执行前后发生了改变

比如我们现在有两个 Redis 客户端 , 分别修改同一个键值对

Redis 事务揭秘:如何确保数据一致性_第1张图片

那大家觉得最后 key 的值是 222 还是 333 呢 ?

按理说我们客户端 2 修改操作执行的晚 , 所以最后的结果应该就是 333 了 , 那我们实际运行一下看一下结果

Redis 事务揭秘:如何确保数据一致性_第2张图片

实际结果是 222

表面上来看 , 我们先执行的 set key 222 , 后执行的 set key 333

但是实际上 , 我们的 set key 222 是推迟执行的 , 也就是说只有执行了 exec 才算执行了 set key 222 , 那还是 set key 222 更晚一些

那这样就容易产生歧义 , 所以我们可以使用 watch 命令来去监控这个 key , 看看这个 key 在事物的 multi 和 exec 之间 , 是否被其他客户端修改了

我们可以看到 , 在执行了 exec 之后 , 返回值为 nil , 代表事务执行失败 , 这是因为我们 watch 的缘故 , 让第二个客户端修改成功了

三 . watch 的实现原理

watch 的实现 , 类似于一个 “乐观锁” , 通过版本号这样的机制来实现乐观锁

乐观锁和悲观锁并不是一个具体的锁 , 而是一类锁的特征

乐观锁 : 加锁之前就会预测接下来锁冲突的概率比较低

悲观锁 : 加锁之前就会预测接下来锁冲突的概率比较高

锁冲突概率的高低 , 和接下来要做的工作是不一样的

Redis 事务揭秘:如何确保数据一致性_第3张图片


那到此为止 , Redis 中的事务就已经介绍完毕 , 如果对你有帮助的话 , 还请一键三连~~~

你可能感兴趣的:(Redis,入门实践,redis,数据库,缓存)