一、事务
1.1 Redis 事务是什么
-
首先,我们要了解传统数据库的事务代表什么?
- 一次与数据库的连接会话当中,所有执行的 SQL,要么一起成功,要么一起失败【例如:银行转账】
-
那么,我们 Redis 事务与 传统数据库的事务有什么不同吗?【以下是Redis事务的特性】
-
接下来,我们再说说 Redis 事务是什么?
- 可以一次执行多个命令,本质是一组命令的集合,一个事务中所有命令都会被序列化,按顺序的串行化执行而不会被其他命令插入【不需加塞】
- 换句话说:就是一个队列中一次性、顺序性、排他性的执行一系列命令
- 可以一次执行多个命令,本质是一组命令的集合,一个事务中所有命令都会被序列化,按顺序的串行化执行而不会被其他命令插入【不需加塞】
1.2 Redis 事务怎么用
-
首先,我们来看一下事务相关的命令
-
对于事务的使用,阳哥为我们分为五种情况:
- 正常执行
- 放弃事务
- 全体连坐
- 冤头债主
- watch 监控
-
接下来我们对这几部分展开叙述
1️⃣ 正常执行:
- 通过
multi
指令开启事务,通过exec
指令开始执行事务
2️⃣ 放弃事务:
- 在事务开始前,通过
discard
指令可以放弃事务,事务中的指令全部不会执行
3️⃣ 全体连坐:
- 在事务执行前,如果存在语句语法有错误,那么使用
exec
指令执行事务的时候,事务内的指令都不会执行
4️⃣ 冤头债主:
- 就是在事务执行前没检查出来错误,在通过
exec
命令执行事务后,发现了错误,这样争取的指令都会执行,错误的指令不会执行 - 因为 Redis 不提供事务回滚的功能,开发者必须在事务执行出错后,自行恢复数据库状态
- 这里就体现出与传统事务的差异了,指令集合中的语句不是同时成功或失败
5️⃣ watch 监控:
-
在介绍这部分之前,我们需要知道什么是乐观锁、悲观锁?
- 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
- 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
- 乐观锁策略:提交版本必须 大于 记录当前版本才能执行更新
-
Redis 使用 Watch 来实现乐观锁定类似 CAS (Check And Set)
使用 check-and-set WATCH 的乐观锁定用于为Redis事务提供一个check-and-set (CAS)行为。WATCH键被监视,以检测它们的变化。如果在执行EXEc命令之前至少修改了一个被监视的键,则整个事务将中止,EXEc将返回Null应答,通知事务失败。
- 我们来演示一下这个 watch 监控的使用:
-
初始化 k1 和 balance 两个 key,先通过
watch
监控 key,在通过multi
开启事务,保证两个 key 的变动在一个事务内
-
监测的 key 存在数据篡改行为
-
- watch命令是一种乐观锁的实现,Redis在修改的时候会检测数据是否被更改,如果更改了,则执行失败,第一个窗口蓝色框第5步执行结果返回为空,也就是相当于是失败
- Redis 事务的 unwatch 命令用于取消 WATCH 命令对所有 key 的监视
- 小结:
- Redis 开始执行事务(EXEC),那么之前加的所有的锁都会被释放
- 当客户端连接丢失时(比如退出连接),所有的东西都会取消监视
1.3 Redis 事务总结
- 开启:通过
multi
指令开启一个事务 - 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列中
- 执行:通过
exec
命令触发事务
二、管道
2.1 背景
- 首先,我们先了解一下为什么会产生管道这种机制:
- Redis 是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。一个请求会遵循以下步骤:
- 1 客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果),并监听Socket返回,通常以阻塞模式等待服务端响应。
- 2 服务端处理命令,并将结果返回给客户端。
- Redis 是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。一个请求会遵循以下步骤:
- 如果同时需要执行大量的命令,那么就要等待上一条命令应答后再执行,这中间不仅仅多了RTT(Round Time Trip),而且还频繁调用系统IO,发送网络请求,同时需要redis调用多次read()和write()系统方法,系统方法会将数据从用户态转移到内核态,这样就会对进程上下文有比较大的影响了,性能不太好
2.2 管道定义
- 为了解决上面的这个问题,我们引出管道的概念:
- 管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完完毕后,通过一条响应一次性将结果返回,通过减少客户端与redis的通信次数来实现降低往返延时时间
- pipeline实现的原理是队列,先进先出特性就保证数据的顺序性
- 管道的定义:Pipeline是为了解决RTT往返回时,仅仅是将命令打包一次性发送对整个Redis的执行不造成其它任何影响
- 批处理命令变种优化措施,类似Redis的原生批命令(mget和mset)
2.3 总结
- Pipeline与原生批量命令对比:
- 原生批命令是原子性的,pipeline 是非原子性的
- 原生批量命令一次只能执行一种命令,pipeline 支持批量执行不同命令
- 原生批命令是服务端实现,pipeline 需要服务端和客户端共同实现
- Pipeline与事务对比:
- 事务具有原子性,管道不具有原子性
- 管道一次性将多条命令发送到服务器,事务是一条一条的发,事务只有在执行 exec 命令后才会执行,管道不会
- 执行事务时会阻塞其他命令,执行管道的时候不会
- 使用 Pipeline 的注意事项:
- 管道的缓冲中的指令只会依次执行,不保证原子性,如果执行中指令发生了异常,将会继续执行后续的指令
- 使用pipeline组装的命令个数不能太多,不然数据量过大客户端阻塞的时间可能过久,同时服务端比时也被迫回复一个队列答复,占用很多内存
三、发布订阅
- 是一种消息通信模式,发送者(publish)发送消息,订阅者(subscribe)接收消息,实现进程间的消息传递
- Redis 可以实现消息中间件的功能,通过发布订阅实现消息的引导和分流
3.1 功能总结
- Redis 客户端可以订阅任意数量的频道【类似微信公众号】
- 当有新消息通过 publish 发送给 channel1 时,可以参考我们下面的这张图
- 发布/订阅其实是一个轻量的队列,只不过数据不会被持久化,一般用来处理实时性较高的异步消息
3.2 常用命令
- 通过
subscribe channel [channel ..]
命令可以订阅一个或多个频道的信息- 推荐先订阅再发布,因为订阅成功前发布的信息是收不到的
- 订阅的客户端一次可以接收由三个参数组成的消息
- 消息的种类
- 始发频道
- 消息的内容
- 通过
publish channel message
命令可以发送消息到指定的频道 - 通过
psubscribe pattern [pattern...]
命令可以按照模式批量订阅,订阅一个或多个符合给定模式(支持*号?号之类的)的频道 - 通过
pubsub subcommand [argument [argument...]]
命令可以查看订阅与发布系统状态-
通过
pubsub channels
命令可以查看由活跃频道组成的列表
-
通过
pubsub numsub [channel [channel...]]
命令可以查看某个频道有几个订阅者
-
通过
pubsub numpat
命令只会统计通过psubscribe
命令执行的,返回客户端订阅的唯一模式的数量
-
- 通过
unsubscribe [channel [channel...]]
命令可以取消订阅某个频道 - 通过
punsubscribe [pattern [pattern ...]]
命令可以退订所有给定模式的频道
3.3 总结
- Redis可以实现消息中间件MQ的功能,通过发布订阅实现消息的引导和分流 【专业人做专业事 >> 不推荐使用】
- 说一下 Pub/Sub 的缺点:
- 发布的消息在Rds系统中不能持久化,因此,必须先执行订阅,再等待消息发布。如果先发布了消息,那么该消息由于没有订阅者,消息将被直接丢弃
- 消息只管发送对于发布者而言消息是即发即失的,不管接收,也没有ACK机制,无法保证消息的消费成功
- 以上的缺点导致Redis的Pub/Sub模式就像个小玩具,在生产环境中几乎无用武之地,为此 Redis5.0版本新增了Stream数据结构,不但支持多播,还支持数据持久化,相比Pub/Sub更加的强大