黑马点评-Redis 消息队列-01_why_redis_mq

黑马点评 Redis 消息队列一:为什么 BlockingQueue 不够,Redis 还能当 MQ?

本文整理黑马点评 Redis 实战篇第 7 章「Redis 消息队列」。

第 6 章我们已经把秒杀下单从"请求线程同步落库"优化成了"Redis 预检 + BlockingQueue 异步下单"。第 7 章继续往前走:为什么还要把本地阻塞队列换成 Redis 消息队列?

这一篇先不急着背 Redis 命令,先把"为什么需要消息队列"这件事讲清楚。


1. 这篇文章解决什么问题

学到第 7 章时,很容易产生一个疑惑:

text 复制代码
第 6 章不是已经用 BlockingQueue 实现异步下单了吗?
请求线程已经能快速返回了,后台线程也能慢慢落库了。
那为什么第 7 章还要学习 Redis 消息队列?

这个问题非常关键。

因为第 6 章解决的是:

text 复制代码
请求线程不要同步等待数据库落库。

第 7 章继续解决的是:

text 复制代码
订单任务不能只放在当前 JVM 内存里。

先给结论:

BlockingQueue 可以帮助我们理解异步下单思想,但它是 JVM 本地内存队列,存在内存限制、服务宕机丢任务、多实例不共享等问题。Redis 消息队列把订单任务放到 Redis 这种独立中间件中,让多个服务实例都能生产和消费消息,并具备更好的持久化、阻塞读取、消息确认和异常恢复能力。


2. 先复习第 6 章 BlockingQueue 做了什么

第 6 章的异步秒杀流程可以简化成:

text 复制代码
请求线程:
执行 Lua 判断库存和一人一单
    ↓
判断通过
    ↓
封装 VoucherOrder
    ↓
放入 BlockingQueue
    ↓
返回订单 id

后台线程:
从 BlockingQueue 中 take 订单任务
    ↓
执行数据库扣库存和保存订单

它的核心变化是:

text 复制代码
请求线程不再直接创建数据库订单。

这当然已经比同步下单快很多。

但是 BlockingQueue 有一个隐藏前提:

text 复制代码
队列只存在当前 Java 进程的内存里。

这个前提在学习阶段没问题,但放到更真实的高并发系统里就会暴露问题。

第 6 章链路图

#mermaid-svg-mFwrQM1cW0tOKxOB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mFwrQM1cW0tOKxOB .error-icon{fill:#552222;}#mermaid-svg-mFwrQM1cW0tOKxOB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mFwrQM1cW0tOKxOB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mFwrQM1cW0tOKxOB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mFwrQM1cW0tOKxOB .marker.cross{stroke:#333333;}#mermaid-svg-mFwrQM1cW0tOKxOB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mFwrQM1cW0tOKxOB p{margin:0;}#mermaid-svg-mFwrQM1cW0tOKxOB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mFwrQM1cW0tOKxOB .cluster-label text{fill:#333;}#mermaid-svg-mFwrQM1cW0tOKxOB .cluster-label span{color:#333;}#mermaid-svg-mFwrQM1cW0tOKxOB .cluster-label span p{background-color:transparent;}#mermaid-svg-mFwrQM1cW0tOKxOB .label text,#mermaid-svg-mFwrQM1cW0tOKxOB span{fill:#333;color:#333;}#mermaid-svg-mFwrQM1cW0tOKxOB .node rect,#mermaid-svg-mFwrQM1cW0tOKxOB .node circle,#mermaid-svg-mFwrQM1cW0tOKxOB .node ellipse,#mermaid-svg-mFwrQM1cW0tOKxOB .node polygon,#mermaid-svg-mFwrQM1cW0tOKxOB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mFwrQM1cW0tOKxOB .rough-node .label text,#mermaid-svg-mFwrQM1cW0tOKxOB .node .label text,#mermaid-svg-mFwrQM1cW0tOKxOB .image-shape .label,#mermaid-svg-mFwrQM1cW0tOKxOB .icon-shape .label{text-anchor:middle;}#mermaid-svg-mFwrQM1cW0tOKxOB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mFwrQM1cW0tOKxOB .rough-node .label,#mermaid-svg-mFwrQM1cW0tOKxOB .node .label,#mermaid-svg-mFwrQM1cW0tOKxOB .image-shape .label,#mermaid-svg-mFwrQM1cW0tOKxOB .icon-shape .label{text-align:center;}#mermaid-svg-mFwrQM1cW0tOKxOB .node.clickable{cursor:pointer;}#mermaid-svg-mFwrQM1cW0tOKxOB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mFwrQM1cW0tOKxOB .arrowheadPath{fill:#333333;}#mermaid-svg-mFwrQM1cW0tOKxOB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mFwrQM1cW0tOKxOB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mFwrQM1cW0tOKxOB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mFwrQM1cW0tOKxOB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mFwrQM1cW0tOKxOB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mFwrQM1cW0tOKxOB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mFwrQM1cW0tOKxOB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mFwrQM1cW0tOKxOB .cluster text{fill:#333;}#mermaid-svg-mFwrQM1cW0tOKxOB .cluster span{color:#333;}#mermaid-svg-mFwrQM1cW0tOKxOB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mFwrQM1cW0tOKxOB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mFwrQM1cW0tOKxOB rect.text{fill:none;stroke-width:0;}#mermaid-svg-mFwrQM1cW0tOKxOB .icon-shape,#mermaid-svg-mFwrQM1cW0tOKxOB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mFwrQM1cW0tOKxOB .icon-shape p,#mermaid-svg-mFwrQM1cW0tOKxOB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mFwrQM1cW0tOKxOB .icon-shape .label rect,#mermaid-svg-mFwrQM1cW0tOKxOB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mFwrQM1cW0tOKxOB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mFwrQM1cW0tOKxOB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mFwrQM1cW0tOKxOB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

