教你一文读懂消息队列并知道队列怎么选

Posted 恒生LIGHT云社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了教你一文读懂消息队列并知道队列怎么选相关的知识,希望对你有一定的参考价值。

01一文读懂消息队列

概述

什么是消息队列?MQ(Message Queue) 消息队列,件如其名,消息的队列,消息即系统间通信的信息,队列是一种先进先出的数据结构。Java 就实现了不少的队列,如下图:

有兴趣的小伙伴可以看一看源码。

为什么需要消息队列?

网上有很多的例子我就不编故事了,我就 copy 一下技术大佬 李钥 老师的例子:

话说小袁是一家巧克力作坊的老板,生产出美味的巧克力需要三道工序:首先将可可豆磨成可可粉,然后将可可粉加热并加入糖变成巧克力浆,最后将巧克力浆灌入模具,撒上坚果碎,冷却后就是成品巧克力了。

最开始的时候,每次研磨出一桶可可粉后,工人就会把这桶可可粉送到加工巧克力浆的工人手上,然后再回来加工下一桶可可粉。小袁很快就发现,其实工人可以不用自己运送半成品,于是他在每道工序之间都增加了一组传送带,研磨工人只要把研磨好的可可粉放到传送带上,就可以去加工下一桶可可粉了。 传送带解决了上下游工序之间的“通信”问题。

传送带上线后确实提高了生产效率,但也带来了新的问题:每道工序的生产速度并不相同。在巧克力浆车间,一桶可可粉传送过来时,工人可能正在加工上一批可可粉,没有时间接收。不同工序的工人们必须协调好什么时间往传送带上放置半成品,如果出现上下游工序加工速度不一致的情况,上下游工人之间必须互相等待,确保不会出现传送带上的半成品无人接收的情况。

为了解决这个问题,小袁在每组传送的下游带配备了一个暂存半成品的仓库,这样上游工人就不用等待下游工人有空,任何时间都可以把加工完成的半成品丢到传送带上,无法接收的货物被暂存在仓库中,下游工人可以随时来取。传送带配备的仓库实际上起到了“通信”过程中“缓存”的作用。

传送带解决了半成品运输问题,仓库可以暂存一些半成品,解决了上下游生产速度不一致的问题,小袁在不知不觉中实现了一个巧克力工厂版的消息队列。

消息队列都能解决哪些问题?

1 解耦

消息队列的作用之一就是实现系统应用之间的解耦。举个例子说明一下解耦的作用和必要性。

我们就以某宝为例,当我下单购买商品时:

a,支付系统发起支付流程;

b,支f宝风控审核订单合法性;

c,发送短信通知付款支f宝;

d,某宝更新统计数据;

e,等等

倘若使用接口时,随着业务不断发展,这些订单下游系统不断的增加,不断变化,并且每个系统可能只需要订单数据的一个子集,负责订单服务的开发团队不得不花费很大的精力,应对不断增加变化的下游系统,不停地修改调试订单系统与这些下游系统的接口。任何一个下游系统接口变更,都需要订单模块重新进行一次上线,对于一个电商的核心服务来说,这几乎是不可接受的。

引入消息队列后,订单服务在订单变化时发送一条消息到消息队列的一个主题 Order 中,所有下游系统都订阅主题 Order,这样每个下游系统都可以获得一份实时完整的订单数据。

无论增加、减少下游系统或是下游系统需求如何变化,订单服务都无需做任何更改,实现了订单服务与下游服务的解耦。

2 异步处理

我们仍然以电商系统为例设计一个秒杀系统。秒杀系统的核心问题就是如何使用有限的服务器资源尽快的处理海量请求,毕竟是 “秒杀” 一定要快!!!那么我们假设秒杀有一下几步(真实秒杀会有更多的步骤):

1,风控;

2,锁定库存;

3,生成订单;

4,发送短信;

5,更新统计数据;

6··········

如果使用接口处理的话,需要一次调用上述五个流程,然后响应秒杀结果。

