消息总线(MQ)知多少

消息总线(MQ)知多少1 什么时候用 MQ 1 1MQ 的基本概念 消息总线 MessageQueue MQ 是一种跨进程的通信机制 用于在上下游之间传递消息 MQ 是一种常见的上下游 逻辑解耦 物理解耦 的消息通信服务 消息发送上游只需要依赖 MQ 逻辑上和物理上都不用依赖其他服务 1 2MQ 的使用场景场景一 数据驱动的任务依赖 有些任务之间有一定的依赖关系 比如 task3 需要使用 task2 的输出作为输入 tas

1 什么时候用MQ?

1.1 MQ的基本概念

  消息总线(Message Queue,MQ),是一种跨进程的通信机制,用于在上下游之间传递消息。MQ是一种常见的上下游“逻辑解耦+物理解耦”的消息通信服务,消息发送上游只需要依赖MQ,逻辑上和物理上都不用依赖其他服务。

1.2 MQ的使用场景

场景一:数据驱动的任务依赖

  有些任务之间有一定的依赖关系,比如:task3需要使用task2的输出作为输入,task2需要使用task1的输出作为输入。这样的话,tast1, task2, task3之间就有任务依赖关系,必须task1先执行,再task2执行,再task3执行。对于这类需求,常见的实现方式是,使用cron人工排执行时间表:

  1. task1,0:00执行,经验执行时间为50分钟;
  2. task2,1:00执行(为task1预留10分钟buffer),经验执行时间也是50分钟;
  3. task3,2:00执行(为task2预留10分钟buffer)
    这里写图片描述

这种方法的坏处是:

  1. 如果有一个任务执行时间超过了预留buffer的时间,将会得到错误的结果;
  2. 总任务的执行时间很长,总是要预留很多buffer,如果前置任务提前完成,后置任务不会提前开始;
  3. 如果一个任务被多个任务依赖,这个任务将会称为关键路径,排班表很难体现依赖关系,容易出错;
  4. 如果有一个任务的执行时间要调整,将会有多个任务的执行时间要调整。

优化方案是,采用MQ解耦:

  1. task1准时开始,结束后发一个“task1 done”的消息;
  2. task2订阅“task1 done”的消息,收到消息后第一时间启动执行,结束后发一个“task2 done”的消息;
  3. task3同理
    这里写图片描述

采用MQ的优点是:

  1. 不需要预留buffer,上游任务执行完,下游任务总会在第一时间被执行;
  2. 依赖多个任务,被多个任务依赖都很好处理,只需要订阅相关消息即可;
  3. 有任务执行时间变化,下游任务都不需要调整执行时间

  需要特别说明的是,MQ只用来传递上游任务执行完成的消息,并不用于传递真正的输入输出数据。

场景二:上游不必关心执行结果

这种方法的坏处是:

  1. 帖子发布流程的执行时间增加了;
  2. 下游服务宕机,可能导致帖子发布服务受影响,上下游逻辑+物理依赖严重;
  3. 每当增加一个需要知道“帖子发布成功”信息的下游,修改代码的是帖子发布服务,属于架构设计中典型的依赖倒转。
    这里写图片描述

优化方案是,采用MQ解耦:

  1. 帖子发布成功后,向MQ发一个消息;
  2. 哪个下游关注“帖子发布成功”的消息,主动去MQ订阅
    这里写图片描述

采用MQ的优点是:

  1. 上游执行时间短;
  2. 上下游逻辑+物理解耦,除了与MQ有物理连接,模块之间都不相互依赖;
  3. 新增一个下游消息关注方,上游不需要修改任何代码

场景三:上游关注执行结果,但执行时间很长

  有时候上游需要关注执行结果,但执行结果时间很长。微信支付,跨公网调用微信的接口,执行时间会比较长,但调用方又非常关注执行结果,此时一般怎么玩呢?

一般采用“回调网关+MQ”方案来解耦:

  1. 调用方直接跨公网调用微信接口;
  2. 微信返回调用成功,此时并不代表返回成功;
  3. 微信执行完成后,回调统一网关;
  4. 网关将返回结果通知MQ;
  5. 请求方收到结果通知
    这里写图片描述

  这里需要注意的是,不应该由回调网关来调用上游来通知结果,如果是这样的话,每次新增调用方,回调网关都需要修改代码,仍然会反向依赖,使用回调网关+MQ的方案,新增任何对微信支付的调用,都不需要修改代码啦。

1.3 什么时候不使用MQ

  虽然MQ是分层架构中的解耦利器,但调用与被调用的关系,是无法被MQ取代的。