用户秒杀请求
请求线程执行 Lua
Redis 资格判断通过?
返回失败
VoucherOrder 放入 JVM BlockingQueue
请求线程返回订单 id
当前 JVM 后台线程
take 队列任务
MySQL 扣库存并保存订单

这张图里最重要的关键词是:

text 复制代码
JVM BlockingQueue

它不是独立的消息中间件。

它只是当前服务实例内存中的一个队列对象。


3. BlockingQueue 的第一个问题:内存有限

第 6 章讲义中队列类似这样:

java 复制代码
private BlockingQueue<VoucherOrder> orderTasks =
        new ArrayBlockingQueue<>(1024 * 1024);

这说明队列是有容量上限的。

如果秒杀请求非常多,Redis 预检通过的订单任务也很多,就可能出现:

text 复制代码
队列满了
    ↓
新的订单任务放不进去
    ↓
请求线程无法正常交付任务

更关键的是:

text 复制代码
这个容量占的是 Java 进程内存。

如果队列里堆积太多任务,JVM 内存压力会变大。

所以 BlockingQueue 的异步能力不是无限的。

它只是把请求线程和后台线程解耦了,但没有从根上解决任务存储可靠性问题。


4. BlockingQueue 的第二个问题:服务宕机会丢任务

这是订单业务里更严重的问题。

假设一个请求已经通过 Lua:

text 复制代码
Redis 库存已经扣减
Redis 已下单 Set 已经记录 userId
订单任务已经放入 BlockingQueue
请求线程已经返回订单 id

但后台线程还没来得及落库。

这时服务进程突然宕机。

会发生什么?

text 复制代码
BlockingQueue 在 JVM 内存里
JVM 进程没了
队列里的订单任务也没了

结果就是:

text 复制代码
用户看起来抢到了
Redis 里也记录了资格
但 MySQL 订单可能永远不会创建

这就是本地内存队列的致命短板。

宕机丢任务示意图

