消息中间件的介绍

什么是消息中间件

消息(Messages)是指应用间传递的数据。消息可以非常简单,比如:文本字符串、Json数据等,也可以很复杂,比如内嵌对象。

消息队列中间件(Message Queue Middleware,简称为MQ)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。

消息队列中间件,也可以称为消息队列或消息中间件。它一般有两种传递模式:(1)点对点(P2P,Point-to-Point)模式 和 (2)发布/订阅(Pub/Sub)模式。点对点模式是基于队列的,消息生产者发送消息到队列,消息消费者从队列中接受消息,队列的存在使得消息的异步传输成为可能。发布订阅模式定义了如何向一个内容节点发布和订阅消息,这个内容节点称为主题(topic),主题可以认为是消息传递的中介,消息发布者将消息发布到某个主题,而消息订阅者则从主题中订阅消息。主题使得消息的订阅者与消息的发布者互相保持独立,不需要进行接触即可保证消息的传递,发布/订阅模式在消息的一对多广播时采用。

为何要使用消息中间件

从上一节的描述中可以看出,消息中间件是一种系统间相互协作的通信机制。那么什么时候需要使用消息队列呢?

(1)异步

举个例子。某天产品经理说:“系统需要增加一个用户注册功能,注册成功后用户能收到邮件通知”。

在实际场景中这种需要很常见,开发人员觉得这个很简单,就是提供一个注册页面,点击按钮提交之后保存用户的注册信息,然后发送邮件,最后返回用户注册成功,如下所示:

需求分析

该功能上线运行了一段时间后,产品人员说:“点击注册按钮之后的响应时间有点长,很多用户表示反感,能不能优化一下?”。开发人员首先想到的优化方案就是将 保存注册信息发送邮件 分开执行,怎么分呢?可以单独启动线程来发做发送邮件的事情,如下所示:

需求分析

没过多久,产品人员又说:“现在注册操作的响应是快了,但有用户反映没收到注册成功的邮件,能不能在发送邮件的时候先保存所发送邮件的内容,如果没有发送成功则进行补发呢?”。

看着开发人员愁眉苦脸的样子,产品人员说:“在邮件发送这个模块,平台部门已经做好了方案,你直接调用他们提供的服务就行。”。开发人员一听,赶紧和平台部门沟通,对方的答复是:“我们提供了一个类似于邮局信箱的东西,你可以直接往里面写上发送邮件的地址、邮件的标题和内容即可。我们会从信箱里面取出信息,向你填写的邮件地址发送响应邮件”。

这个故事讲的就是使用:消息队列的典型场景——异步处理。消息队列还可用于解决解耦、流量消峰、日志收集、事务最终一致性等问题。

(2)解耦

某天产品经理说:“为了方便与网站用户之间进行交流、共享信息、解决网站中遇到的各种问题,网站要增加一个论坛功能,在这个论坛中用户可以发布帖子,其他用户可以在这个帖子下评论和回复。” 开发人员一听,觉得这个需求很简单,不难实现,就是常见的网上论坛。于是,没过几天就完成了功能开发并转测上线了。

过了一段时间,用户量增加了,对帖子发布功能的使用越来越频繁。产品人员说:“信息质量部门期望在发帖子的时候能检查所发布的内容是不是合规”。没多久,产品人员又说:“大数据部门希望能根据帖子的内容提供用户相关信息来丰富用户的画像”。又过了几天,产品人员说:“营销部门最近在做活动,如果用户发布的是与营销活动相关的帖子,则能给该用户增加积分”。

经验少的开发人员遇到这种情况,一般是来一个需要叠加一段业务逻辑代码。这当然可以尽快交付上线,但仔细分析需求发现,发布帖子应该作为一个独立的功能,并且这个功能并不需要关心后面不断增加的那些功能,更不需要关心后面功能的执行结果,只需要通知其他相应模块执行就行了。

在大型系统的开发过程中会经常碰到此类情况,随着需求的叠加,各模块之间逐渐变成了互相调用的关系,这种模块间紧密关联的关系就是紧耦合。紧耦合带来的问题是对一个模块的功能变更将导致其关联模块发生变化,因此各个模块难以独立演化。

要解决这个问题,可以在模块之间调用时增加一个中间层来实现解耦,这也方便了以后的扩展。所谓解耦,简单的理解就是一个模块只关心自己的核心流程,而依赖该模块执行结果的其他模块如果做的不是很重要的事情,有通知即可,无须等待结果。换句话说,基于消息队列的模型,关心的是通知,而非处理

例子:

看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃……

需求分析

在这个场景中,A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?头发都白了啊!

如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。

需求分析

总结:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。

面试技巧:你需要去考虑一下你负责的系统中是否有类似的场景,就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦,也是可以的,你就需要去考虑在你的项目里,是不是可以运用这个 MQ 去进行系统的解耦。在简历中体现出来这块东西,用 MQ 作解耦。

(3)削峰

每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。

一般的 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。

但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。

需求分析

如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。

需求分析

这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。

消息队列有什么优缺点

优点上面已经说了,就是在特殊场景下有其对应的好处解耦异步削峰

缺点有以下几个:

  • 系统可用性降低
    系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,人家 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整,MQ 一挂,整套系统崩溃的,你不就完了?如何保证消息队列的高可用性呢?

  • 系统复杂度提高
    硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。

  • 一致性问题
    A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。

所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。

Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?

特性 ActiveMQ RabbitMQ RocketMQ Kafka
单机吞吐量 万级,比 RocketMQ、Kafka 低一个数量级 同 ActiveMQ 10 万级,支撑高吞吐 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景
topic 数量对吞吐量的影响 topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源
时效性 ms 级 微秒级,这是 RabbitMQ 的一大特点,延迟最低 ms 级 延迟在 ms 级以内
可用性 高,基于主从架构实现高可用 同 ActiveMQ 非常高,分布式架构 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消息可靠性 有较低的概率丢失数据 基本不丢 经过参数优化配置,可以做到 0 丢失 同 RocketMQ
功能支持 MQ 领域的功能极其完备 基于 erlang 开发,并发能力很强,性能极好,延时很低 MQ 功能较为完善,还是分布式的,扩展性好 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用

综上,各种对比之后,有如下建议:

一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了;

后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;

不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 Apache,但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。

所以中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择。

如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。


  目录