消息队列高手课
李玥
京东零售技术架构部资深架构师
立即订阅
8426 人已学习
课程目录
已完结 41 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词 | 优秀的程序员,你的技术栈中不能只有“增删改查”
免费
预习 | 怎样更好地学习这门课?
基础篇 (8讲)
01 | 为什么需要消息队列?
02 | 该如何选择消息队列?
03 | 消息模型:主题和队列有什么区别?
04 | 如何利用事务消息实现分布式事务?
05 | 如何确保消息不会丢失?
06 | 如何处理消费过程中的重复消息?
07 | 消息积压了该如何处理?
08 | 答疑解惑(一) : 网关如何接收服务端的秒杀结果?
进阶篇 (21讲)
09 | 学习开源代码该如何入手?
10 | 如何使用异步设计提升系统性能?
11 | 如何实现高性能的异步网络传输?
12 | 序列化与反序列化:如何通过网络传输结构化的数据?
13 | 传输协议:应用程序之间对话的语言
14 | 内存管理:如何避免内存溢出和频繁的垃圾回收?
加餐 | JMQ的Broker是如何异步处理消息的?
15 | Kafka如何实现高性能IO?
16 | 缓存策略:如何使用缓存来减少磁盘IO?
17 | 如何正确使用锁保护共享数据,协调异步线程?
18 | 如何用硬件同步原语(CAS)替代锁?
19 | 数据压缩:时间换空间的游戏
20 | RocketMQ Producer源码分析:消息生产的实现过程
21 | Kafka Consumer源码分析:消息消费的实现过程
22 | Kafka和RocketMQ的消息复制实现的差异点在哪?
23 | RocketMQ客户端如何在集群中找到正确的节点?
24 | Kafka的协调服务ZooKeeper:实现分布式系统的“瑞士军刀”
25 | RocketMQ与Kafka中如何实现事务?
26 | MQTT协议:如何支持海量的在线IoT设备?
27 | Pulsar的存储计算分离设计:全新的消息队列设计思路
28 | 答疑解惑(二):我的100元哪儿去了?
案例篇 (7讲)
29 | 流计算与消息(一):通过Flink理解流计算的原理
30 | 流计算与消息(二):在流计算中使用Kafka链接计算任务
31 | 动手实现一个简单的RPC框架(一):原理和程序的结构
32 | 动手实现一个简单的RPC框架(二):通信与序列化
33 | 动手实现一个简单的RPC框架(三):客户端
34 | 动手实现一个简单的RPC框架(四):服务端
35 | 答疑解惑(三):主流消息队列都是如何存储消息的?
测试篇 (2讲)
期中测试丨10个消息队列热点问题自测
免费
期末测试 | 消息队列100分试卷等你来挑战!
结束语 (1讲)
结束语 | 程序员如何构建知识体系?
消息队列高手课
登录|注册

08 | 答疑解惑(一) : 网关如何接收服务端的秒杀结果?

李玥 2019-08-08
你好,我是李玥。
我们的“消息队列高手课”专栏自从上线到现在,同学们的学习热情和参与度都非常高。每一节课都有很多同学留言评论,这些留言里有总结知识分享收获的,有提出精彩问题的,还有给自己加油打气立 Flag 的,竟然还有说老师长得像黄渤的。我又仔细去看了一下配图,还是真挺像的。下次老师和极客时间的设计师小姐姐说一样,让她们照着吴彦祖来 P 图。
同学们每一条的留言我都认真看过,大部分留言我都给出了回复。在基础篇的最后一节课,我来统一解答一下大家都比较关注的一些问题。

1. 网关如何接收服务端的秒杀结果?

