1.MQ是什么?
MQ就是我们常说的消息队列,全称Message Queue,是作为一个单独的中间件产品存在的,独立部署。
消息队列本质上就是一个队列,将消息放到队列里,用队列做存储信息的介质。消息的发送方称为生产者,消息的接收方称为消费者。
为什么要用消息队列?什么时候用消息队列?消息队列有什么处?
消息队列主要有三个作用:解耦、异步、削峰
-
解耦
具体场景:用户通过订单系统下单,订单系统需要调用库存系统的接口发送订单消息。订单系统与库存系统耦合。
问题一(部分系统失败):如果库存系统无法访问,订单系统进行减库存操作时会失败,导致订单系统也发生故障。
问题二(系统的可拓展性):如果现在需要新增一个日志系统来统计订单情况,需要订单系统将订单消息也发送给日志系统,因此需要在订单系统上做改动。
可以看到,以接口耦合传递消息的传统方式导致两个问题:一是一个系统失效会导致另一个系统的失效或异常;二是在拓展系统时,需要经常对原系统的代码做改动。
解决方案:以消息队列的方式传递信息,能够使系统解耦。订单系统向消息队列传递信息时,它不再需要调用别的系统接口,因此其他系统的失效不会对它有影响;它也不关心消息什么时候会被哪个系统取走,当系统拓展时,它不再需要调用新的接口,新增的消费者会直接从消息队列中获取消息。
-
异步消息
具体场景:用户在系统A进行一个操作,系统A在得到数据后需要串行调用系统B、C、D的接口发送数据。
public class A{ public void operation(){ getData();//execution time:200ms syncDataWithB();//execution time:200ms syncDataWithC();//execution time:200ms syncDataWithD();//execution time:200ms } }
问题:用户在这个操作过程中,需要等待系统A串行调用完所有信息发送接口后才能完成操作,如果收信方很多、接口的执行时间长,用户需要等待的时间也很长,系统的响应会很慢。
解决方案:系统A如果使用消息队列来传递信息,只需在得到数据后,将数据上传消息队列,收信方的数量与收信时间不会影响到系统A的响应速度。
public class A{ public void operation(){ getData();//execution time:200ms sendMessage();//execution time:50ms } }
-
流量削峰
具体场景:一组特定用户需要使用数据库,短时间内发出了大量的数据库请求。
问题:假设现在对数据库的请求每秒生产10000条,但数据库每秒只能处理2000条。如果过多的请求直接打到数据库,数据库会直接崩溃。
解决方案:使用消息队列获取并存放数据库请求,数据库以一个可以维持正常运行的速率获取消息队列里面的请求并处理,多余的请求在消息队列里持久化并堆积,达到流量削峰的目的。
MQ的缺点:
- 增加系统的复杂性。系统之间传递数据时需要考虑消息队列可能产生的问题,如消息丢失、消息重复、消息顺序等。
- 降低系统的可用性。各个系统强依赖于消息队列,消息队列的可用性就变得非常关键了。如果消息队列崩溃,其他依赖于消息队列的系统也会崩溃。
- 消息一致性问题。多个系统依赖一个系统的消息,如果部分系统消费成功,部分失败,可能导致数据不一致的问题。
解决方案:
-
保证高可用性:
搭建集群,RabbitMQ的集群不同于RocketMQ,Kafka的分布式架构,使用的是主从架构。集群搭建的模式有两种,分别是也有普通集群和镜像集群模式。
-
如何处理重复消息:
保证消息的幂等性,意思就是说对相同资源的一次请求或者多次请求得到的结果是一致的
-
如何预防消息积压:
一般是下游系统来不及处理上游发送的消息,才会导致消息堆积。扩容 Consumer 的实例数量的同时,必须同步扩容topic的分区(也叫队列)数量,确保 Consumer 的实例数和分区数量是相等的。如果 Consumer 的实例数量超过分区数量,这样的扩容实际上是没有效果的。因为对于消费者来说,在每个分区上实际上只能支持单线程消费。
-
系统发生了消息积压,该如何处理?
快速定位消息积压的原因。要么是发送变快了,要么是消费变慢了。通过监控数据,很容易确定是哪种原因。如果是单位时间发送的消息增多,比如说是赶上大促或者抢购,短时间内不太可能优化消费端的代码来提升消费性能,唯一的方法是通过扩容消费端的实例数来提升总体的消费能力。
如果短时间内没有足够的服务器进行扩容,考虑将系统降级,关闭一些不重要的业务,减少发送数据量。
-
确保消息不会丢失:
生产阶段
在编写发送消息代码时,需要注意,正确处理返回值或者捕获异常,就可以保证这个阶段的消息不会丢失。另一种方式,如果MQ Server端无响应,生产端需要支持重试,当然Broker需要支持幂等。
存储阶段
配置 Broker 参数,在收到消息后,将消息写入磁盘后再给 Producer 返回确认响应。如果是 Broker 是由多个节点组成的集群,配置 Broker 参数,至少将消息发送到 2 个以上的节点,再给客户端回复发送确认响应。
消费阶段
客户端从 Broker 拉取消息后,执行用户的消费业务逻辑,成功后,才会给 Broker 发送消费确认响应。
-
如何保证消息的严格顺序?
1.2 MQ的评价指标
- 单机吞吐量:单位时间内成功地传送数据的数量
- 时效性:传输数据的所要花费的时间
- topic数量对吞吐量的影响:在互联网企业的实际生产环境中,Topic数量和分区都会比较多,这就要求消息中间件在多Topic共存的时候,依然能够保证服务的稳定性
- 可用性:是否保证高可用性
- 消息可靠性:消息是否会丢失
- 支持的协议:
- 支持的模型:
- 多语言支持:
- 社区的活跃度:
- 学习成本:
- 首次部署难度:
1.3 主流的消息队列中间件
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 十万级,高吞吐量 | 十万级,高吞吐量 |
时效性 | ms级 | μs级 | ms级 | ms级以内 |
可用性 | 高(主从架构) | 高(主从架构 ) | 非常高(分布式架构) | 非常高(分布式架构,多副本) |
开发语言 | Java | Erlang | Java | Scala |
消息可靠性 | 有slave做数据备份 | 有slave做备份 | 参数优化后可以做到0丢失 | 数据可靠,有replica机制,容错容灾 |
生产者-消费者模型 | 支持 | 支持 | 支持 | 支持 |
发布-订阅模型 | 支持 | 支持 | 支持 | 支持 |
请求-响应模型 | 支持 | 支持 | 支持 | - |
多语言支持 | 支持,Java优先 | 语言无关 | 只支持Java | 支持,Java优先 |
持久化能力 | 内存、文件、数据库 | 支持数据堆积,但数据堆积会影响生产速率 | 磁盘文件 | 磁盘文件,理论上可以做到无限消息堆积。 |
社区支持 | 社区不活跃 | 社区活跃 | 社区活跃 | 社区活跃 |
部署难度 | 纯Java程序,只需支持Java虚拟机即可部署 | 易于部署,多语言客户端 | 需要Java JDK、git、maven,部署相对复杂 | - |
部署方式 | 独立、嵌入 | 独立 | 独立 | - |
管理 | 面向开发者的管理界面,可以使用命令行脚本 | 易用的用户界面 | 提供CLI管理工具 | 命令行以及第三方管理界面Kafka-Manager |
特点 | 功能齐全,被大量开源项目使用,历史久 | 由于Erlang语言的并发能力,性能很好 | 各个环节分布式扩展设计,主从HA;支持上万个队列;多种消费模式;性能很好 | 支持简单的MQ功能,不支持事务,在大数据领域的实时计算以及日志采集被大规模使用 |
1.3.1 ActiceMQ
ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。
优点:
- 支持来自Java,C,C ++,C#,Ruby,Perl,Python,PHP的各种跨语言客户端和协议;
- 完全支持JMS客户端和Message Broker中的企业集成模式;
- 支持许多高级功能,如消息组,虚拟目标,通配符和复合目标;
- 完全支持JMS 1.1和J2EE 1.4,支持瞬态,持久,事务和XA消息;
- Spring支持,以便ActiveMQ可以轻松嵌入到Spring应用程序中,并使用Spring的XML配置机制进行配置;
- 专为高性能集群,客户端 - 服务器,基于对等的通信而设计;
- 支持可插拔传输协议,例如in-VM,TCP,SSL,NIO,UDP,多播,JGroups和JXTA传输;
- 使用JDBC和高性能日志支持非常快速的持久性。
缺点:
- 官方社区现在对ActiveMQ 5.x维护越来越少,较少在大规模吞吐的场景中使用。
1.3.2 RabbitMQ
RabbitMQ 2007年发布,是一个在AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
优点:
- 异步消息传递:支持多种消息协议,消息队列,传送确认,灵活的路由到队列,多种交换类型;
- 支持几乎所有最受欢迎的编程语言:Java,C,C ++,C#,Ruby,Perl,Python,PHP等等;
- 可以部署为高可用性和吞吐量的集群;,跨多个可用区域和区域进行联合;
- 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件;
- 提供了一个易用的用户界面,使得用户可以监控和管理消息Broker,社区活跃度高。
缺点:
- erlang开发,很难去看懂源码,基本职能依赖于开源社区的快速维护和修复bug,不利于做二次开发和维护;
- RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重;
- 需要学习比较复杂的接口和协议,学习和维护成本较高。
总结:
- 结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。不过RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug。
- 如果你的数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ。
1.3.3 RocketMQ
RocketMQ是阿里开源的消息中间件,纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是简单的复制,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景,支撑了阿里多次双十一活动。
优点:
- 支持发布/订阅(Pub/Sub)和点对点(P2P)消息模型;
- 在一个队列中可靠的先进先出(FIFO)和严格的顺序传递;
- 支持拉(pull)和推(push)两种消息模式;
- 单一队列百万消息的堆积能力;
- 支持多种消息协议,如 JMS、MQTT 等;
- 可靠的FIFO和严格的有序消息传递在同一队列中;
- 灵活的分布式横向扩展部署架构,满足至少一次消息传递语义;
- 提供 docker 镜像用于隔离测试和云集群部署;
- 提供配置、指标和监控等功能丰富的 Dashboard。
缺点:
- 支持的客户端语言不多,目前是java及c++,其中c++不成熟
- 社区活跃度一般
- 没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码
总结:
- 天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。
- RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。
1.3.4 Kafka
Kafka是linkedin开源的MQ系统,主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,0.8开始支持复制,不支持事务,适合产生大量数据的互联网服务的数据收集业务。
优点:
- 高吞吐、低延迟:kakfa 最大的特点就是收发消息非常快,kafka 每秒可以处理几十万条消息,它的最低延迟只有几毫秒;
- 高伸缩性: 每个主题(topic) 包含多个分区(partition),主题中的分区可以分布在不同的主机(broker)中;
- 持久性、可靠性: Kafka 能够允许数据的持久化存储,消息被持久化到磁盘,并支持数据备份防止数据丢失,Kafka 底层的数据存储是基于 Zookeeper 存储的,Zookeeper 我们知道它的数据能够持久存储;
- 容错性: 非常高,kafka是分布式的,一个数据多个副本,某个节点宕机,Kafka 集群能够正常工作;
- 消息有序:消费者采用Pull方式获取消息,消息有序,通过控制能够保证所有消息被消费且仅被消费一次;
- 有优秀的第三方Kafka Web管理界面Kafka-Manager,在日志领域比较成熟,被多家公司和多个开源项目使用;
- 功能支持:功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用。
缺点:
- Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长;
- 使用短轮询方式,实时性取决于轮询间隔时间;
- 消费失败不支持重试;
- 支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
- 社区更新较慢。
总结:
- Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。
- 大型公司建议可以选用,如果有日志采集功能,肯定是首选kafka。