MQ的不足是:

  1. 系统更复杂,多了一个MQ组件;
  2. 消息传递路径更长,延时会增加;
  3. 消息可靠性和重复性互为矛盾,消息不丢不重难以同时保证;
  4. 上游无法知道下游的执行结果,这一点是很致命的

例如:用户登录场景,登录页面调用passport服务,passport服务的执行结果直接影响登录结果,此处的”登录页面”与”passport服务”就必须使用调用关系,而不能使用MQ通信。

1.4 总结

  1. MQ是一个互联网架构中常见的解耦利器。
  2. 什么时候不使用MQ?上游实时关注执行结果。
  3. 什么时候使用MQ?1)数据驱动的任务依赖; 2)上游不关心多下游执行结果; 3)异步返回执行时间长。

2 MQ是如何做到消息必达?

  MQ要想尽量消息必达,架构上有两个核心设计点:(1)消息落地(2)消息超时、重传、确认。

2.1 MQ核心架构

这里写图片描述

  MQ是一个系统间解耦的利器,它能够很好的解除发布者、订阅者之间的耦合,将上下游的消息投递解耦成两个部分。MQ的核心架构图,基本可以分为三大块:

  1. 发送方 -> 左侧粉色部分,由两部分构成:业务调用方与MQ-client-sender,其中后者向前者提供了两个核心API:SendMsg(bytes[] msg)、SendCallback();
  2. MQ核心集群 -> 中间蓝色部分,分为四个部分:MQ-server,zk,db,管理后台web;
  3. 接收方 -> 右侧黄色部分,由两部分构成:业务接收方与MQ-client-receiver,其中后者向前者提供了两个核心API:RecvCallback(bytes[] msg)、SendAck()

2.2 MQ消息可靠投递核心流程

MQ消息投递上半场,MQ-client-sender到MQ-server流程见上图:

  1. MQ-client将消息发送给MQ-server(此时业务方调用的是API:SendMsg);
  2. MQ-server将消息落地,落地后即为发送成功;
  3. MQ-server将应答发送给MQ-client(此时回调业务方是API:SendCallback)

  MQ消息投递下半场,MQ-server到MQ-client-receiver流程见上图:

  1. MQ-server将消息发送给MQ-client(此时回调业务方是API:RecvCallback);
  2. MQ-client回复应答给MQ-server(此时业务方主动调用API:SendAck);
  3. MQ-server收到ack,将之前已经落地的消息删除,完成消息的可靠投递

2.3 如果消息丢了怎么办?

  MQ消息投递的上下半场,都可以出现消息丢失,为了降低消息丢失的概率,MQ需要进行超时和重传。

2.3.1 上半场的超时与重传

  MQ上半场的1或者2或者3如果丢失或者超时,MQ-client-sender内的timer会重发消息,直到期望收到3,如果重传N次后还未收到,则SendCallback回调发送失败,需要注意的是,这个过程中MQ-server可能会收到同一条消息的多次重发。

2.3.2 下半场的超时与重传

  MQ下半场的4或者5或者6如果丢失或者超时,MQ-server内的timer会重发消息,直到收到5并且成功执行6,这个过程可能会重发很多次消息,一般采用指数退避的策略,先隔x秒重发,2x秒重发,4x秒重发,以此类推,需要注意的是,这个过程中MQ-client-receiver也可能会收到同一条消息的多次重发。

3.MQ如何做到消息幂等

3.1 消息必达的前提

3.2 上半场的幂等性设计

MQ消息发送上半场,即上图中的步骤1-3

  有了这个inner-msg-id,就能保证上半场重发,也只有1条消息落到MQ-server的DB中,实现上半场幂等。

3.3 下半场的幂等性设计

MQ消息发送下半场,即上图中的步骤4-6

  有了这个业务ID,才能够保证下半场消息消费业务方即使收到重复消息,也只有1条消息被消费,保证了幂等。

3.4 总结

  MQ为了保证消息必达,消息上下半场均可能发送重复消息,如何保证消息的幂等性呢?

上半场

下半场

结论:幂等性,不仅对MQ有要求,对业务上下游也有要求。

4. MQ如何实现消息延迟

4.1 缘起

4.2 高效延时消息设计与实现

  高效延时消息,包含两个重要的数据结构:

  同时,启动一个timer,这个timer每隔1s,在上述环形队列中移动一格,有一个Current Index指针来标识正在检测的slot。

Task结构中有两个很重要的属性:

