目录
消息队列简介
消息队列简单使用
这样发消息有什么好处呢?
消息队列有什么特性?
为什么需要消息队列?
消息队列的好处
业务解耦
最终一致性
广播
错峰与流控
顺序保证
消息队列的坏处
可用性降低
复杂度变高
一致性
消息队列核心概念
消息模式
PTP点对点
Pub/Sub发布订阅
推和拉的区别
常用消息队列介绍
RabbitMQ
ActiveMQ
RocketMQ
选择Rabbitmq的原因
Rocketmq为什么性能高
Kafka
为什么kafaka的吞吐量大
选型比较总结
在介绍消息队列之前,先看看消息队列在实际业务场景中的简单使用。
流程A在处理时,没有在当前线程同步的处理完,而是直接发送一条消息A1到消息队列中,然后消息队列过了一段时间,这段时间可能是几毫秒、几秒甚至几分钟都有可能,这个消息开始被处理,消息被处理的过程,其实就相当于流程A被处理,这是一个简单的模型,我们套上一个实际的业务场景来看一下。
例如:
业务需要下单成功时,给用户发送短信提示下单成功,如果没有消息队列的话,我们会选择同步调用发短信的接口并等待短信发送成功,正常情况下这看着没什么问题,但这时候我们可以假设一下,在发短信的中间出现的问题,比如短信接口实现出问题了或者说短信调用端超时了,又或者短信发送达到上限了等等情况。这时候我们是选择重新试几次还是放弃发送呢,还是选择把这个信息放入数据库,过一段时间再尝试看看呢?不管怎么样,这里的设计都会变的很复杂,当我们使用了消息队列会变成什么样子呢。
我们假设流程A就是要发短信的这件事情,这时候我们可以把发短信这个操作封装成一条消息发送到消息队列里,消息队列按照一定的顺序处理队列中的消息,某一个时刻开始处理刚收到的这个消息,它会通知一个服务去发送一条短信。
顺利的话,刚放进队列的马上会处理,处理时,一次性就发成功了,这样流程就ok了。
处理消息时,如果出现什么问题呢?比如刚刚说的短信接口出现问题了、超时了。这时候它可以选择把这个消息重新放到消息队列中等待处理,当然这里也有一些细节需要处理,比如在消息里面指定最少的执行时间、这样如果短信不断的发送失败,尝试的次数会少一些,不至于一直在循环尝试,另外还需要控制一下这里处理消息的速度,用刚才的场景来说,就是控制每秒发送短信的上限。还是有很多需要考虑的。
首先,我们通过消息队列完成了一个异步解耦合的过程,短信发送时,我们保证短信发送到消息队列中就可以了,接着就可以继续做发送短信后面的各种操作。
其次很明显的一点,我们的设计变的更简单了,我们不需要在下单的这个业务中过多考虑短信的问题,而是直接把短信发送给队列就可以了。
另外我们通过这里面的消息保证了最终一致性,我们通过这样的设计,保证了这条短信肯定会通知到用户,即使当前短信发送有问题,我们也可以通过消息队列来保证短信服务恢复后,将短信发送给用户,只是不那么及时而已。
最后一点,我们假设短信发送完成之后,还要发送邮件,有了消息队列,我们就不需要做同步等待了,可以直接做并行处理,下单的核心流程可以更快的结束。这样就很容易增加业务系统的异步处理能力,减少、甚至不可能出现并发现象。
有时候我们在网站上需要输入手机号获取验证码时,有时等了半天验证码还没有收到,算上时间,其实接口已经超时了,这时候有可能后台就是通过消息队列的方式来发送验证码短信,可能这时短信发送出现了问题,或者服务器的网络开了小差,或者消息队列里消息太多了。
消息队列的场景特别多,就拿下单而言,可以在提交订单时,基本操作做完就提示订单已提交。然后发送一条创建订单的消息发送到消息队列中,通过处理消息队列里的消息,去推进订单的状态,处理的过程中可以发送其他消息到消息队列里,随着消息的处理,用户在前端刷新订单状态时,就可以看到订单的实时状态了,有了这种设计,用户就不需要在提交订单时,一直等在那里。再比如我们在携程、去哪里等网站购买机票,其实是从第三方航空公司出票,而跟第三方网站的交互,通常不是在一个系统里进行的,这时候就可以选用消息队列来推进订单的处理了。
1. 业务无关:只做消息分发
一个具有普适性质的消息队列组件,它不需要考虑上层的业务模型,它只要做好消息的分发就可以了 。上层业务的不同模块反而需要依赖消息队列所定义的规范来进行通信。
2. FIFO:先发送先到达
3. 容灾:节点的动态增删和消息的持久化
4.性能:吞吐量提升,系统内部通信效率提高
生产 和 消费 的速度或稳定性不一致
消息队列作为抽象层,可以弥合双方的差异。消息是在两台计算机间传送的数据单位,消息可以是字符串,也可以是嵌入的对象,消息被发送队列中,消息队列是在消息的传输过程中保存消息的一个容器。
当你需要使用消息队列的时候,首先需要考虑它的必要性,可以使用消息队列的场景有很多,最常用的是业务解耦、最终一致性、广播与错峰流控等等。反之如果需要强一致性,关注业务逻辑的处理结果,那么RPC显得更为合适。那么什么是RPC呢?其实就是指远程调用。
解耦是消息队列里要解决的最本质的问题,所谓解耦,简单讲就是一个事务只关心核心的流程,而需要依赖其他系统但不那么重要的事情有通知即可,无需等待结果。基于消息的模型关心的是通知,而不是处理。
最终一致性指的是两个系统的状态保持一致,要么都成功要么都失败。当然了,有个时间的限制,理论上是越快越好,但实际上各种异常的情况下,可能会有一定的延迟来达到最终的一致状态,但最终两系统的最终状态是一样的。
所有跨jvm的一致性问题,从技术角度上讲,通用的解决方案有两个 一个是强一致性、一个是最终一致性。
强一致性是指分布式事务。
最终一致性主要是用记录和补偿的方式来处理,在做所有不确定的事情之前,先把不确定的事情记录下来,然后去做不确定的事情,它的结果通常分三种:成功、失败、不确定(超时等等、可以等价为失败),如果是成功就把记录的事情清理掉,如果是失败或者不确定,我们可以依靠定时任务等方式把所有失败的事情重新做一遍,直到成功为止。
最终一致性不是消息队列必备的特性,但确实可以依靠队列来做一致性的事情。需要注意的是,像kafka等消息队列它的设计在设计层面上具有丢消息的可能,比如定时刷盘,如果断电,会丢消息等等。哪怕只丢千分之一的消息,也要用其他手段来保证结果正确。
消息队列的基本功能之一就是进行广播,如果没有消息队列,每当有一个新的业务加入,我们都要调整一下新接口,有了消息队列,我们只需要关心消息是否送达到消息队列,新接入的接口订阅相关的消息,自己去做处理就可以了。
我们试想一下,上下层对于事情的处理能力是不同的,比如web前端每秒承受上千万的请求并不是神奇的事情,只需要多加点机器,搭建一些LVS负载均衡设备和nginx等即可。但数据库的处理却十分有限,即使使用了分库分表,单机的处理能力任然有限,出于成本的考虑,我们不能奢求数据库的机器数量追上前端。
这样的问题同样也出现在系统与系统之间,比如短信系统可能由于短板效应,速度卡在网关上,比如每秒几百次请求,它跟前端的并发量不是一个数量级的,但是用户晚一会收到短信,一般也不会影响多大,如果没有消息队列,两个系统之间通过协商滑动窗口等复杂的方案也不是说不能实现,但是呢,系统的复杂性会成指数型的增长,势必会在上层或者下层做些存储,并且要处理定时阻塞等一系列问题,而且每当处理能力有差异的时候,都需要单独开发一套逻辑来维护这套逻辑,所以利用中间系统(消息队列)来转储通信内容,并在下层系统有能力处理这些消息的时候再处理这些消息,是一套相对比较通用的方式。
总之消息队列不是万能的,对于需要强事务保证,而且延迟很敏感的,RPC远程调用是优于消息队列的。对于一些无关痛痒或者说对于别人重要,但对于自己不是很关心的事情可以用消息队列去做,支持最终一致性的消息队列能够用来处理延迟不那么敏感的分布式事务场景,而且相对与笨重的分布式事务,可能是更优的处理方式。
当上下层处理能力存在差距的时候,利用消息队列做通用的“漏斗”,在下层有能力处理的时候再进行分发,同时下层如果有很多系统关心你发出的通知的时候,也果断的使用消息队列来解决。
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka保证一个Partition内的消息的有序性。
服务中引入的依赖越多,引入的外部组件越多,维护的成本就越高,本来你只是S1调用S2、S3服务即可。引入了消息队列,消息队列打入失败怎么办?消息队列挂了怎么办?怎么保证消息队列的稳定性?这些就需要后面再说了!
消息重复消费怎么办?消息丢失怎么办?如果要求消费的消息有序又怎么搞?本来在一个S1里,这些问题都好解决,加了一个MQ,这下麻烦大了!
用户请求S1返回成功,是真的成功了吗?当然不是,这只是代表打入MQ成功了啊。但这是如果消费MQ的某个服务挂了怎么办?你怎么监控,怎么处理数据一致性问题,真是搞的头都大了。
Broker(消息服务器)
Broker的概念来自与Apache ActiveMQ,通俗的讲就是MQ的服务器。
Producer(生产者)
业务的发起方,负责生产消息传输给broker
Consumer(消费者)
业务的处理方,负责从broker获取消息并进行业务逻辑处理
Topic(主题)
发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅 者,实现消息的广播
Queue(队列)
PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收。
本地队列
本地队列按照功能可划分为初始化队列,传输队列,目标队列和死信队列。
初始化队列:用作消息触发功能。
传输队列:是暂存待传的消息,条件许可的情况下,通过管道将消息传送到其他的队列管理器。
目标队列:是消息的目的地,可以长期存放消息。
死信队列:如果消息不能送达目标队列,也不能再路由出去,则被自动放入死信队列保存。
别名队列&远程队列
是一个队列定义,用来指定远端队列管理器的队列。使用了远程队列,程序就不需要知道目标队列的位置。
模型队列
模型队列定义了一套本地队列的属性结合,一旦打开模型队列,队列管理器会按照这些属性动态地创建出一个本地队列。
Message(消息体)
根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输
点对点模型用于消息生产者和消息消费者之间点到点的通信。
点对点模式包含三个角色:
消息队列(Queue)
发送者(Sender)
接收者(Receiver)
每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,可以放在内存 中也可以持久化,直到他们被消费或超时。
特点:
每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)
发送者和接收者之间在时间上没有依赖性
接收者在成功接收消息之后需向队列应答成功
利用FIFO先进先出的特性,可以保证消息的顺序性。
发布订阅模型包含三个角色:
主题(Topic)
发布者(Publisher)
订阅者(Subscriber)
多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。
特点:
每个消息可以有多个消费者:和点对点方式不同,发布消息可以被所有订阅者消费
发布者和订阅者之间有时间上的依赖性。
针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
为了消费消息,订阅者必须保持运行的状态。
其他3个都支持推拉,kafaka只支持拉模式
本部分主要介绍四种常用的消息队列(RabbitMQ/ActiveMQ/RocketMQ/Kafka)的主要特性、优点、缺点。
RabbitMQ 2007年发布,是一个在AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
主要特性:
可靠性: 提供了多种技术可以让你在性能和可靠性之间进行权衡。这些技术包括持久性机制、投递确认、发布者证实和高可用性机制;
灵活的路由: 消息在到达队列前是通过交换机进行路由的。RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做RabbitMQ的插件来使用;
消息集群:在相同局域网中的多个RabbitMQ服务器可以聚合在一起,作为一个独立的逻辑代理来使用;
队列高可用:队列可以在集群中的机器上进行镜像,以确保在硬件问题下还保证消息安全;
多种协议的支持:支持多种消息队列协议;
服务器端用Erlang语言编写,支持只要是你能想到的所有编程语言;
管理界面: RabbitMQ有一个易用的用户界面,使得用户可以监控和管理消息Broker的许多方面;
跟踪机制:如果消息异常,RabbitMQ提供消息跟踪机制,使用者可以找出发生了什么;
插件机制:提供了许多插件,来从多方面进行扩展,也可以编写自己的插件
使用RabbitMQ需要:
ErLang语言包
RabbitMQ安装包
RabbitMQ可以运行在Erlang语言所支持的平台之上:
Solaris
BSD
Linux
MacOSX
TRU64
Windows NT/2000/XP/Vista/Windows 7/Windows 8
Windows Server 2003/2008/2012
Windows 95, 98
VxWorks
优点:
由于erlang语言的特性,mq 性能较好,高并发;
健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
有消息确认机制和持久化机制,可靠性高;
高度可定制的路由;
管理界面较丰富,在互联网公司也有较大规模的应用;
社区活跃度高;
缺点:
尽管结合erlang语言本身的并发优势,性能较好,但是不利于做二次开发和维护;
实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队。此特性使得RabbitMQ易于使用和部署,但是使得其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大;
需要学习比较复杂的接口和协议,学习和维护成本较高;
ActiveMQ是由Apache出品,ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现。它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。
主要特性:
服从 JMS 规范:JMS 规范提供了良好的标准和保证,包括:同步或异步的消息分发,一次和仅一次的消息分发,消息接收和订阅等等。遵从 JMS 规范的好处在于,不论使用什么 JMS 实现提供者,这些基础特性都是可用的;
连接性:ActiveMQ 提供了广泛的连接选项,支持的协议有:HTTP/S,IP 多播,SSL,STOMP,TCP,UDP,XMPP等等。对众多协议的支持让 ActiveMQ 拥有了很好的灵活性。
支持的协议种类多:OpenWire、STOMP、REST、XMPP、AMQP ;
持久化插件和安全插件:ActiveMQ 提供了多种持久化选择。而且,ActiveMQ 的安全性也可以完全依据用户需求进行自定义鉴权和授权;
支持的客户端语言种类多:除了 Java 之外,还有:C/C++,.NET,Perl,PHP,Python,Ruby;
代理集群:多个 ActiveMQ 代理可以组成一个集群来提供服务;
异常简单的管理:ActiveMQ 是以开发者思维被设计的。所以,它并不需要专门的管理员,因为它提供了简单又使用的管理特性。有很多中方法可以监控 ActiveMQ 不同层面的数据,包括使用在 JConsole 或者 ActiveMQ 的Web Console 中使用 JMX,通过处理 JMX 的告警消息,通过使用命令行脚本,甚至可以通过监控各种类型的日志。
使用ActiveMQ需要:
Java JDK
ActiveMQ安装包
ActiveMQ可以运行在Java语言所支持的平台之上。
优点:
跨平台(JAVA编写与平台无关有,ActiveMQ几乎可以运行在任何的JVM上)
可以用JDBC:可以将数据持久化到数据库。虽然使用JDBC会降低ActiveMQ的性能,但是数据库一直都是开发人员最熟悉的存储介质。将消息存到数据库,看得见摸得着。而且公司有专门的DBA去对数据库进行调优,主从分离;
支持JMS :支持JMS的统一接口;
支持自动重连;
有安全机制:支持基于shiro,jaas等多种安全配置机制,可以对Queue/Topic进行认证和授权。
监控完善:拥有完善的监控,包括Web Console,JMX,Shell命令行,Jolokia的REST API;
界面友善:提供的Web Console可以满足大部分情况,还有很多第三方的组件可以使用,如hawtio;
缺点:
社区活跃度不及RabbitMQ高;
根据其他用户反馈,会出莫名其妙的问题,会丢失消息;
目前重心放到activemq6.0产品-apollo,对5.x的维护较少;
不适合用于上千个队列的应用场景;
RocketMQ出自 阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进,消息可靠性上比 Kafka 更好。RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。
主要特性:
是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点;
Producer、Consumer、队列都可以分布式;
Producer向一些队列轮流发送消息,队列集合称为Topic,Consumer如果做广播消费,则一个consumer实例消费这个Topic对应的所有队列,如果做集群消费,则多个Consumer实例平均消费这个topic对应的队列集合;
能够保证严格的消息顺序;
提供丰富的消息拉取模式;
高效的订阅者水平扩展能力;
实时的消息订阅机制;
亿级消息堆积能力;
较少的依赖;
使用RocketMQ需要:
Java JDK
安装git、Maven
RocketMQ安装包
RocketMQ可以运行在Java语言所支持的平台之上。
优点:
单机支持 1 万以上持久化队列
RocketMQ 的所有消息都是持久化的,先写入系统 PAGECACHE,然后刷盘,可以保证内存与磁盘都有一份数据,
访问时,直接从内存读取。
模型简单,接口易用(JMS 的接口很多场合并不太实用);
性能非常好,可以大量堆积消息在broker中;
支持多种消费,包括集群消费、广播消费等。
各个环节分布式扩展设计,主从HA;
开发度较活跃,版本更新很快。
缺点:
支持的客户端语言不多,目前是java及c++,其中c++不成熟;
RocketMQ社区关注度及成熟度也不及前两者;
没有web管理界面,提供了一个CLI(命令行界面)管理工具带来查询、管理和诊断各种问题;
没有在 mq 核心中去实现JMS等接口;
1、Kafka可以保证顺序处理消息,RabbitMQ相对较弱。
2、在消息路由和过滤方面,RabbitMQ提供了更好的支持。
3、RabbitMQ有消息存活时间(TTL)和延迟/预定消息功能,Kafka没有。
4、在消息留存方面,RabbitMQ消息一旦消费成功就会删除,反之处理失败则放回,但Kafka会保留消息,根据超时时间来删除消息,所以Kafka可以反复消费消息。
5、在容错处理上,RabbitMQ提供了诸如交付重试和死信交换器(DLX)来处理消息处理故障,相反,Kafka没有提供这种开箱即用的机制,需要在应用层提供和实现消息的重试机制。
6、在伸缩方面,通常Kafka(使用顺序磁盘I/O来提供性能)被认为比RabbitMQ有更优越的性能,从Kafka使用分区的架构上看,它在横向扩展上会优于RabbitMQ,当然,RabbitMQ在纵向扩展上会有更多的优势,而且在吞吐量上,Kafka每秒可处理十几万消息,RabbitMQ每秒可处理几万消息,如果系统达不到百万级用户量,可以不关心伸缩性问题。
7、RabbitMQ(智能代理和傻瓜式消费者模式)比Kafka(傻瓜式代理和智能消费者模式)在消费者复杂度上更简单。
8、优先选择RabbitMQ的条件:
高级灵活的路由规则
消息时序控制(控制消息过期或消息延迟)
高级的容错处理能力,在消费者更有可能处理消息不成功的情景中(瞬时或持久)
更简单的消费者实现
优先选择Kafka的条件:
严格的消息顺序
延长消息留存时间,包括过去消息重放的可能
传统解决方案无法满足的高伸缩能力
1.顺序写
顺序写比随机写的性能会高很多,不会有大量寻址的过程
2.异步刷盘
相比较于同步刷盘,异步刷盘的性能会高很多
3.零拷贝
使用mmap的方式进行零拷贝,提高了数据传输的效率
Apache Kafka是一个分布式消息发布订阅系统。它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),,之后成为Apache项目的一部分。Kafka系统快速、可扩展并且可持久化。它的分区特性,可复制和可容错都是其不错的特性。
主要特性:
快速持久化,可以在O(1)的系统开销下进行消息持久化;
高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;
.完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;
支持同步和异步复制两种HA;
支持数据批量发送和拉取;
zero-copy:减少IO操作步骤;
数据迁移、扩容对用户透明;
无需停机即可扩展机器;
其他特性:严格的消息顺序、丰富的消息拉取模型、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制;
使用Kafka需要:
Java JDK
Kafka安装包
优点:
客户端语言丰富,支持java、.net、php、ruby、python、go等多种语言;
性能卓越,单机写入TPS约在百万条/秒,消息大小10个字节;
提供完全分布式架构, 并有replica机制, 拥有较高的可用性和可靠性, 理论上支持消息无限堆积;
支持批量操作;
消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;
有优秀的第三方Kafka Web管理界面Kafka-Manager;
在日志领域比较成熟,被多家公司和多个开源项目使用;
缺点:
Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
使用短轮询方式,实时性取决于轮询间隔时间;
消费失败不支持重试;
支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
社区更新较慢;
页缓存技术+磁盘顺序写
Kafka的消息数据是写在硬盘上的,保证了消息数据的可靠性,但写硬盘还能保证几十万条/秒的消息处理速度,是怎么做到的?答案是基于操作系统的页缓存和磁盘顺序写来实现的。
页缓存Page Cache—操作系统自己管理的内存缓存(os cache)。Kafka在写入消息时是直接写入页缓存,然后由操作系统决定什么时候把页缓存里的数据刷入磁盘文件中。这样一来,消息写入性能就变成了写内存,不是在写磁盘,请看下图。
常规的磁盘写入都是随机写,随便找到文件的某个位置来写数据,这样的性能非常差,但是通过追加文件末尾按照顺序的方式来写数据的话,其写入性能跟写内存的性能都相差无几的,Kafak就是采用顺序写的方案。再加上页缓存的应用,才能做到在普通服务器上每秒写入几十万条消息,实现了数据写入的超高性能 。
零拷贝技术
解决了写入问题,那消息读取呢?频繁的从磁盘读数据然后发给消费者,性能又是如何保证的?Kafka为了解决这个问题,在读数据的时候是引入零拷贝技术。
先看下图是常规的硬盘读写流程,操作系统读取硬盘数据后放在OS Cache,然后需要拷贝一次到Kafka进程,然后Kafka再将数据拷贝到Socket缓存才能发送到网卡,这样流程的性能当然没有保障。
再看下图, Kafka的设计是直接将操作系统OS Cache中的数据发送到网卡,跳过了两次拷贝数据的步骤,Socket缓存中仅仅会拷贝一个文件描述符过去,不会拷贝数据到Socket缓存,大大提升了数据读取性能。
如果要看每个维度具体的比较,可以看
博客园
简略的,可以看
还可以参考
该如何选择消息队列? - 知乎