MySQL BlockingQueue Java 服务 用户 MySQL BlockingQueue Java 服务 用户 #mermaid-svg-LT7OUZy0mxbUeoZr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LT7OUZy0mxbUeoZr .error-icon{fill:#552222;}#mermaid-svg-LT7OUZy0mxbUeoZr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LT7OUZy0mxbUeoZr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LT7OUZy0mxbUeoZr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LT7OUZy0mxbUeoZr .marker.cross{stroke:#333333;}#mermaid-svg-LT7OUZy0mxbUeoZr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LT7OUZy0mxbUeoZr p{margin:0;}#mermaid-svg-LT7OUZy0mxbUeoZr .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LT7OUZy0mxbUeoZr text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-LT7OUZy0mxbUeoZr .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LT7OUZy0mxbUeoZr .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-LT7OUZy0mxbUeoZr .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-LT7OUZy0mxbUeoZr .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-LT7OUZy0mxbUeoZr #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-LT7OUZy0mxbUeoZr .sequenceNumber{fill:white;}#mermaid-svg-LT7OUZy0mxbUeoZr #sequencenumber{fill:#333;}#mermaid-svg-LT7OUZy0mxbUeoZr #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-LT7OUZy0mxbUeoZr .messageText{fill:#333;stroke:none;}#mermaid-svg-LT7OUZy0mxbUeoZr .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LT7OUZy0mxbUeoZr .labelText,#mermaid-svg-LT7OUZy0mxbUeoZr .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-LT7OUZy0mxbUeoZr .loopText,#mermaid-svg-LT7OUZy0mxbUeoZr .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-LT7OUZy0mxbUeoZr .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LT7OUZy0mxbUeoZr .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-LT7OUZy0mxbUeoZr .noteText,#mermaid-svg-LT7OUZy0mxbUeoZr .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-LT7OUZy0mxbUeoZr .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LT7OUZy0mxbUeoZr .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LT7OUZy0mxbUeoZr .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LT7OUZy0mxbUeoZr .actorPopupMenu{position:absolute;}#mermaid-svg-LT7OUZy0mxbUeoZr .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-LT7OUZy0mxbUeoZr .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LT7OUZy0mxbUeoZr .actor-man circle,#mermaid-svg-LT7OUZy0mxbUeoZr line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-LT7OUZy0mxbUeoZr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 秒杀请求Lua 判断通过放入订单任务返回订单 id服务宕机队列任务丢失,无法落库

如果订单任务存到 Redis、RabbitMQ、Kafka 这类独立中间件里,即使某个 Java 服务宕机,消息仍然有机会被其他消费者继续处理。

这就是消息队列从"本地队列"升级成"独立中间件"的价值。


5. BlockingQueue 的第三个问题:多实例不共享

真实项目通常不会只部署一个 Java 服务。

可能是:

text 复制代码
Nginx
  ├── Tomcat A
  ├── Tomcat B
  └── Tomcat C

如果每个 Tomcat 内部都有一个 BlockingQueue,那么它们是三份不同的队列:

text 复制代码
Tomcat A 的 BlockingQueue
Tomcat B 的 BlockingQueue
Tomcat C 的 BlockingQueue

这些队列互相看不见。

Tomcat A 放进去的订单任务,Tomcat B 的后台线程消费不到。

这会带来几个问题:

text 复制代码
1. 任务分散在不同 JVM 中,统一管理困难。
2. 某个实例宕机,只会丢该实例内存中的任务。
3. 后台消费能力无法天然统一调度。

而 Redis 消息队列是共享的:

text 复制代码
Tomcat A、B、C 都往同一个 Redis Stream 写消息。
多个消费者也从同一个 Stream 读消息。

本地队列 vs Redis 队列