在《01 | 为什么需要消息队列?》这节课里面,我们举了一个秒杀的例子,这个例子是用来说明消息队列是如何来实现异步处理的。课后很多同学留言提问,网关在发送消息之后,是如何来接收后端服务的秒杀结果,又如何来给 APP 返回响应的呢?
在解答这个问题之前,我需要先说一下,实际生产环境中的秒杀系统,远比我们举的这个例子复杂得多,实现方案也是多种多样的,不是说一定要按照我们这个例子的方式来实现。
在这个例子中,网关接收后端服务秒杀结果,实现的方式也不只一种,这里我给大家提供一个比较简单的方案。
比如说,用 Java 语言来举例子:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《消息队列高手课》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(58)

  • 大龄小学生 置顶
    老师,一图胜千言,来点图吧。

    作者回复: 你要的图来了。我在文中补充了一个流程图,便于同学们理解。

    2019-08-08
    2
    12
  • linqw
    学习完网关答疑篇,写下自己的理解和疑惑,老师有空帮忙看下哦
    1、秒杀的理解:
    APP--发送秒杀请求--》网关(也是RPC服务端,和配置中心保持长连接,比如nacos,将其路由和配置信息定时的发送给配置中心,配置中心对其进行管理,定时的清除宕机的网关路由信息,如超过一定时间没有接收到网关的心跳包)--》将其APP请求做一定的封装,增加网关id和网关实例中唯一的请求id发送给消息队列,为了保证消息不丢失,网关对其发送消息出现的异常进行处理,如超时异常,直接返回秒杀失败,网关发送消息的这个过程中可能涉及到分布式事务,使用消息队列的分布式事务进行处理,然后网关需要等待一段时间,等待秒杀服务端使用RPC调用网关实例的接收秒杀结果,为此创建一个新对象,将其请求id做为key,新对象做为value放入CurrentMap中,调用新对象的超时wait方法进行等待秒杀结果--发送封装的APP请求,包含网关id和请求id--》消息队列接收APP请求消息,为了保证消息不丢失,开启Sync_Flush参数将消息保存到磁盘,并且为了防止一台机器磁盘出问题,集群需要2台机器都有消息才确认请求--从消息队列中拉取消息--》秒杀服务端,为了低延迟执行风控、预占库存,拿到消息中网关id,从本地路由中查询网关id的实例信息,如果获取不到调用网关实例时,需先从配置中心获取到网关的路由信息,秒杀服务端也需和配置中心保持长连接,定时的从配置中心拉取网关的路由信息,保存到本地,使用RPC调用网关实例的接收秒杀结果的方法,为了保证消息不丢失,先执行消费逻辑,再响应消息队列,如果根据网关id获取不到网关实例,或者确认消息队列超时或出现异常,秒杀服务端回滚事务,此过程也涉及到分布式事务,为了防止消费重复消息,接口的幂等性,将请求id和网关id做为唯一键。也为了防止消息积压,消息队列中的主题队列和消费组中的消费者一一对应,保证消息被快速消费。
    2、秒杀异步,APP发送请求给网关,网关接收请求后将请求做一定的封装(包括请求id,网关id,账户id),然后发送到消息队列中,响应APP请求,无需等待后需的流程,然后秒杀成功以否直接返回,后续流程处理完使用短信的形式告知用户是否秒杀成功,不知道这样做法是否可行。
    3、最近在撸rocketmq的源码,搞了namesrv、logging、logappend模块,想成为commiter,立个flag,等后续JMQ出来,撸其源码,也想成为commiter,道阻且长,持续进化。

    作者回复: 我认真的看了同学的对于秒杀的理解,技术上都没什么问题。

    从业务角度,老师有一些不同的看法。

    对于秒杀这种场景,宏观上的设计应该是倾向于利用有限的资源处理短时间内海量的请求,保证服务不宕机。有少量请求处理出错(注意是后端错误,用户不可见)或消息丢失,是可以接受的。

    毕竟秒杀拼的就是运气,某个用户秒杀请求在处理的时候丢失,和处理成功但没秒到,对于用户来说都是运气不好而已。

    基于这样的设计理念,很多保证数据可靠性的做法都可以牺牲掉,用于换取系统更大的吞吐量比较划算。

    2019-08-08
    1
    14
  • 滴流乱转小胖子
    mq界,吴彦祖老师你好,感谢分享

    作者回复: 谢谢你,蔡徐坤同学。

    2019-08-08
    1
    9
  • Abyssal
    七夕不过节,继续学习 —— 谁让我是单身狗呢
    2019-08-08
    1
    8
  • 摩云神雕
    请教下老师,topic的partition数 是根据什么确定的?

    我理解partition的设置 是为了一个消费组中多个消费者并行消费的,
    那么partition数根据什么设定呢? 根据消费者数 和 broker机器的性能吗

    假设我现在的kafka有 3个broker节点,
    创建了一个topic, partition值设为5,
    然后我的一个消费组中有5个消费者, 正好一个消费者 消费一个partition,

    后续, 这个消费组 我又想加2个消费者,
    呢partition数 也调成7 是吧?

    但是, 我这个topic 可能被好几个消费组消费的,
    你消费组A扩展了消费者数, 想扩展partition数,
    但是 我消费组B没这个需求啊, 怎么办?

    这个topic是消费组A、B、C、D大家共同订阅的,
    就因为 你A组加了几个消费者,
    我就去改这个topic的partition数吗? 这里没太想通;

    另外,在生产环境中 改了partition数,会造成什么影响?

    作者回复: 多个消费组的时候,确实有你说的问题,partition数量需要兼顾所有消费者。一般的做法都是照顾消费最慢的那个消费组,按照它的速度和消费者数量来确定partition数量。

    大部分MQ都支持动态扩容,增加分区数量,分区数量变更后,会重新分配消费者和分区对应关系,对生产基本没什么影响。

    2019-09-20
    1
    5
  • 虢国技匠
    "同一个消费组内,每个队列只能被一个消费者实例占用"
    我在想:如果队列只有两个Q1和Q2,但是G1中有三个消费者实例C1,C2和C3;那会不会有一个消费者实例一直在偷懒(没有干活的机会,😂)。
    对于这种情况会报错?还是说内部会有机制队列的占用是换着分配给消费者实例的(不是某个实例一直占用)?

    作者回复: 是的,这时候就会有一个消费者没有干活的机会。

    至于,如何分配队列与消费者的关系,不同的消息队列处理也不一样,有的消息队列是绑定队列与消费者,这样有一个消费者一直闲着,其它二个一直干活。

    也有的消息队列是分时绑定,也就是你干一会儿,我干一会儿,但任何时刻,都会有一个消费者处于闲着的状态。

    2019-09-05
    5
  • 木木木
    关于有序性还有疑惑,即使采用了一致性hash,无论扩容还是缩容队列,对分配相邻队列的用户部分还是有影响的,难道要等这些队列消费完了,阻止生产者发消息吗?感觉不具有可操作性

    作者回复: 扩容后只需要等一会儿,确保扩容之前的消息都消费完成了(不确定的话可以等久一点儿也没关系)再消费新分区的数据就可以了,生产不需要停。

    因为一致性哈希可以保证单调性:如果已经有一些内容通过哈希分派到了相应的分区中,又有新的分区加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的分区中去,而不会被映射到旧的分区集合中的其他分区

    2019-08-20
    2
  • Liam
    秒杀这个案例中,超时之后不需要补偿机制吗,对于下游服务来说很可能以及成功了

    作者回复: 这个案例中,你说的这种情况是有可能存在的。

    是否需要补偿,也无所谓对错,总体效果是一样的。秒杀的目的就是从众多秒杀用户中公平的选择n个用户,补偿或不补偿,影响的只是这n个用户是谁的问题。

    所以这是一个架构选择的问题。

    我建议是不用补偿,按失败处理,锁定的库存超时未支付后会自动释放,好处是比较简单。

    2019-08-14
    2
  • 微微一笑
    老师好,看了秒杀的代码,有点疑问:
    等待后端超过timeout设置的时间点,且没有秒杀结果,finally代码块中会remove掉这个请求id,并返回用户秒杀失败;若在remove之前,后端服务返回了秒杀结果并秒杀成功,在非常极致的情况下,会不会出现用户看到秒杀失败,系统却秒杀成功的情况发生呢?

    作者回复: 你可以再看一下这课的文本,我补充了一个流程图。

    对于你说的这个情况,是不会出现的。因为,后端服务返回的秒杀结果,只会存放在Map中,并不会直接返回APP。

    给APP返回结果的,只能是处理APP请求的那个线程。

    2019-08-09
    3
    2
  • 明日
    老师,关于事务消息的ACID那个问题没有提到,能不能找机会说下你的看法?个人的看法是没有实现隔离性,一致性只能保证最终一致,而原子操作和持久化可以通过各种手段实现。

    作者回复: 严格的说,ACI都没实现,只有D实现了。

    放宽点儿限制的话,或者考虑实际效果的话,A(原子性)绝大多数情况下还是可以保证的,即“要么都成功,要么都失败”。C(一致性)通过补偿,大部分情况下也可以保证最终一致。

    2019-08-08
    1
    2
  • A:春哥大魔王
    看了几期,感觉分布式消息队列的设计方案和分布式存储系统的设计方案很类似,如果再加上事务处理,存储细节方案应该更像了

    作者回复: 同学那不是像,消息队列就是个分布式存储系统。

    2019-08-08
    2
  • 猿人谷
    这篇答疑解惑,虽然简短,但绝对的诚意十足。希望多出这种答疑解惑的章节,毕竟评论区里很多留言的问题非常有代表性,对代表性的问题出这种答疑解惑的章节,学到的更多,也更能体现大家的参与度。
    2019-08-08
    2
  • 业余草
    相对来说,哪一个消息队列的学习成本较高?

    作者回复: 我觉得是Kafka吧,功能足够复杂,而且老外写代码的脑回路和我们不大一样。

    2019-08-08
    1
    2
  • TonySweet
    刚开始学习 RocketMQ. Consumer可以register的MessageListener 有两种: MessageListenerOrderly 和 MessageListenerConcurrently.
    这和老师讲的, RocketMQ 每个queue只要一个单线程消费者矛盾吗?

    作者回复: 首先强调一下,并不是“每个队列只要一个单线程消费者”,而是“每个队列只能被一个消费者实例占用。”

    rocketMQ的MessageListenerConcurrently,和我们上面讲的内容也不矛盾,它这个并行消费是完全在客户端实现的。大致的原理就是:

    1. 客户端从服务端的某个队列读取一批消息;
    2. 分发给客户端的多个线程消费;
    3. 都消费成功后,给服务端返回消费成功确认。

    2019-11-04
    1
  • asdf100
    订阅模型中,消息的发送方称为发布者(Publisher),消息的接收方称为订阅者(Subscriber),服务端存放消息的容器称为主题(Topic)。发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。

    那么消息到底存储在主题里还是队列里????

    作者回复: 队列

    2019-08-22
    1
    1
  • asdf100
    消息一直在队列里不删除?

    作者回复: 消息什么时候删除取决于消息队列的配置,比如Kafka默认就是超过多长时间后就自动删除了。

    2019-08-22
    1
  • vi
    李sir,一个迟来的学生,刚刚看到这里,对于rabbitmq来说,没有消费组队列,只有exchange转送到相应的对列中,要想提高并发,看到的方法可以设置多线程消息,好像变成了单个对列并行消息的模式,会不会也有文中所说的这个问题,rabbitmq还可以通过设置prefetch来缓存一定的数目,是不是就相当于增加每个消费者的队列数来解决并发的问题了

    作者回复: 想要增加消费并发,可以考虑exchange把消息平均的分摊到多个消费队列中。

    2019-08-21
    1
    1
  • 发条橙子 。
    老师,我的疑问点有些偏 。 我看老师依赖注入用的是JSR330的注释 Inject , 但实际上和spring自身的 Autowired 注释功能相同, 所以我平时都是直接用 spring 自带的注释。 请问老师使用 JSR330 提供的注释是有什么讲究莫

    作者回复: JSR是一个标准,Spring是JSR的一个实现,并做了很多的扩展。

    2019-08-17
    1
  • sswzfly
    我是消息队列小达人了
    2019-08-09
    1
  • 大白先生
    老师,在秒杀场景中,后端服务通过调用onResult方法来放入秒杀结果,有没有可能有一种情况,就是秒杀成功了,但是获取mutex是空,这样的活,会不会前段接收了秒杀失败的提示,但是库存扣减成功了

    作者回复: 会有这种情况。但无需特殊处理。

    一般秒杀成功后,还有后续的支付流程。

    有些用户秒杀成功也不会支付。你说的这种情况与“秒杀成功不支付”一并处理就行了。一般都是有一个支付超时,超过这个时限还没支付,就取消这个秒杀资格,自动释放库存。

    2019-08-09
    1
收起评论
58
返回
顶部