我们尝试优化一下秒杀流程,其实当我们风控过了,库存锁定了,就可以响应给用户秒杀成功了,对了后面的几步并不一定要当前的秒杀请求处理。所以我们通过消息队列处理异步请求可以更快的返回结果,减少用户等待时间,提高了系统整体性能。

3 流量控制

继续以上边的秒杀系统为例,我们已经实现了异步处理,但我们如何避免大量请求压垮系统呢?

一个健壮的系统需要有自我保护能力,即使在海量的请求下仍然能在自己能力范围内处理请求,拒绝处理不了的请求并且保证服务运行正常,但是直接拒绝请求似乎不是很友好,因此我们需要重新设计一套健壮的架构来保护后段正常运行。

大体思路:用消息队列来隔离网关与后端服务,以达到流量控制保护后端服务。

加入消息队列,秒杀请求流程变为:

1,网关在收到请求后,将请求放入请求消息队列;

2,后端服务从请求消息队列中获取 APP 请求,完成后续秒杀处理过程,然后返回结果。

如图所示,当大量请求到达网关是,网关不会把请求直接转发给秒杀系统,而是将消息堆积到消息队列,后端服务就可以根据自己的能力处理请求。对于超时的请求可以直接丢弃,APP将超时无响应的请求处理为秒杀失败即可。

这种设计虽然能达到“削峰填谷”的作用,单同时也增加了整个链路的处理时间,是的响应时间变长,同时也增加了系统的复杂度。那有没有更好的解决办法?答:令牌桶。

令牌桶控制流量的原理是:单位时间内只发放固定数量的令牌到令牌桶中,规定服务在处理请求之前必须先从令牌桶中拿出一个令牌,如果令牌桶中没有令牌,则拒绝请求。这样就保证单位时间内,能处理的请求不超过发放令牌的数量,起到了流量控制的作用。

实现的方式也很简单,不需要破坏原有的调用链,只要网关在处理 APP 请求时增加一个获取令牌的逻辑。令牌桶可以简单地用一个有固定容量的消息队列加一个“令牌发生器”来实现:令牌发生器按照预估的处理能力,匀速生产令牌并放入令牌队列(如果队列满了则丢弃令牌),网关在收到请求时去令牌队列消费一个令牌,获取到令牌则继续调用后端秒杀服务,如果获取不到令牌则直接返回秒杀失败。以上是常用的使用消息队列两种进行流量控制的设计方法,你可以根据各自的优缺点和不同的适用场景进行合理选择。

02 消息队列怎么选

1 老牌儿消息队列 RabbitMQ

RabbitMQ,俗称兔子MQ,该中间件的特点也是,像兔子一样:轻量级、迅捷。Erlang 语言编写的,最早是为电信行业系统之间的可靠通信设计的,支持 AMQP,XMPP,SMTP,STOMP协议。号称世界上使用最广泛的消息队列,占有率是不是第一这个还真么办法统计,不过绝对是“最流行的中间件之一”。RabbitMQ 支持非常灵活的路由配置,和其他消息队列不同的是,它在生产者和队列之间增加了一个 交换器(Exchange) 模块。

Exchange 模块会根据配置的路由规则将生产者发出的消息分发给相应的队列。路由的规则非常灵活,也可以自己来实现路由规则。基于这个 Exchange,可以产生很多的玩儿法,如果你正好需要这个功能,RabbitMQ 是个不错的选择。

RabbitMQ 应该是所有队列里支持编程语言最多的,所以如果你是冷门语言开发, RabbitMQ 估计是你的不二之选,因为你没得选!!!

RabbitMQ 的几个问题

1)RabbitMQ 的设计理念,消息是一个管道,所以消息堆积是一种不正常的情况,应该避免堆积,当大量堆积消息时,会导致 RabbitMQ 的性能急剧下降。