#mermaid-svg-whYmdSraV234kj7S{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-whYmdSraV234kj7S .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-whYmdSraV234kj7S .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-whYmdSraV234kj7S .error-icon{fill:#552222;}#mermaid-svg-whYmdSraV234kj7S .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-whYmdSraV234kj7S .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-whYmdSraV234kj7S .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-whYmdSraV234kj7S .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-whYmdSraV234kj7S .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-whYmdSraV234kj7S .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-whYmdSraV234kj7S .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-whYmdSraV234kj7S .marker{fill:#333333;stroke:#333333;}#mermaid-svg-whYmdSraV234kj7S .marker.cross{stroke:#333333;}#mermaid-svg-whYmdSraV234kj7S svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-whYmdSraV234kj7S p{margin:0;}#mermaid-svg-whYmdSraV234kj7S .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-whYmdSraV234kj7S .cluster-label text{fill:#333;}#mermaid-svg-whYmdSraV234kj7S .cluster-label span{color:#333;}#mermaid-svg-whYmdSraV234kj7S .cluster-label span p{background-color:transparent;}#mermaid-svg-whYmdSraV234kj7S .label text,#mermaid-svg-whYmdSraV234kj7S span{fill:#333;color:#333;}#mermaid-svg-whYmdSraV234kj7S .node rect,#mermaid-svg-whYmdSraV234kj7S .node circle,#mermaid-svg-whYmdSraV234kj7S .node ellipse,#mermaid-svg-whYmdSraV234kj7S .node polygon,#mermaid-svg-whYmdSraV234kj7S .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-whYmdSraV234kj7S .rough-node .label text,#mermaid-svg-whYmdSraV234kj7S .node .label text,#mermaid-svg-whYmdSraV234kj7S .image-shape .label,#mermaid-svg-whYmdSraV234kj7S .icon-shape .label{text-anchor:middle;}#mermaid-svg-whYmdSraV234kj7S .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-whYmdSraV234kj7S .rough-node .label,#mermaid-svg-whYmdSraV234kj7S .node .label,#mermaid-svg-whYmdSraV234kj7S .image-shape .label,#mermaid-svg-whYmdSraV234kj7S .icon-shape .label{text-align:center;}#mermaid-svg-whYmdSraV234kj7S .node.clickable{cursor:pointer;}#mermaid-svg-whYmdSraV234kj7S .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-whYmdSraV234kj7S .arrowheadPath{fill:#333333;}#mermaid-svg-whYmdSraV234kj7S .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-whYmdSraV234kj7S .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-whYmdSraV234kj7S .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-whYmdSraV234kj7S .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-whYmdSraV234kj7S .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-whYmdSraV234kj7S .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-whYmdSraV234kj7S .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-whYmdSraV234kj7S .cluster text{fill:#333;}#mermaid-svg-whYmdSraV234kj7S .cluster span{color:#333;}#mermaid-svg-whYmdSraV234kj7S div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-whYmdSraV234kj7S .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-whYmdSraV234kj7S rect.text{fill:none;stroke-width:0;}#mermaid-svg-whYmdSraV234kj7S .icon-shape,#mermaid-svg-whYmdSraV234kj7S .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-whYmdSraV234kj7S .icon-shape p,#mermaid-svg-whYmdSraV234kj7S .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-whYmdSraV234kj7S .icon-shape .label rect,#mermaid-svg-whYmdSraV234kj7S .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-whYmdSraV234kj7S .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-whYmdSraV234kj7S .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-whYmdSraV234kj7S :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Redis 消息队列
Tomcat A
Redis Stream
Tomcat B
Tomcat C
消费者 1
消费者 2
本地 BlockingQueue
Tomcat A
Queue A
Tomcat B
Queue B
Tomcat C
Queue C

这就是第 7 章要做的事:

text 复制代码
把订单任务从 JVM 本地队列,挪到 Redis 中的共享消息队列。

6. 什么是消息队列

讲义中对消息队列的定义很直白:

text 复制代码
消息队列就是存放消息的队列。

最简单的消息队列模型包含三个角色:

text 复制代码
1. 生产者:发送消息的人。
2. 消息队列:保存和管理消息的地方。
3. 消费者:从队列中取消息并处理的人。

放到秒杀业务里:

text 复制代码
生产者:秒杀请求线程,或者 Lua 脚本
消息队列:Redis Stream
消费者:后台下单线程
消息:订单任务,包含 userId、voucherId、orderId

秒杀中的 MQ 模型

