一、 消息队列(MQ)
消息队列(Message Queue)在互联网公司使用的越来越多,主要用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。消息队列实现系统之间的双向解耦,生产者往消息队列中发送消息,消费者从队列中拿取消息并处理,生产者不用关心是谁来消费,消费者不用关心谁在生产消息,从而达到系统解耦的目的,也大大提高了系统的高可用性和高并发能力。
目前在生产环境,使用较多的消息队列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、RocketMQ等。
二、 工作模式
2.1 点对点模式
每个消息只有一个消费者(Consumer),一旦被消费,便从消息队列中删除。
发送者和消费者间没有依赖性,发送者发送消息后,不管有没有消费者,并不会影响下一次发送消息。
2.2 发布订阅模式
每个消息可以有多个订阅者;每个订阅者都可以接收到主题的所有消息;
生产者依然不会关心有没有消费者,只管发送消息。
两种模型区别:一份消息是否能被多次消费;如果发布订阅模式只有一个订阅者,那么两个模型基本一样;所以发布订阅模型在功能层面是兼容点对点模式的;
三、 MQ应用场景
消息队列的应用场景较多,以前三个最为典型:
-
1.异步处理
-
2.应用解耦
-
3.流量削锋
-
4.消息通讯
-
5.排序保证
3.1 异步处理
假定用户注册后,需要发注册邮件和注册短信给注册用户,传统的做法有两种
-
1、串行的方式;
-
2、并行的方式 ;
(1)串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西。
(2)并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。
假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。
虽然并性已经提高的处理时间,但是,前面说过邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回。
(3)消息队列:引入消息队列后,把发送邮件、短信不是必须的业务逻辑进行异步处理。
由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。
3.2 应用解耦
假定双11购物节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口。
不过这种做法有一个缺点:订单系统和库存系统高耦合。当库存系统出现故障时,订单就会失败。
引入消息队列后:
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,获取下单消息,进行库操作。就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失。
3.3 流量削峰
流量削峰一般在秒杀活动中应用广泛。
秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
一般有两个作用:
-
1、可以控制活动人数,超过此一定阀值的订单直接丢弃
-
2、可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
1、用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面。
2、秒杀业务根据消息队列中的请求信息,慢慢做后续处理即可。
4 消息队列缺点
-
系统可用性降低:系统引入的外部依赖越多,越容易挂掉。本来就是 A 系统调用 BCD 三个系统的接口就好了,原本ABCD 四个系统好好的,没啥问题。你把加个 MQ 进来了 ,让A发布消息,BCD订阅消息,如果 MQ 出问题了呢?BCD的功能就都不会执行了。
-
系统复杂度提高:需要保证,消费者有没有重复消费信息?重要信息丢失怎么办?信息需要有先后顺序怎么办?一系列的问题都需要考虑并解决,此时业务逻辑的复杂度就上来了。
-
一致性问题:你访问 A 系统,处理完后给你返回成功,同时向消息队列发布信息,但这时你已经收到处理结果了,以为成功了。但如果 BCD 三个系统收到信息后,它们之间有的处理失败的,这时ABCD的数据就不一致了。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,系统复杂度提升了一个数量级,也许是复杂了 10 倍。
合理的使用消息队列,确实会给系统带来很大的优势。但也并不是所有的系统都适合加入消息队列。
5 常用消息中间件
5.1 ZeroMQ
ZeroMQ号称是“史上最快的消息队列”,基于c语言开发的,可以在任何平台通过任何代码连接,通过inproc、IPC、TCP、TIPC、多播传送消息,支持发布-订阅、推-拉、共享队列等模式,高速异步I/O引擎。
根据官方的说法,ZeroMQ是一个简单好用的传输层,像框架一样的可嵌入的socket类库,使Socket编程更加简单、简洁、性能更高,是专门为高吞吐量/低延迟的场景开发的。ZeroMQ与其他MQ有着本质的区别,它根本不是消息队列服务器,更类似与一个底层网络通讯库,对原有Socket API进行封装,在使用的时候引入对应的jar包即可,可谓是相当灵活。
同时,因为它的简单灵活,如果我们想作为消息队列使用的话,需要开发大量代码。而且,ZeroMQ不支持消息持久化,其定位并不是安全可靠的消息传输,所以还需要自己编码保证可靠性。简而言之一句话,ZeroMQ很强大,但是想用好需要自己实现。
5.2 Rabbitmq
RabbitMQ 2007年发布,是一个在AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
主要特性:
-
可靠性: 提供了多种技术可以让你在性能和可靠性之间进行权衡。这些技术包括持久性机制、投递确认、发布者证实和高可用性机制;
-
灵活的路由: 消息在到达队列前是通过交换机进行路由的。RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做RabbitMQ的插件来使用;
-
消息集群:在相同局域网中的多个RabbitMQ服务器可以聚合在一起,作为一个独立的逻辑代理来使用;
-
队列高可用:队列可以在集群中的机器上进行镜像,以确保在硬件问题下还保证消息安全;
-
多种协议的支持:支持多种消息队列协议;
-
多语言支持:服务器端用Erlang语言编写,支持只要是你能想到的所有编程语言;
-
管理界面: RabbitMQ有一个易用的用户界面,使得用户可以监控和管理消息Broker的许多方面;
-
跟踪机制:如果消息异常,RabbitMQ提供消息跟踪机制,使用者可以找出发生了什么;
-
插件机制:提供了许多插件,来从多方面进行扩展,也可以编写自己的插件;
优点:
-
RabbitMQ 使用erlang语言开发,由于erlang语言的特性,mq 性能较好,高并发;
-
健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
-
有消息确认机制和持久化机制,可靠性高;
-
高度可定制的路由;
-
管理界面较丰富,在互联网公司也有较大规模的应用;
-
社区活跃度高;
缺点:
-
-
尽管结合erlang语言本身的并发优势,性能较好,但是不利于做二次开发和维护;
-
实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队。此特性使得RabbitMQ易于使用和部署,但是使得其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大;
-
需要学习比较复杂的接口和协议,学习和维护成本较高;
-
5.3 ActiveMQ
ActiveMQ介于ZeroMQ和RabbitMQ之间。类似于ZeroMQ,它可以部署于代理模式和P2P(点对点)模式。类似于RabbitMQ,它易于实现高级场景,而且只需付出低消耗。被誉为消息中间件的“瑞士军刀”。
支持OpenWire、Stomp、AMQP v1.0、MQTT v3.1、REST、Ajax、Webservice等多种协议;完全支持JMS1.1和J2EE 1.4规范(事务、持久化、XA消息);支持持久化到数据库。但是ActiveMQ不够轻巧,而且对于队列较多的情况支持不好,据说还有丢消息的情况。
目前已经有了其下一代消息产品Apollo。
Apache称Apollo为最快、最强健的STOMP服务器。支持STOMP、AMQP、MQTT、OpenWire协议,支持Topic、Queue、持久订阅等消费形式,支持对消息的多种处理,支持安全性处理,支持REST管理API。。。功能列表很长,最大的弊病就是目前市场接收度不够,所以使用的并不广泛。
STOMP:简单"流"文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
5.4 kafka
Apache Kafka是一个分布式消息发布订阅系统。它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),之后成为Apache项目的一部分。Kafka系统快速、可扩展并且可持久化。它的分区特性,可复制和可容错都是其不错的特性。
主要特性:
-
快速持久化,可以在O(1)的系统开销下进行消息持久化;
-
高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;
-
.完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;
-
支持同步和异步复制两种HA;
-
支持数据批量发送和拉取;
-
zero-copy:减少IO操作步骤;
-
数据迁移、扩容对用户透明;
-
无需停机即可扩展机器;
-
其他特性:严格的消息顺序、丰富的消息拉取模型、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制;
优点:
-
客户端语言丰富,支持java、.net、php、ruby、python、go等多种语言;
-
性能卓越,单机写入TPS约在百万条/秒,消息大小10个字节;
-
提供完全分布式架构, 并有replica机制, 拥有较高的可用性和可靠性, 理论上支持消息无限堆积;
-
支持批量操作;
-
消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;
-
有优秀的第三方Kafka Web管理界面Kafka-Manager;
-
在日志领域比较成熟,被多家公司和多个开源项目使用;
缺点:
-
Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
-
使用短轮询方式,实时性取决于轮询间隔时间;
-
消费失败不支持重试;
-
支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
-
社区更新较慢;
5.5 RocketMQ
RocketMQ 是阿里巴巴在 2012 年开源的消息队列产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进,后来捐赠给 Apache 软件基金会,2017 正式毕业,成为 Apache 的顶级项目。RocketMQ 在阿里内部被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,Binglog 分发等场景。经历过多次双十一考验,它的性能、稳定性和可靠性都是值得信赖的。
RocketMQ 有着不错的性能,稳定性和可靠性,具备一个现代的消息队列应该有的几乎全部功能和特性,并且它还在持续的成长中。
RocketMQ 有非常活跃的中文社区,大多数问题可以找到中文的答案。RocketMQ 使用 Java 语言开发,源代码相对比较容易读懂,容易对 RocketMQ 进行扩展或者二次开发。
RocketMQ 对在线业务的响应时延做了很多的优化,大多数情况下可以做到毫秒级的响应,如果你的应用场景很在意响应时延,那应该选择使用 RocketMQ。
RocketMQ 的性能比 RabbitMQ 要高一个数量级,每秒钟大概能处理几十万条消息。
RocketMQ 的劣势是与周边生态系统的集成和兼容程度不够。
5.6 对比总结
根据上面的对比得出以下结论:
1)、中小型软件公司,建议选RabbitMQ
因为erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。正所谓,成也萧何,败也萧何!他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要
不考虑kafka的原因是,中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以kafka排除
不考虑rocketmq的原因是,rocketmq是阿里出品,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐
2)、大型软件公司,根据具体使用在rocketMQ和kafka之间二选一
大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。