2)RabbitMQ 的性能应该是我接下来要介绍的队列里最差的,根据官方给出的测试数据结合我们日常的使用经验,依据硬件配置的不通,它大概每秒钟可以处理几万到几十万的消息。除非你对消息队列有极高的性能要求,不然的话兔子MQ的性能能支撑绝大多数场景。

3)RabbitMQ 是 Erlang 写的,目前来说我还没遇到我身边会这玩意的,所以说这玩意有些太小众了,据说更麻烦的是这个语言的学习曲线非常陡峭,就是不咋好学习。大多数流行的编程语言,比如 Java、C/C++、Python 和 javascript,虽然语法、特性有很多的不同,但它们基本的体系结构都是一样的,你只精通一种语言,也很容易学习其他的语言,短时间内即使做不到精通,但至少能达到“会用”的水平。所以如果你想基于 RabbitMQ 做一些扩展和二次开发什么的,建议你慎重考虑一下可持续维护的问题。

2 国产火箭MQ“RocketMQ”

RocketMQ 是阿里巴巴在 2012 年开源的消息队列产品,后来捐赠给 Apache 软件基金会,2017 正式毕业,成为 Apache 的顶级项目。阿里内部也是使用 RocketMQ 作为支撑其业务的消息队列,经历过多次“双十一”考验,它的性能、稳定性和可靠性都是值得信赖的。作为优秀的国产消息队列,近年来越来越多的被国内众多大厂使用。

RocketMQ 就像一个品学兼优的好学生,有着不错的性能,稳定性和可靠性,具备一个现代的消息队列应该有的几乎全部功能和特性,并且它还在持续的成长中。

RocketMQ 有着非常活跃的中文社区,你能遇到的大多数问题都能找到中文答案,并且 RocketMQ 是用 Java 开发的!他的贡献者也大多数是中国人,所以源码相对比容易懂,所以也比较容易扩展和二次开发。

RocketMQ的性能要比RabbitMQ搞一个量级,每秒钟大概几十万的消息吧。它的一个劣势是国产的,在国际上还不是很流行,与周边生态的集成和兼容要差一点。

3 Kafka

关于Kafka的介绍先 copy 一下百度百科

Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息。

因为 Kafka 早期设计目的是用来处理海量的日志,所以为了获得极致的性能,在其他方面做了一些牺牲,例如不保证消息的可靠性,可能丢失消息,不支持集群,功能简陋,这个版本的Kafka不能称之为合格的消息队列。但是,后边随着Kafka的发展,满满地修复了这些设计的缺陷,现在无论在数据的可靠性,稳定性和功能性等方面都可以满足绝大多数的场景。

Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域,几乎所有的相关开源软件系统都会优先支持 Kafka。

Kafka 使用 Scala 和 Java 语言开发,设计上大量使用了批量和异步的思想,这种设计使得 Kafka 能做到超高的性能。Kafka 的性能,尤其是异步收发的性能,是三者中最好的,但与 RocketMQ 并没有量级上的差异,大约每秒钟可以处理几十万条消息。

由于Kafka的这种异步批量设计,所以接收消息的响应延时比较高,因为 Kafka 并不是立即把消息发出去,而是攒一攒再发。所以,Kafka不太适合实时性要求比较高的业务场景。

总结

如果你的业务场景对消息队列的功能和性能没有很高的要求,只是要一个开箱即用,易于维护的消息队列,那推荐你(必须是)RabbitMQ。

如果你的业务场景要求延迟低和金融级的稳定性,那推荐你RocketMQ。

Kafka特别适合处理海量日志监控信息,或者大数据,流计算相关产品。

以上是关于教你一文读懂消息队列并知道队列怎么选的主要内容,如果未能解决你的问题,请参考以下文章

一文读懂RabbitMQ 消息队列

一文读懂RabbitMQ 消息队列

Kafka 一文读懂

Kafka 一文读懂

系统学习消息队列分享 为什么需要消息队列?

一文读懂 .NET 中的高性能队列 Channel