#mermaid-svg-5okmxdfLtrCYd3rX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5okmxdfLtrCYd3rX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5okmxdfLtrCYd3rX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5okmxdfLtrCYd3rX .error-icon{fill:#552222;}#mermaid-svg-5okmxdfLtrCYd3rX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5okmxdfLtrCYd3rX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5okmxdfLtrCYd3rX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5okmxdfLtrCYd3rX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5okmxdfLtrCYd3rX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5okmxdfLtrCYd3rX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5okmxdfLtrCYd3rX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5okmxdfLtrCYd3rX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5okmxdfLtrCYd3rX .marker.cross{stroke:#333333;}#mermaid-svg-5okmxdfLtrCYd3rX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5okmxdfLtrCYd3rX p{margin:0;}#mermaid-svg-5okmxdfLtrCYd3rX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5okmxdfLtrCYd3rX .cluster-label text{fill:#333;}#mermaid-svg-5okmxdfLtrCYd3rX .cluster-label span{color:#333;}#mermaid-svg-5okmxdfLtrCYd3rX .cluster-label span p{background-color:transparent;}#mermaid-svg-5okmxdfLtrCYd3rX .label text,#mermaid-svg-5okmxdfLtrCYd3rX span{fill:#333;color:#333;}#mermaid-svg-5okmxdfLtrCYd3rX .node rect,#mermaid-svg-5okmxdfLtrCYd3rX .node circle,#mermaid-svg-5okmxdfLtrCYd3rX .node ellipse,#mermaid-svg-5okmxdfLtrCYd3rX .node polygon,#mermaid-svg-5okmxdfLtrCYd3rX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5okmxdfLtrCYd3rX .rough-node .label text,#mermaid-svg-5okmxdfLtrCYd3rX .node .label text,#mermaid-svg-5okmxdfLtrCYd3rX .image-shape .label,#mermaid-svg-5okmxdfLtrCYd3rX .icon-shape .label{text-anchor:middle;}#mermaid-svg-5okmxdfLtrCYd3rX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5okmxdfLtrCYd3rX .rough-node .label,#mermaid-svg-5okmxdfLtrCYd3rX .node .label,#mermaid-svg-5okmxdfLtrCYd3rX .image-shape .label,#mermaid-svg-5okmxdfLtrCYd3rX .icon-shape .label{text-align:center;}#mermaid-svg-5okmxdfLtrCYd3rX .node.clickable{cursor:pointer;}#mermaid-svg-5okmxdfLtrCYd3rX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5okmxdfLtrCYd3rX .arrowheadPath{fill:#333333;}#mermaid-svg-5okmxdfLtrCYd3rX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5okmxdfLtrCYd3rX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5okmxdfLtrCYd3rX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5okmxdfLtrCYd3rX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5okmxdfLtrCYd3rX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5okmxdfLtrCYd3rX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5okmxdfLtrCYd3rX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5okmxdfLtrCYd3rX .cluster text{fill:#333;}#mermaid-svg-5okmxdfLtrCYd3rX .cluster span{color:#333;}#mermaid-svg-5okmxdfLtrCYd3rX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5okmxdfLtrCYd3rX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5okmxdfLtrCYd3rX rect.text{fill:none;stroke-width:0;}#mermaid-svg-5okmxdfLtrCYd3rX .icon-shape,#mermaid-svg-5okmxdfLtrCYd3rX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5okmxdfLtrCYd3rX .icon-shape p,#mermaid-svg-5okmxdfLtrCYd3rX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5okmxdfLtrCYd3rX .icon-shape .label rect,#mermaid-svg-5okmxdfLtrCYd3rX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5okmxdfLtrCYd3rX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5okmxdfLtrCYd3rX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5okmxdfLtrCYd3rX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生产者:秒杀请求线程 / Lua
消息队列:stream.orders
消费者:后台下单线程
MySQL:扣库存并保存订单

消息队列的作用不是让业务消失。

它只是把业务从:

text 复制代码
请求线程马上做

改成:

text 复制代码
先发消息,后台消费者稍后做

7. 消息队列的三个核心价值

讲消息队列时,经常会听到三个词:

text 复制代码
解耦
异步
削峰

这三个词如果只背,很空。

放回秒杀业务里就很好理解。

价值 1:解耦

没有消息队列时,请求线程必须知道:

text 复制代码
怎么扣库存
怎么保存订单
异常怎么处理

引入消息队列后,请求线程只需要:

text 复制代码
把订单消息发出去

后台消费者负责:

text 复制代码
拿到消息后落库

生产者和消费者通过队列连接,不再直接调用。

这就是解耦。

价值 2:异步

没有消息队列时:

text 复制代码
请求线程必须等数据库下单完成才能返回。

引入消息队列后:

text 复制代码
请求线程发完消息就可以返回。
后台消费者慢慢处理数据库落库。

这就是异步。

价值 3:削峰

秒杀请求可能一瞬间进来很多。

如果所有请求都直接打数据库,数据库会被瞬时流量冲击。

消息队列可以把瞬时流量先接住:

text 复制代码
前端瞬间来了 1 万个请求
Redis 快速判断资格并写入消息队列
后台消费者按自己的速度慢慢处理

