Redis(五)- 异步秒杀引出消息队列

目录

Redis优化秒杀问题引出消息队列

分析Redis中执行的判断秒杀库存以及检验一人一单 两步操作:

案例演示:

总结阻塞队列依旧存在的缺点:

为解决这两个问题,我们学习下一节课:

基于Redis的消息队列实现异步秒杀

基于List结构模拟消息队列

基本解决了JDK内置的阻塞队列的两个问题:

基于PubSub的消息队列

总结:

基于Stream的消息队列

写入:把消息信息写入到消息队列

读取:读取消息队列中的消息

总结:

基于Stream的消息队列-消费者组

总结:

Redis消息队列三种方式总结:


Redis优化秒杀问题引出消息队列

模拟场景记忆:

一家餐厅,一开始只有一个小姐姐在工作,她接收到一个客户的点单之后就去后厨进行炒菜然后把菜端给客户。这是效率极低的。所以我们请来了一个后厨厨师,此时小姐姐只负责帮助客户进行点餐并且把点的是什么记录在一张纸上传递给后厨,后厨负责炒菜。并且小姐姐点餐执行的过程是极其快速的 是比后厨快速的。这也进行形成映射,小姐姐执行点餐的环境就相当于是Redis服务器,后厨执行的流程就是在Tomcat服务器与MySQL数据库之间的操作一致。

实际Redis优化秒杀场景:

如果说我们把所有的业务都交付予Tomcat服务器与数据库交互实现的话,效率会非常的低。

Redis(五)- 异步秒杀引出消息队列_第1张图片

所以说为了提升性能,我们可以在Redis服务器中进行实现判断秒杀库存与校验一人一单。然后与在Tomcat服务器中业务实现异步读取即可完成整个流程。

意思就是:

我们在Redis中进行完成判断秒杀库存与校验一人一单的工作 !在Tomcat服务器中完成其他的流程。

如图:

Redis服务器中完成对优惠券id,用户id到一个阻塞队列中,Tomcat服务器上部署的进行异步读取队列中保存的信息,完成下单的操作。

Redis(五)- 异步秒杀引出消息队列_第2张图片

分析Redis中执行的判断秒杀库存以及检验一人一单 两步操作:

把库存id以及对应的剩余数量放到set集合类型的 KEY-VALUE中

把订单id以及对应购买该商品的用户id放置到一个set集合类型中【set集合可以保证用户id不可重复,当相同的用户去买同一个id的商品时 会保存失败 意思就是不可以购买。】

如图所示:为了进行保证多条代码的原子性,我们使用的是Lua脚本。当我们使用Redis进行调用的时候就变成一句代码,就避免线程问题

Redis(五)- 异步秒杀引出消息队列_第3张图片

案例演示:

Redis(五)- 异步秒杀引出消息队列_第4张图片

Redis(五)- 异步秒杀引出消息队列_第5张图片

1.

Redis(五)- 异步秒杀引出消息队列_第6张图片

2.编写Lua脚本

我们使用的是Redis中的set数据结构进行保存,因为set是不可重复的。

Redis(五)- 异步秒杀引出消息队列_第7张图片

Redis(五)- 异步秒杀引出消息队列_第8张图片

3.执行lua脚本 基于Lua脚本,判断秒杀库存 一人一单,决定用户是否抢购成功

Redis(五)- 异步秒杀引出消息队列_第9张图片

Redis(五)- 异步秒杀引出消息队列_第10张图片

4.如果抢购成功,将优惠券id和用户id封装之后 存入到阻塞队列

Redis(五)- 异步秒杀引出消息队列_第11张图片

5.开启线程任务,不断地从阻塞队列中获取信息,实现异步下单功能

出现问题:

Redis(五)- 异步秒杀引出消息队列_第12张图片

解决方法:

Redis(五)- 异步秒杀引出消息队列_第13张图片

代码演示:

把订单执行任务提交到线程池中执行:

Redis(五)- 异步秒杀引出消息队列_第14张图片

