1.消息队列MQ
1.1. 什么是MQ
-
MQ
(message queue
),消息队列,本质是个队列,队列特点,FIFO
先进先出,消息队列 -
顾名思义,队列中存放的内容是
message
-
还是一种跨进程的通信机制,用于上下游传递消息
-
在互联网架构中,
MQ
是一种非常常见的上下游"逻辑解耦+物理解耦"的消息通信服务,使用了MQ
之后,消息发送上游只需要依赖MQ
,不用依赖其他服务
1.2 为什么要用MQ
1.2.1 流量消峰
举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单没问题,正常时段用户下单一秒后就能返回结果。
但是在高峰期,如果有两万次下单,订单系统是处理不了的,只能限制订单超过一万后不允许用户下单。使用消息队列做缓冲,这样可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。
1.2.2 应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。
用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。
当转变成基于MQ
的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中间用户感受不到物流系统的故障,提升系统的可用性。
1.2.3 异步处理
在日常开发中,有些服务间调用需要是异步的。
例如A
调用B
,B
需要消耗很长时间,但是A
需要知道B
什么时候可以执行完,以前一般有两种方式:
-
A
过一段时间去调用B
的api
查询 -
A
提供一个回调api
,B
执行完之后调用api
通知A
这两种实现方式都不是很优雅,使用消息总线,可以很方便解决这个问题
A
调用B
后,只需要监听B
处理完成的消息,当B
处理完成后,会发送一条消息给MQ
,MQ
会将此消息转发给A
。这样A
服务既不用循环调用B
的查询api
,也不用提供回调api
。同样B
服务也不用做这些操作。A
服务还能及时的得到异步处理成功的消息。
2. MQ几种协议
对于MQ
,可能都有所了解,ActiveMQ
、RabbitMQ
、RocketMQ
、Kafka
等等各种以及 JMS
、AMQP
等各种协议,然而这些MQ
各自都有什么特点呢?先来了解下MQ
常用的几种协议。
2.1 JMS
2.1.1 JMS 介绍
JMS
全称 Java Message Service
,JMS
是 JavaEE
的消息服务接口,JMS
主要有两个版本:1.1
、2.0
两者相比,后者主要是简化了收发消息的代码
考虑到MQ
是一个非常常用的工具,所以 JavaEE
为此制定了专门的规范 JMS
JMS
作为规范,它只是一套接口,并不包含具体的实现,如果我们要使用 JMS
,那么还需要对应的实现
2.1.2 JMS 模型
JMS
消息服务支持两种消息模型:
- 点对点或队列模型
- 发布/订阅模型
在点对点或队列模型下,一个生产者向一个特定的队列发布消息,一个消费者从该队列中读取消息。这里,生产者知道消费者的队列,并直接将消息发送到对应的队列。这是一种点对点的消息模型,这种模式被概括为:
- 只有一个消费者将获得消息
- 生产者不需要在消费者消费该消息期间处于运行状态,消费者也同样不需要在消息发送时处于运行状态,即消息的生产者和消费者是完全解耦的
- 每一个成功处理的消息都由消息消费者签收。
发布者/订阅者模型支持向一个特定的消息主题发布消息,消费者则可以定义自己感兴趣的主题,这是一种点对面的消息模型,这种模式可以被概括为:
- 多个消费者可以消费消息
- 在发布者和订阅者之间存在时间依赖性,发布者需要创建一个订阅(
subscription
),以便客户能够订阅;订阅者必须保持在线状态以接收消息;当然,如果订阅者创建了持久的订阅,那么在订阅者未连接时,消息生产者发布的消息将会在订阅者重新连接时重新发布
2.1.3 JMS 实现
开源的支持 JMS
的MQ
有:
Kafka
Apache ActiveMQ
2.2 AMQP
2.2.1 AMQP 简介
另一个协议是 AMQP
,2006
年,AMQP
规范发布。2007
年,Rabbit
技术公司基于 AMQP
标准开发的 RabbitMQ 1.0
发布。目前 RabbitMQ
的最新版本为 3.9.13
,基于AMQP 0-9-1
在 AMQP
协议中,消息收发涉及到如下一些概念:
Broker
:接收和分发消息的应用,我们日常所用的RabbitMQ
就是一个Message Broker
Virtual host
:出于多租户和安全因素设计的,把AMQP
的基本组件划分到一个虚拟的分组中,类似于网络中的namespace
概念。当多个不同的用户使用同一个RabbitMQ
提供的服务时,可以划分出多个vhost
,每个用户在自己的vhost
中创建exchange/queue
等Connection
:publisher
/consumer
和broker
之间的TCP
连接,断开连接的操作只会在client
端进行,Broker
不会断开连接,除非出现网络故障或broker
服务出现问题Channel
:如果每一次访问RabbitMQ
都建立一个Connection
,在消息量大的时候建立TCP Connection
的开销将是巨大的,效率也较低。Channel
是在Connection
内部建立的逻辑连接,如果应用程序支持多线程,通常每个Thread
创建单独的Channel
进行通讯,AMQP method
包含了Channel id
帮助客户端和Message Broker
识别Channel
,所以Channel
之间是完全隔离的。Channel
作为轻量级的Connection
极大减少了操作系统建立TCP Connection
的开销Exchange
:Message
到达Broker
的第一站,根据分发规则,匹配查询表中的routing key
,分发消息到queue
中去。常用的类型有:direct
(点对点),topic
(发布订阅) 以及fanout
(广播)Queue
:消息最终被送到这里等待Consumer
取走,一个Message
可以被同时拷贝到多个queue
中Binding
:Exchange
和Queue
之间的虚拟连接,binding
中可以包含routing key
,Binding
信息被保存到Exchange
中的查询表中,作为Message
的分发依据
2.2.2 AMQP 实现
实现了 AMQP
协议的一些具体的MQ
Apache ActiveMQ
RabbitMQ
RocketMQ
ActiveMQ
不仅支持 JMS
,也支持 AMQP
,另外还有大家熟知的阿里出品的 RocketMQ
,这个是自定义了一套协议,社区也提供了 JMS
2.3 MQTT
MQTT
(Message Queuing Telemetry Transport
,消息队列遥测传输)是 IBM
开发的一个即时通讯协议,目前看来算是物联网开发中比较重要的协议之一,该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和 Actuator
的通信协议,它的优点是格式简洁、占用带宽小、支持移动端通信、支持 PUSH
、适用于嵌入式系统
2.4 XMPP
XMPP
(Extensible Messaging and Presence Protoco
,可扩展消息处理现场协议)是一个基于 XML
的协议,多用于即时消息(IM
)以及在线现场探测,适用于服务器之间的准即时操作。核心是基于 XML
流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。 它的优点是通用公开、兼容性强、可扩展、安全性高,缺点是 XML
编码格式占用带宽大
2.5 JMS Vs AMQP
日常接触较多的应该是 JMS
和 AMQP
协议,既然 JMS
和 AMQP
都是协议,看下两者区别
3 MQ分类
3.1 ActiveMQ
ActiveMQ
是 Apache
下的一个子项目,使用完全支持 JMS1.1
和J2EE1.4
规范的JMS Provider
实现,少量代码就可以高效地实现高级应用场景,并且支持可插拔的传输协议,如:in-VM
, TCP
, SSL
, NIO
, UDP
, multicast
, JGroups and JXTA transports
。
现在的 ActiveMQ 分为两个版本:
ActiveMQ Classic
ActiveMQ Artemis
这里的 ActiveMQ Classic
就是原来的 ActiveMQ
,而 ActiveMQ Artemis
是在 RedHat
捐赠的 HornetQ
服务器代码的基础上开发的,两者代码完全不同,后者支持 JMS2.0
,使用基于 Netty
的异步 IO
,大大提升了性能,更为神奇的是,后者不仅支持 JMS
协议,还支持 AMQP
协议、STOMP
以及 MQTT
。
- 优点:单机吞吐量万级,时效性
ms
级,可用性高,基于主从架构实现高可用性,消息丢失概率很低 - 缺点:官方社区现在对
ActiveMQ
维护越来越少,高吞吐量场景较少使用
3.2 RabbitMQ
RabbitMQ
算是 AMQP
体系下最为重要的产品了,它基于 Erlang
语言开发实现
RabbitMQ
支持 AMQP
、XMPP
、SMTP
、STOMP
等多种协议,功能强大,适用于企业级开发
来看一张 RabbitMQ
的结构图:
消息流程:生产者发送消息->消息交换机->把消息路由给消息队列->消费者消费消息
优点:
- 由于
erlang
语言的高并发特性,性能较好 - 吞吐量到万级,
MQ
功能比较完备,健壮、稳定、易用、跨平台、支持多种语言 如:Python
、Ruby
、.NET
、Java
、JMS
、C
、PHP
、ActionScript
、XMPP
、STOMP
等,支持AJAX
文档齐全 - 开源提供的管理界面非常棒,用起来很好用,社区活跃度高
- 更新频率相当高
缺点:商业版需要收费,学习成本较高
3.3 RocketMQ
RocketMQ
是阿里开源的一款分布式消息中间件,原名 Metaq
,从3.0
版本开始改名为 RocketMQ
,是阿里参照 Kafka
设计思想使用 Java
语言实现的一套 MQ
。RocketMQ
将阿里内部多款 MQ
产品(Notify
、Metaq
)进行整合,只维护核心功能,去除了所有其他运行时依赖,保证核心功能最简化,在此基础上配合阿里上述其他开源产品实现不同场景下 MQ
的架构,被阿里巴巴广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog
分发等场景。RocketMQ
具有以下特点:
- 保证严格的消息顺序。
- 提供针对消息的过滤功能。
- 提供丰富的消息拉取模式。
- 高效的订阅者水平扩展能力。
- 实时的消息订阅机制。
- 亿级消息堆积能力
优点:
- 单机吞吐量十万级,可用性非常高,分布式架构,消息可以做到0丢失,
MQ
功能较为完善 - 还是分布式的,扩展性好,支持10亿级别的消息堆积,不会因为堆积导致性能下降,
- 源码是
Java
可以自己阅读源码,定制自己公司的MQ
缺点:
- 支持的客户端语言不多,目前是
Java
及C++
- 社区活跃度一般,没有在
MQ
核心中去实现JMS
等接口,有些系统要迁移需要修改大量代码
3.4 Kafka
Kafka
是 Apache
下的一个开源流处理平台,由 Scala
和 Java
编写。Kafka
是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作(网页浏览,搜索和其他用户的行动)流数据。Kafka
的目的是通过 Hadoop
的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息。
Kafka
具有以下特性:
- 快速持久化:通过磁盘顺序读写与零拷贝机制,可以在
O(1)
的系统开销下进行消息持久化 - 高吞吐:在一台普通的服务器上既可以达到
10W/s
的吞吐速率 - 高堆积:支持
topic
下消费者较长时间离线,消息堆积量大
- 完全的分布式系统:
Broker
、Producer
、Consumer
都原生自动支持分布式,通过Zookeeper
可以自动实现更加复杂的负载均衡 - 支持
Hadoop
数据并行加载
优点:
- 性能卓越,单机写入
TPS
约在百万条/秒,最大的优点,就是吞吐量高 - 时效性
ms
级可用性非常高,kafka
是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
- 消费者采用
Pull
方式获取消息,消息有序,通过控制能够保证所有消息被消费且仅被消费一次 - 有优秀的第三方
Kafka Web
管理界面Kafka-Manager
- 在日志领域比较成熟,被多家公司和多个开源项目使用
- 功能支持:功能较为简单,主要支持简单的
MQ
功能,在大数据领域的实时计算以及日志采集被大规模使用
缺点:
Kafka
单机超过64
个队列/分区,Load
会发生明显的飙高现象,队列越多,load
越高,发送消息响应时间变长,使用短轮询方式,实时性取决于轮询间隔时间,消费失败不支持重试- 支持消息顺序,但是一台代理宕机后,就会产生消息乱序,社区更新较慢
3.5 比较
最后,对以上四种MQ
做个比较总结:
4 MQ选择
4.1 Kafka
Kafka
主要特点是基于Pull
的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。大型公司建议可以选用,如果有日志采集功能,肯定是首选kafka
了
4.2 RocketMQ
天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。RoketMQ
在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ
4.3 RabbitMQ
结合erlang
语言本身的并发优势,性能好时效性微秒级,社区活跃度也比较高,管理界面用起来十分方便,如果你的数据量没有那么大,中小型公司优先选择功能比较完备的RabbitMQ
--end--