这就是削峰。

它不是让总工作量减少。

而是把瞬时压力摊平。

三个价值示意图

#mermaid-svg-5aBnyNAo72swOh7F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5aBnyNAo72swOh7F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5aBnyNAo72swOh7F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5aBnyNAo72swOh7F .error-icon{fill:#552222;}#mermaid-svg-5aBnyNAo72swOh7F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5aBnyNAo72swOh7F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5aBnyNAo72swOh7F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5aBnyNAo72swOh7F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5aBnyNAo72swOh7F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5aBnyNAo72swOh7F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5aBnyNAo72swOh7F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5aBnyNAo72swOh7F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5aBnyNAo72swOh7F .marker.cross{stroke:#333333;}#mermaid-svg-5aBnyNAo72swOh7F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5aBnyNAo72swOh7F p{margin:0;}#mermaid-svg-5aBnyNAo72swOh7F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5aBnyNAo72swOh7F .cluster-label text{fill:#333;}#mermaid-svg-5aBnyNAo72swOh7F .cluster-label span{color:#333;}#mermaid-svg-5aBnyNAo72swOh7F .cluster-label span p{background-color:transparent;}#mermaid-svg-5aBnyNAo72swOh7F .label text,#mermaid-svg-5aBnyNAo72swOh7F span{fill:#333;color:#333;}#mermaid-svg-5aBnyNAo72swOh7F .node rect,#mermaid-svg-5aBnyNAo72swOh7F .node circle,#mermaid-svg-5aBnyNAo72swOh7F .node ellipse,#mermaid-svg-5aBnyNAo72swOh7F .node polygon,#mermaid-svg-5aBnyNAo72swOh7F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5aBnyNAo72swOh7F .rough-node .label text,#mermaid-svg-5aBnyNAo72swOh7F .node .label text,#mermaid-svg-5aBnyNAo72swOh7F .image-shape .label,#mermaid-svg-5aBnyNAo72swOh7F .icon-shape .label{text-anchor:middle;}#mermaid-svg-5aBnyNAo72swOh7F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5aBnyNAo72swOh7F .rough-node .label,#mermaid-svg-5aBnyNAo72swOh7F .node .label,#mermaid-svg-5aBnyNAo72swOh7F .image-shape .label,#mermaid-svg-5aBnyNAo72swOh7F .icon-shape .label{text-align:center;}#mermaid-svg-5aBnyNAo72swOh7F .node.clickable{cursor:pointer;}#mermaid-svg-5aBnyNAo72swOh7F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5aBnyNAo72swOh7F .arrowheadPath{fill:#333333;}#mermaid-svg-5aBnyNAo72swOh7F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5aBnyNAo72swOh7F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5aBnyNAo72swOh7F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5aBnyNAo72swOh7F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5aBnyNAo72swOh7F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5aBnyNAo72swOh7F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5aBnyNAo72swOh7F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5aBnyNAo72swOh7F .cluster text{fill:#333;}#mermaid-svg-5aBnyNAo72swOh7F .cluster span{color:#333;}#mermaid-svg-5aBnyNAo72swOh7F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5aBnyNAo72swOh7F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5aBnyNAo72swOh7F rect.text{fill:none;stroke-width:0;}#mermaid-svg-5aBnyNAo72swOh7F .icon-shape,#mermaid-svg-5aBnyNAo72swOh7F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5aBnyNAo72swOh7F .icon-shape p,#mermaid-svg-5aBnyNAo72swOh7F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5aBnyNAo72swOh7F .icon-shape .label rect,#mermaid-svg-5aBnyNAo72swOh7F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5aBnyNAo72swOh7F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5aBnyNAo72swOh7F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5aBnyNAo72swOh7F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 高并发秒杀请求
Redis 资格判断
消息队列暂存订单消息
后台消费者按能力消费
MySQL 落库
解耦:生产者不直接调用消费者
异步:请求线程不等待落库
削峰:瞬时流量变成队列积压


8. 为什么这里不用 Kafka、RabbitMQ,而先学 Redis MQ

真实项目里,消息队列可以用很多成熟组件:

text 复制代码
RabbitMQ
Kafka
RocketMQ
Redis Stream