创建成员属性,在主线程中创建出代理对象 然后给成员属性赋值

Redis(五)- 异步秒杀引出消息队列_第15张图片

代理对象执行方法:

Redis(五)- 异步秒杀引出消息队列_第16张图片

6.进行改造。

由于我们现在是异步请求,所以不用返回什么值给前端。

Redis(五)- 异步秒杀引出消息队列_第17张图片

Redis(五)- 异步秒杀引出消息队列_第18张图片

总结阻塞队列依旧存在的缺点:

1.

对于库存余量和一人一单进行判断 如果说该用户具有购买资格,那么立即结束,完成抢单业务。耗时较短,这样响应时间就变短了 对用户比较友好 !

对于耗时较长的下单操作业务,我们将下单业务放入到阻塞队列,利用独立线程进行异步下单。异步的意思为与前端解除关系,无需进行返回值给前端,并且和其他业务分开执行 提升整体效率。

Redis(五)- 异步秒杀引出消息队列_第19张图片

2.

Redis(五)- 异步秒杀引出消息队列_第20张图片

内存限制问题:

我们使用的是JDK里面的阻塞队列,这个队列使用的是JVM里面的内存。JVM的内存是有一定限度的,当高并发环境下 大量线程执行任务涌入,会进行消耗JVM内存 。为了避免大规模消耗 达到顶峰,我们要对阻塞队列进行一个初始化大小限制 。以此来进行限制阻塞队列占用JVM内存的额度,但是从另一方面来说,存在了内存限制问题。

数据安全问题:

1.当用户进行提交一个订单,但是此时服务器宕机了。那么订单会丢失

2.当我们从队列中取出一个任务将要执行,但是此时出现了一个异常或者一些事故,此时队列消失,所以说这个订单任务就再也没有机会去进行处理了。

为解决这两个问题,我们学习下一节课:

基于Redis的消息队列实现异步秒杀

模拟场景:

对于之前,快递员送快递是送货上门的,但是假设说客户正好去上班了,那么此时客户怎么办?班也得上,不回去的话 快递员又白跑一趟。效率是极低的。因此我们出现了快递柜,就相当于下面的Message Queue消息队列。快递员就相当于是生产者 ,一大早快速的把所有的快递放到快递柜中,并且这个快递柜是极其安全的。发放完毕之后,客户,也就是下面的消费者会不紧不慢的进行去取走快递异步完成对应的操作。

Redis中实际生产场景:

生产者把判断秒杀时间和库存 校验一人一单以及发生优惠券id和用户id 发送到消息队列(Message Queue)中,这个消息队列相比于JDK内置的阻塞队列具有很多好处,首先基于Redis的消息队列是不依赖于JVM的,是存储在JVM以外的,因此不会有上面的内存不够的问题发送。其次基于Redis的消息队列是数据持久化的,即使队列中的任务执行的过程中出现了异常,那么对应消息的数据也不会丢失找不到。最后说明:基于Redis的消息队列是安全的,消息队列会把消息数据信息进行保护起来 是具有安全性的。消费者可以进行接收消息然后完成下单操作。

Redis(五)- 异步秒杀引出消息队列_第21张图片

对于消息队列,有RabbitMQ等服务器,但是对于小型企业来说,我们可以进行使用Redis内置的三种方式进行实现消息队列。

Redis(五)- 异步秒杀引出消息队列_第22张图片

基于List结构模拟消息队列

Redis(五)- 异步秒杀引出消息队列_第23张图片

Redis(五)- 异步秒杀引出消息队列_第24张图片

测试:

1.从左边存入两个元素

Redis(五)- 异步秒杀引出消息队列_第25张图片

2.从右边取出两个元素,当取第三个的时候 发现没了 那么就阻塞等待

Redis(五)- 异步秒杀引出消息队列_第26张图片

基本解决了JDK内置的阻塞队列的两个问题:

