消息队列的使用

629 阅读12分钟

一、消息队列MQ(Message Queue)的使用场景

1、异步处理

场景说明:用户注册后,需要发注册邮件和注册短信

传统方式将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户。

640?

小结:如以上案例描述,传统的方式系统的性能(吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?

引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下:

640?

按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20QPS,比传统方式提高了3倍。

2、应用解耦

场景说明:用户下单后,订单系统需要通知库存系统。

传统的做法是,订单系统调用库存系统的接口。如下图:

640

传统模式的缺点:

假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合。如何解决以上问题呢?引入应用消息队列后的方案,如下图:

640

订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功

库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作

假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。

3、流量削锋

应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,超过系统最大的处理能力,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

640

用户的请求,服务器接收后,首先写入消息队列。秒杀业务根据消息队列中的请求信息,再做后续处理。

二、JMS消息模型

Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。 点对点与发布订阅最初是由JMS定义的。

1、点对点模式Point-to-Point(P2P)

在这里插入图片描述

生产者发送一条消息到queue,只有一个消费者能收到。

2、发布/订阅(广播)模式Publish/Subscribe(Pub/Sub)

在这里插入图片描述

消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有有效订阅者消费。

三、消息队列的技术选型

juejin.cn/post/684490…

zhuanlan.zhihu.com/p/60196818

juejin.cn/post/684490…

1、ActiveMQ

单机吞吐量:万级

时效性:ms级

可用性:高,基于主从架构实现高可用性

消息可靠性:有较低的概率丢失数据

功能支持:MQ领域的功能极其完备

总结:

非常成熟,功能强大,在早些年业内大量的公司以及项目中都有应用

偶尔会有较低概率丢失消息

现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本

主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用

2、RabbitMQ

单机吞吐量:万级

topic数量都吞吐量的影响:

时效性:微秒级,延时低是一大特点。

可用性:高,基于主从架构实现高可用性

消息可靠性:数据一致性、稳定性和可靠性很高

功能支持:基于erlang开发,所以并发能力很强,性能极其好,延时很低

总结

erlang语言开发,性能极其好,延时很低;

开源提供的管理界面非常棒,用起来很好用

社区相对比较活跃,几乎每个月都发布几个版本分

在国内一些互联网公司近几年用rabbitmq也比较多一些 但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。

erlang开发,很难去看懂源码,基本只能依赖于开源社区的快速维护和修复bug,不利于二次开发和维护

需要学习比较复杂的接口和协议,学习和维护成本较高

3、Kafka

单机吞吐量:十万级,最大的优点,就是吞吐量高。

topic数量都吞吐量的影响:topic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源

时效性:ms级

可用性:非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用

消息可靠性:经过参数优化配置,消息可以做到0丢失

功能支持:由Scala和Java编写,功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用

总结:

kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展

同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量

kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略

4、RocketMQ

RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于订单、交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。

单机吞吐量:十万级

topic数量都吞吐量的影响:topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降。可支持大量topic是一大优势。

时效性:ms级

可用性:非常高,分布式架构

消息可靠性:经过参数优化配置,消息可以做到0丢失

功能支持:MQ功能较为完善,还是分布式的,扩展性好

总结:

接口简单易用,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景

源码是java,我们可以自己阅读源码,定制自己公司的MQ,可以掌控

社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码  

支持10亿级别的消息堆积,不会因为堆积导致性能下降

为什么阿里会自研RocketMQ?

(1)Kafka的业务应用场景主要定位于日志传输;对于复杂业务支持不够

(2)阿里很多业务场景对数据可靠性、数据实时性、消息队列的个数等方面的要求很高,kafka针对海量数据,但是对数据的正确度要求不是十分严格。而阿里巴巴中用于交易相关的事情较多,对数据的正确性要求极高,Kafka不合适

(3)当业务成长到一定规模,采用开源方案的技术成本会变高.

开源方案无法满足业务的需要;旧版本、自开发代码与新版本的兼容都可能是问题;运维角度,Kafka使用 scala 编写,而阿里是java系。Kafka 的后续维护是个问题。

(4)阿里在团队、成本、资源投入等方面约束性条件几乎没有.

综上,阿里选择自己开发RocketMQ更多是业务的驱动,当业务更多的需要以下功能的支持时,kafka 不能满足或者 ActiveMQ 等其他消息中间件不能满足,财大气粗能力又强业务还复杂,所以就自己开发了。

消息队列选择建议

1.Kafka

Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。

2.RocketMQ

天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。

RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。

3.RabbitMQ

RabbitMQ :结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。不过,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug。

如果你的数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ。

4.ActiveMQ

最早大家都用ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,不推荐用这个了

四、使用消息队列需要考虑的问题

1、消息重复消费问题

以kafka为例,kafka有个offset的概念,当每个消息被写进去后,都有一个offset,代表序号,然后consumer消费该数据之后,隔一段时间,会把自己消费过的消息的offset提交一下,代表已经消费过了。下次要是重启,就会继续从上次消费到的offset来继续消费。

但是当重启应用时,会导致consumer有些消息处理了,但是没来得及提交offset。等重启之后,少数消息就会再次消费一次。

要避免这个重复消费的问题,可以在消费端引入内存、Redis、数据库来保存消息消费记录,根据消息Id来判断消息是否已经被消费过

2、如何保证消息的可靠性传输

1)生产者消息丢失

生产者消息丢失,可以使用本地消息表解决、消息确认/重发等方式来解决。以RabbitMQ为例,它有confirm机制,发出去的消息是否入队列,会使用回调的形式告知生产者,生产者收到消息后判断是Ack还是Nak,如果是Nak则重发消息。


此时还会有问题,如果极端情况下订单服务挂了,再次重启后消息就真丢失了,所以最好还是在生产中对消息做持久化,待订单服务恢复后使用Job重新发送消息。

2)消息队列丢了数据

MQ消息丢失一般为未开启持久化,MQ挂了再次重启后消息丢失,所以应当将消息持久化到磁盘中。如果MQ收到消息后在同步到磁盘之前MQ挂了,那磁盘中也没有消息,这样还是会导致消息丢失消息,需要通过相关参数配置、集群搭建保证消息队列的高可用性。

3)消费端弄丢了数据

消费者消息丢失,大都为开启了autoAck选项,消费者收到消息后还未完成处理,此时服务挂了,由于开启了autoAck, MQ会以为此消息已经被成功消费,将消息从队列中移除,而服务恢复过后也不会收到原来的消息了。

关闭自动ack,当代码里确保处理完的时候,在程序里手动提交ack。

3、如何保证消息的顺序性

消息为什么会错乱

1、一个queue,有多个consumer去消费,这样就会造成顺序的错误,consumer从MQ里面读取数据是有序的,但是每个consumer的执行时间是不固定的,无法保证先读到消息的consumer一定先完成操作,这样就会出现消息并没有按照顺序执行,造成数据顺序错误

2、由于消费者消费消息之后,消费之后,有可能交给很多个线程去处理数据,这样就导致数据顺序错乱

kafka特性

1、kafka中,写入一个partion中的数据是一定有顺序的
2、kafka中一个消费者消费一个partion的数据,消费者取出数据时,也是有顺序的


解决方案:

1、对数据指定某个 key做hash分发,那么这些数据会到同一个 partition 里面,在 partition 里面这些数据是有顺序,指定消费者消费相应的partition

2、为保证一个消费者中多个线程去处理时,不会使得消息的顺序被打乱,可以在消费者中,消息分发至不同的线程时,加一个队列,消费者去做hash分发,将需要放在一起的数据,分发至同一个队列中,最后多个线程从队列中取数据