黑马点评这里选择 Redis 消息队列,有几个原因:

text 复制代码
1. 项目里已经引入 Redis,不需要额外部署新中间件。
2. 当前业务是学习型秒杀场景,Redis Stream 足够演示消息队列核心机制。
3. Redis Stream 支持阻塞读取、消费者组、ACK、pending-list,能比 List 和 PubSub 更接近真正 MQ。

但要注意:

text 复制代码
Redis Stream 能当消息队列用,不代表它在所有场景都能替代专业 MQ。

如果是大型业务、高可靠消息、复杂路由、海量日志流、跨系统事件治理,还是可能选择 RabbitMQ、Kafka、RocketMQ 这类专门 MQ。

黑马点评第 7 章的目标更聚焦:

text 复制代码
在不引入新中间件的情况下,用 Redis Stream 改造第 6 章本地阻塞队列。

9. 第 7 章的演进路线

第 7 章不是一上来直接用 Stream。

讲义按这个顺序讲:

text 复制代码
1. 认识消息队列
2. List 模拟消息队列
3. PubSub 发布订阅
4. Stream 普通读写
5. Stream 消费者组
6. Stream 改造异步秒杀下单

这个顺序是很合理的。

因为它在回答一个连续问题:

text 复制代码
Redis 能不能当 MQ?
如果能,用哪个数据结构最合适?

List 可以当队列,但能力不完整。

PubSub 可以广播消息,但可靠性不足。

Stream 才更接近真正消息队列。

演进路线图

#mermaid-svg-MkuzvsDxfHQHRKeJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MkuzvsDxfHQHRKeJ .error-icon{fill:#552222;}#mermaid-svg-MkuzvsDxfHQHRKeJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MkuzvsDxfHQHRKeJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .marker.cross{stroke:#333333;}#mermaid-svg-MkuzvsDxfHQHRKeJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MkuzvsDxfHQHRKeJ p{margin:0;}#mermaid-svg-MkuzvsDxfHQHRKeJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .cluster-label text{fill:#333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .cluster-label span{color:#333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .cluster-label span p{background-color:transparent;}#mermaid-svg-MkuzvsDxfHQHRKeJ .label text,#mermaid-svg-MkuzvsDxfHQHRKeJ span{fill:#333;color:#333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .node rect,#mermaid-svg-MkuzvsDxfHQHRKeJ .node circle,#mermaid-svg-MkuzvsDxfHQHRKeJ .node ellipse,#mermaid-svg-MkuzvsDxfHQHRKeJ .node polygon,#mermaid-svg-MkuzvsDxfHQHRKeJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MkuzvsDxfHQHRKeJ .rough-node .label text,#mermaid-svg-MkuzvsDxfHQHRKeJ .node .label text,#mermaid-svg-MkuzvsDxfHQHRKeJ .image-shape .label,#mermaid-svg-MkuzvsDxfHQHRKeJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-MkuzvsDxfHQHRKeJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MkuzvsDxfHQHRKeJ .rough-node .label,#mermaid-svg-MkuzvsDxfHQHRKeJ .node .label,#mermaid-svg-MkuzvsDxfHQHRKeJ .image-shape .label,#mermaid-svg-MkuzvsDxfHQHRKeJ .icon-shape .label{text-align:center;}#mermaid-svg-MkuzvsDxfHQHRKeJ .node.clickable{cursor:pointer;}#mermaid-svg-MkuzvsDxfHQHRKeJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .arrowheadPath{fill:#333333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MkuzvsDxfHQHRKeJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MkuzvsDxfHQHRKeJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MkuzvsDxfHQHRKeJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MkuzvsDxfHQHRKeJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MkuzvsDxfHQHRKeJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MkuzvsDxfHQHRKeJ .cluster text{fill:#333;}#mermaid-svg-MkuzvsDxfHQHRKeJ .cluster span{color:#333;}#mermaid-svg-MkuzvsDxfHQHRKeJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MkuzvsDxfHQHRKeJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MkuzvsDxfHQHRKeJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-MkuzvsDxfHQHRKeJ .icon-shape,#mermaid-svg-MkuzvsDxfHQHRKeJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MkuzvsDxfHQHRKeJ .icon-shape p,#mermaid-svg-MkuzvsDxfHQHRKeJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MkuzvsDxfHQHRKeJ .icon-shape .label rect,#mermaid-svg-MkuzvsDxfHQHRKeJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MkuzvsDxfHQHRKeJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MkuzvsDxfHQHRKeJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MkuzvsDxfHQHRKeJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} BlockingQueue
Redis List
Redis PubSub
Redis Stream
Stream 消费者组
秒杀异步下单最终版