1.基于Redis进行存储,因此运行内存的消耗是不消耗JVM的内存的,内存限制得到了解决。

2.一旦我们把数据存储到这里模拟的消息队列之后,我们就实现了数据的持久化,无论之后是服务器宕机还是出现异常,都不会造成数据丢失。

3.保证满足消息的有序性

缺点:

1.无法避免消息丢失

2.只支持单消费者

:当我们从消息队列中拿取出一个数据之后,还没有进行操作 就发生异常了,此时会造成消息数据丢失。我们无法再去进行获取该已经

丢失的消息数据,并且我们无法通过另外一个消费者去进行获取。

Redis(五)- 异步秒杀引出消息队列_第27张图片

基于PubSub的消息队列

PubSub。顾名思义,一个消费者可以进行订阅一个或多个channel频道,生产者向对应channel发送消息后,所有订阅者都能收到相关的消息。所以该消息队列是支持多消费者的。

Redis(五)- 异步秒杀引出消息队列_第28张图片

Redis(五)- 异步秒杀引出消息队列_第29张图片

三种通配符:

Redis(五)- 异步秒杀引出消息队列_第30张图片

总结:

优点:

采用发布订阅模型

分析:生产者进行发布消息到频道上 多个消费者可以进行订阅频道

支持多生产,多消费

分析:生产者可以进行多次发布消息 并且频道可以由多个消费者订阅。所以对应多个消费者

缺点:

1.不支持数据持久化:

2.无法避免消息丢失

分析前两点:

基于PubSub订阅式的消息队列和基于List模拟的消息队列不同,后者是对消息数据进行存储的,所以说我们可以做到数据的持久化,但是对于前者来说只是一个订阅性质,有可能说这个channel没有人订阅。结果生产者对这个channel进行发送消息,就会因为没有消费者进行接收消息从而导致消息丢失

3.消息堆积有上限,超出时数据丢失

虽然说消息不是存储在JVM内存中的,但是当消费者接收到生产者发送的消息数据时 是会先把这些消息数据存储到客户端的消息缓存中的,进行依次取出处理。但是当消息堆积达到上限之后,超出缓存所能承担最大值时 超出的数据会造成丢失

Redis(五)- 异步秒杀引出消息队列_第31张图片

基于Stream的消息队列

写入:把消息信息写入到消息队列

XADD users * name jack age 21

含义:

创建一个消息队列名为users的队列

*表示消息的唯一id由Redis自动生成 :格式为:时间戳-递增数字

后面接着的为发送到队列中的消息 一个个都是Entry类型的 [ Entry类型即是hash结构中KEY-VALUE中VALUE对应的field-value]

Redis(五)- 异步秒杀引出消息队列_第32张图片

读取:读取消息队列中的消息

XREAD COUNT 1 BLOCK 1000 STREAMS users 0 :

含义:1表示每次最多读取消息的最大数量

BLOCK 1000表示当没有消息读取时 进行阻塞 阻塞1000毫秒

STREAMS users中的users即是消息队列的名称

0即是起始id,起始id表示只返回大于该id的消息。0代表从第一个消息开始,$代表从最新的消息开始

Redis(五)- 异步秒杀引出消息队列_第33张图片

Redis(五)- 异步秒杀引出消息队列_第34张图片

注意:

当我们指定起始ID为$时,代表读取的是最新的消息。如果说我们处理一条消息的过程中,又有超过1条以上的消息到达队列,则下次获取时也只能获取到最新的一条,会出现漏读消息的问题。

Redis(五)- 异步秒杀引出消息队列_第35张图片

总结:

1.消息可回溯:

消息数据读取完之后 永久的保存在消息队列当中 。不会消失。你想要在什么时候进行读取这个消息数据 ,就在什么时候读取。

2.一个消息可以被多个消费者读取:

对于一个消息数据,可以进行多次读取,可以被多个消费者进行反复的读取

3.可以阻塞读取:

可以进行阻塞式读取,当消费者没有读取到时 设置一个阻塞时间进行等待读取