这里写图片描述

  假设当前Current Index指向第一格,当有延时消息到达之后,例如希望3610秒之后,触发一个延时消息任务,只需:

  Current Index不停的移动,每秒移动到一个新slot,这个slot中对应的Set,每个Task看Cycle-Num是不是0:

  使用了“延时消息”方案之后,“订单48小时后关闭评价”的需求,只需将在订单关闭时,触发一个48小时之后的延时消息即可:

4.3 总结

  环形队列是一个实现“延时消息”的好方法,开源的MQ好像都不支持延迟消息,不妨自己实现一个简易的“延时消息队列”,能解决很多业务问题,并减少很多低效扫库的cron任务。

5.MQ如何实现削峰填谷

5.1 站点与服务、服务与服务上下游之间,一般如何通讯?

  一种是“直接调用”,通过RPC框架,上游直接调用下游;另一种是采用“MQ推送”,上游将消息发给MQ,MQ将消息推送给下游。

5.2 为什么会有流量冲击?

  不管采用“直接调用”还是“MQ推送”,都有一个缺点,下游消息接收方无法控制到达自己的流量,如果调用方不限速,很有可能把下游压垮。假如,上游下单业务简单,每秒发起了10000个请求,下游秒杀业务复杂,每秒只能处理2000个请求,很有可能导致下游系统被压垮,引发雪崩。

  为了避免雪崩,常见的优化方案有两种:1)业务上游队列缓冲,限速发送;2)业务下游队列缓冲,限速执行。

5.3 MQ怎么改能缓冲流量?

  由MQ-server推模式,升级为MQ-client拉模式。MQ-client根据自己的处理能力,每隔一定时间,或者每次拉取若干条消息,实施流控,达到保护自身的效果。并且这是MQ提供的通用功能,无需上下游修改代码。

5.4 如果上游发送流量过大,会不会导致消息在MQ中堆积?

  下游MQ-client拉取消息,消息接收方能够批量获取消息,需要下游消息接收方进行优化,方能够提升整体吞吐量,例如:批量写。

5.4 结论

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232614.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • java字段_java字段是什么

    java字段_java字段是什么Java 中的属性和字段有什么区别 Java 中的属性 property 通常可以理解为 get 和 set 方法 而字段 field 通常叫做 类成员 或 类成员变量 有时也叫 域 理解为 数据成员 用来承载数据的 字段 field 类成员 字段 通常是在类中定义的类成员变量 例如 publicclassA privateStrin 123 我们可以说 A 类中有一个成员变

    2025年7月14日
    6
  • iOS的QuickTime Plugin

    当UIWebView播放视频时,可以看到viewhierarchy里有FigPluginView的身影。这个类来自于QuickTimePlugin,plugin的路径为:/Application

    2021年12月24日
    45
  • MQTTBox客户端工具(Windows版)

    MQTTBox是一个带有可视化的界面的MQTT的客户端工具.工具特性:创建连接到相同或不同MQTT代理的多个并发MQTT客户端TCP,SSL/TLS,Web套接字和安全的Web套接字支持创建具有多种连接设置的MQTT客户端-最后的遗留,keepAlive,连接超时等等用户名/密码认证连接,断开连接,重新连接MQTT客户端到代理的选项将消息从同一MQTT客户端发布到多个主题从同一MQTT客户端订阅多个主题支持单级(+)和多级(#)订阅主题单击即可复制,重新发布有效负载查看已发

    2022年4月4日
    1.7K
  • django修改数据_java实现增删改查

    django修改数据_java实现增删改查前言在ORM框架中,所有模型相关的操作,比如添加/删除等。其实都是映射到数据库中一条数据的操作。因此模型操作也就是数据库表中数据的操作。添加一个模型到数据库中:添加模型到数据库中。首先需要创建一

    2022年7月29日
    6
  • css鼠标点击的五种状态

    css鼠标点击的五种状态1、a:link{color:#fff}未访问时的状态(鼠标点击前显示的状态)2、a:hover{color:#fff}鼠标悬停时的状态3、a:visited{color:#fff}已访问过的状态(鼠标点击后的状态)4、a:active{color:#fff}鼠标点击时的状态5、a:focus{color:#fff}点击后鼠标移开保持鼠标点击时的

    2022年5月31日
    29
  • es6set和map区别_list和map的区别面试

    es6set和map区别_list和map的区别面试转自https://blog.csdn.net/terence1212/article/details/52487656set是一种关联式容器,其特性如下:set以RBTree作为底层容器所得元素的只有key没有value,value就是key不允许出现键值重复所有的元素都会被自动排序不能通过迭代器来改变set的值,因为set的值就是键map和set一样是关联式容器,它们的底层容器都是红黑树,区别…

    2025年9月30日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号