这一章真正要记住的是:

Redis 消息队列不是一个命令,而是一组能力的逐步补齐:存消息、阻塞读、多消费者、消息确认、异常恢复。


10. 本篇最容易混淆的几个点

1. 消息队列是不是为了让下单逻辑不执行

不是。

下单逻辑仍然要执行。

只是从请求线程执行,变成后台消费者执行。

2. BlockingQueue 算不算消息队列

从思想上算。

它也有生产者、队列、消费者。

但它是 JVM 本地内存队列,不适合作为更可靠的分布式消息队列。

3. Redis 消息队列是不是一定比专业 MQ 好

不是。

Redis Stream 是轻量方案,适合项目已经有 Redis、业务规模不复杂、希望降低部署成本的场景。

专业 MQ 在消息可靠性、生态、运维能力、复杂路由等方面通常更成熟。

4. 削峰是不是减少了数据库工作量

不是。

削峰不是减少总量,而是把瞬时高峰变成后端可承受的消费速度。


11. 面试怎么回答

如果面试官问:为什么第 6 章已经用了 BlockingQueue,第 7 章还要换成 Redis Stream?

可以这样回答:

BlockingQueue 是 JVM 本地内存队列,虽然能实现请求线程和后台线程解耦,但存在内存容量限制、服务宕机丢任务、多实例部署时队列不共享等问题。Redis Stream 是 Redis 提供的消息队列结构,消息存储在 Redis 中,多个服务实例都能访问同一个队列,并且支持阻塞读取、消费者组、ACK 和 pending-list,更适合承接异步秒杀订单任务。

如果面试官问:消息队列在秒杀业务里起什么作用?

可以这样回答:

在秒杀业务里,消息队列用于把请求线程和数据库落库线程解耦。请求线程只负责执行 Redis Lua 脚本判断库存和一人一单,判断通过后把订单消息写入队列并快速返回订单 id。后台消费者再从队列读取订单消息,执行数据库扣库存和保存订单。这样可以提高接口响应速度,并把瞬时高并发流量削成后台可控的消费速度。


12. 总结

这一篇的主线是:

text 复制代码
第 6 章 BlockingQueue 能讲通异步思想
    ↓
但它是 JVM 本地内存队列
    ↓
存在内存限制、宕机丢任务、多实例不共享
    ↓
所以第 7 章引入 Redis 消息队列
    ↓
用 Redis Stream 承接秒杀订单消息

最重要的是记住:

消息队列不是为了让订单不落库,而是为了让请求线程不直接等落库;订单任务先进入队列,后台消费者再按自己的节奏处理。

下一篇继续看 Redis 里最容易想到的两个方案:

text 复制代码
List 和 PubSub 为什么看起来能做 MQ,但最终都不是秒杀下单的最佳答案?
相关推荐
烧饼Fighting1 小时前
Jenkins自动化编译部署Spring Boot项目
spring boot·自动化·jenkins
oradh1 小时前
Oracle数据库扩展区(extent)概述
数据库·oracle·oracle基础·oracle数据库扩展区概述
IT策士2 小时前
Redis 从入门到精通:初识 Redis
数据库·redis·缓存
IT策士2 小时前
Redis 从入门到精通:数据结构Hash 与 List
数据结构·redis·哈希算法
CodeSheep程序羊2 小时前
宇树科技,即将上市!
java·c语言·c++·人工智能·python·科技·硬件工程
白露与泡影2 小时前
Java 8老系统旁路接入AI Gateway:不升级JDK也能用AI
java·人工智能·gateway
Misnearch2 小时前
Java中创建Map的做法
java·hashmap
scan7242 小时前
从runtime获取信息
java·服务器·前端
不剪发的Tony老师2 小时前
DBHub:一款免费开源的数据库MCP服务器
数据库·mcp