4.具有漏读的风险:

我们使用 $ 进行读取一个队列时,是表示从最新的消息开始读取。当我们在读取时 XADD插入了多条消息数据,但是对于 $ 读取来说,我们只能从插入的多条数据中的最新插入的那一条开始读取,这样就产生了漏读!

Redis(五)- 异步秒杀引出消息队列_第36张图片

基于Stream的消息队列-消费者组

消费者组:将多个消费者划分到一个组中,监听同一个队列。

消息分流:

队列中的消息会分流给组内的不同消费者,而不是重复消费,从而加快消息处理的速度

消息标示:

消费者组会进行维护一个标示,记录最后一个被处理的消息,哪怕消费者宕机重启,还会从标示之后读取消息。确保每一个消息都会被消费。

消费确认:

消费者获取信息后,信息处于pending状态,并存入一个pending-list。当处理完成之后,我们需要通过XACK来进行确认消息,标记消息为已处理,这样才会从pending-list中移除。

Redis(五)- 异步秒杀引出消息队列_第37张图片

Redis(五)- 异步秒杀引出消息队列_第38张图片

演示:

1.在一个消费者组中进行创建出两个消费者:消费者c1 和消费者c2

在一个消费组内由两个消费者分别进行消费,当进行读取消费时 依次读取出k1 v1 k2 v2。。这与谁先读有关。

因为s1队列中的消息是按照消费者的读取顺序,进行分流给c1和c2这两个消费者的。

Redis(五)- 异步秒杀引出消息队列_第39张图片

2.

消息数据读取之后会进行保存到pending-list中,此时消息处于pending状态。如果我们希望消息处理完成,那么要使用XACK指令进行确认标记该消息已处理。处理之后才会从pending-list中进行移除。

Redis(五)- 异步秒杀引出消息队列_第40张图片

补充:STREAMS s1 >表示的意思为读取的队列名称为s1 ;>表示从下一个未消费的消息数据开始读取。

3.

Redis(五)- 异步秒杀引出消息队列_第41张图片

Redis(五)- 异步秒杀引出消息队列_第42张图片

总结:

STREAM类型消息队列的XREADGROUP命令特点:

1.消息可回溯

一个消息数据被一个消费者进读取。但是当其他的消费者进行读取该消息时,依旧可以读取 并且可以反复读取。

2.可以多消费者争抢消息,加快消费速度

我们是在一个消费者组包含多个消费者进行争抢消息,这样就能加快消费速度

3.可以阻塞读取

4.没有漏读的风险

5.有消息确认的机制,保证消息至少被消费一次

Redis(五)- 异步秒杀引出消息队列_第43张图片

Redis消息队列三种方式总结:

Redis(五)- 异步秒杀引出消息队列_第44张图片

但是对于要求特别严格的互联网公司来说:基于Redis的Stream似乎不是百分百的选择。

极小情况下 可能会出现的问题:

1.基于Redis的Stream对应的消息持久化特点并不是万无一失的

2.对于消息确认机制只是对消费者进行支持,对于生产者并不支持

Redis(五)- 异步秒杀引出消息队列_第45张图片

1.

Redis(五)- 异步秒杀引出消息队列_第46张图片

2.

Redis(五)- 异步秒杀引出消息队列_第47张图片

Redis(五)- 异步秒杀引出消息队列_第48张图片

3.

Redis(五)- 异步秒杀引出消息队列_第49张图片

3.1

Redis(五)- 异步秒杀引出消息队列_第50张图片

3.2 进行循环读取消息队列中的订单信息 然后进行处理 并且进行ACK确认

Redis(五)- 异步秒杀引出消息队列_第51张图片

3.3当订单处理的过程中 发生异常时,我们要封装一个方法进行处理读取入pending-list集合中未进行ACK确认成功的消息数据

Redis(五)- 异步秒杀引出消息队列_第52张图片

你可能感兴趣的:(Redis,lua,开发语言,redis,java)