文章目录
消息队列是个啥
概念解释
- 官方说法是,MQ(全称Message Queue)是一种进程间通信或同一进程的不同线程间的通信方式,队列就是一个消息容器
- 现实使用中,我们将消息队列称之为中间件,从它的名字就可以看出,消息队列不存储消息内容的本身,它只是消息的搬运工
背景了解
要理解MQ,就不得不了解一下RPC通信模型先
RPC通信模型
-
比较官方的解释一下: RPC(全程Remote Procedure Call,远程过程调用),是一个可以像调用本地服务一样调用远程服务的计算机通信协议。
-
模型图:
这里面有必要解释一些术语:
服务提供者(provider):提供具体的调用方法的系统,根据上图也就是服务端,服务端才是方法真正的提供者
服务消费者(consumer):调用服务的系统,根据模型图,也就是客户端
序列化(Serialization):将对象转换为便于进行网络传输的二进制或文本数据的过程
反序列化(Deserialization):二进制或文本数据再还原为对象的过程 -
RPC调用步骤
1、建立通信:消费者要想调用提供者的方法,首先要和提供者建立通信连接,主要是通过客户端和服务器之间建立TCP连接实现的
2、服务寻址:寻址实现的是确定提供者的IP、端口号以及方法的名称
3、网络传输:消费者发起一个RPC调用时,将调用方法和参数的数据进行序列化传输给提供者,提供者将接收到的参数进行反序列化操作后执行方法
4、服务调用:提供者进行本地调用后得到了返回值,提供者将返回值进行序列化操作后,再通过网络传输将二进制数据发送回给消费者 -
简单总结一下: 了解了整个流程后,在我的理解里就剩下了一句话对客户端透明的远端服务调用,也就是客户端虽然实际调用的是服务端方法,但RPC通信模型会让客户端觉得调用的是本地方法
好,解释一下为什么了解MQ要先提RPC呢? 因为从本质上看,RPC一般对于客户端来说是一种同步的远程服务调用技术,而一般来说MQ是异步的远程调用
- RPC和MQ的区别
MQ特点
面向数据、生产者与消费者、有缓冲节点、异步、系统级/模块级通信
MQ适用场景
1、消息的发送者和消费者需要解耦(发送者和消费者都不再直接交互,而是通过中间件间接交互,实现了解耦)
2、发送者并不明确谁是消费者(也是因为发送者和消费者不进行直接交互)
3、发送者并不关心谁来消费信息(理由同上)
4、各个消费者可以从不同的角度入手处理消息
5、消费者的处理结果也不返回给发送者
6、消息的发送和处理是异步的
7、消息的关注者不止一个
MQ使用总结
也就是计算结果不要求立即返回给消息的发送者时,应该使用MQ,例如日志服务、业务监控服务
RPC特点
面向动作、请求响应模式、同步、对象级/函数级通信
RPC适用场景
1、客户端必须明确要调用哪个服务器
2、调用需要立即得到返回结果
3、架构简单
RPC使用总结
如果服务的调用需要在短期内返回结果时,并且同一个请求的关注者只有一个,这个时候就应该使用RPC
消息队列实质思路
MQ的实质就是一发一存一消费
这也是为什么叫MQ为中间件的缘故
MQ原始模型
原始模型的两个关键词:消息和队列
消息: 需要传输的数据,可以是最简单的文本字符串,也可以是自定义的复杂格式
队列: 一种先进先出的数据结构,在这里是存放消息的容器,入队即发消息的过程,出队即收消息的过程
过程: 其实看这个图就很清晰了,生产者将消息投递到一个叫做 队列 的容器中,然后再从容器中取出消息,最后转发给消费者
原始模型的进化
根据消息处理的特点,可以将MQ处理消息的模式分为两类
- 队列模型(又叫做点对点模型)
点对点模式(PTP):一个生产者发送的每一个消息,都只有一个消费者消费,看起来就像消息从一个点被传递到另一个点,也就是单播
它允许多个生产者向同一个队列里发送消息,但是只有一个消费者能拿到数据,如果有多个消费者,那么多个消费者是竞争者关系,最终一条消息只能被其中一个消费者接收到,读完即会被删除
(2)发布订阅模式(PubSub):一个生产者发送的每一个消息,都会发送到所有订阅了此队列的消费者,也就是广播
在发布订阅模型中,存放消息的容器变成了主题(主题也是一个队列),订阅者在接收消息之前需要先订阅主题,最终,每个订阅者都可以收到同一个主题的全量消息
可以看到和队列模式唯一不同的就是,发布订阅模式中的一份消息数据可以被多次消费
各类消息队列
消息队列中间件是分布式系统中的重要组件
目前使用较多的消息队列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、RocketMQ
略微介绍一下各位,有个印象:
ActiveMQ
特性
- Apache出品、最流行的、能力强劲的开源消息总线
- 完全支持JMS1.1和J2EE1.4规范,【这里是来自菜鸡的吐槽,我又不知道JMS是啥,计算机真是越学越多/(ㄒoㄒ)/~~。查询了一番,才知道JMS(Java Message Service)即java消息服务应用程序接口,是一个Java平台关于面向消息中间件的API,用在两个应用程序之间,或者分布式系统中发送消息,进行异步通信】,总之JMS提供了良好的标准和保证
- 广泛的连接选项,支持的协议众多有:HTTP、HTTPS、IP多播、SSL、STOMP、TCP、UDP、XMPP等等。促使ActiveMQ拥有很好的灵活性
- 多种持久化选择,且安全性完全可以依据用户需求进行自定义鉴权和授权
- 支持的客户端语言种类众多:Java、C++、.NET、Perl、PHP、Python、Ruby
- 代理集群,多个ActiveMQ代理可以组成一个集群来提供服务,且保证了高性能,客户端-服务端,点对点
- 管理简单。ActiveMQ是以开发者思维被设计的,有多种方法可以监控ActiveMQ不同层面的数据
- 对Spring的支持 可以很容易内嵌到使用Spring的系统中去,且支持Spring2.0的特性
- 支持通过JDBC和journal提供高速的消息持久化
- 支持Ajax
缺点
- 社区不活跃,不如RabbitMQ
- 会出现莫名奇妙的问题,会丢消息
- 不适用与上千个队列的应用场景
RabbitMQ
特征
- 流行的开源消息队列系统,erlang语言开发(也不知道是个啥语言,暂时不去查了,越查越多),这个语言支持你能想到的所有编程语言
- 按照AMQP(高级消息队列协议)的标准实现,这个协议从根本上避免了生产厂商的封闭,使用任何语言的各种客户都可以从中受益
- 用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗
- 灵活的路由:消息在到达队列前是通过交换机进行路由的,RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果有更复杂的路由需求,可以将这些交换机组合起来使用,甚至可以实现自己的交换机类型,并且当作RabbitMQ的插件来用
- 消息集群:在相同局域网中的多个RabbitMQ服务器可以聚合在一起,作为一个独立的逻辑代理来使用
- 队列高可用:队列可以在集群中的机器上进行镜像,以确保在硬件问题下还保证消息安全
- 多种协议的支持:支持多种消息队列协议
- 管理界面:RabbitMQ有一个易用的用户界面,使得用户可以监控和管理消息Broker的许多方面
- 跟踪机制:如果消息异常,提供了消息跟踪机制,使用者可以找出发生了什么(要不说人主流呢)
- 插件机制:提供了许多插件
优点
- 由于erlang语言的特性,mq性能较好,高并发
- 健壮、稳定、易用、跨平台、支持多种语言、文档齐全
- 有消息确认机制和持久化机制、可靠性高
- 高度可定制的路由
- 管理界面较丰富,在互联网公司有较大规模的应用
- 社区活跃度高
缺点
- 尽管erlang语言本身的并发优势,性能较好,但不利于做二次开发和维护(成也erlang,败也erlang)
- 实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队,此特性使得RabbitMQ易于使用和部署,但同时其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大
- 需要学习比较复杂的接口和协议,学习和维护成本较高
Kafka
特征
- 分布式消息发布订阅系统,其分区特性、可复制和可容错都是其不错的特性
- 快速持久化,可在O(1)的系统开销下进行消息持久化
- 高吞吐,在一台普通的服务器上就可以达到10W/s的吞吐率
- 完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式、自动实现负载均衡
- 支持同步和异步复制两种
- 支持数据批量发送和拉取
- zero-copy:减少IO操作步骤
- 数据迁移、扩容对用户透明
- 无需停机即可扩展机器
- 其他特性:严格的消息顺序、丰富的消息拉取机制、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制
优点
- 客户端语言丰富
- 性能卓越、单机写入TPS约在百万级/秒,消息大小为10个字节
- 提供完全分布式架构,并有replica机制,拥有较高的可用性和可靠性,理论上支持消息无限堆积
- 支持批量操作
- 消费者采用Pull方式获取消息,消息有序,通过控制能够保证所有消息被消费且仅被消费一次
- 有优秀的第三方Kafka Web管理界面kafka-Manager
- 在日志领域比较成熟,被多家公司和多个开源项目使用
缺点
- Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
- 使用短轮询方式,实时性取决于轮询间隔时间
- 消费失败不支持重试
- 支持消息顺序,但是一台代理宕机后,就会产生消息乱序
RocketMQ
特征
- 具有高性能、高可靠、高实时、分布式特点
- Producer、Consumer、队列都可以分布式
- Producer向一些队列轮流发送消息,队列集合称为Topic,Consumer如果做广播消费,则一个consumer实例消费这个Topic对应的所有队列,如果做集群消费,则多个Consumer实例平均消费这个topic对应的队列集合
- 能够保证严格的消息顺序
- 提供丰富的消息拉取模式
- 高效的订阅者水平扩展能力
- 实时的消息订阅机制
- 亿级消息堆积能力
- 较少的依赖
- 可以运行在Java语言所支持的平台之上
优点
- 单机支持1万以上持久化队列
- 所有消息都是持久化的,先写入系统PageCache,然后刷盘,可以保证内存和磁盘都有一份数据,访问时,直接从内存取
- 模型简单,接口易用
- 性能优越,可以大量堆积消息在broker中
- 支持多种消费,包括集群消费,广播消费等
- 各个环节分布式扩展设计
- 开发都较活跃,版本更新快
缺点
- 没有web管理界面,提供了一个CLI管理工具来查询、管理和诊断各种问题
- 没有在MQ核心去实现JMS等接口
ZeroMQ
特征
- 号称史上最快的消息队列,其他消息队列和它不是一个级别的,但是与此同时带来的是会丢失一些消息
- ZMQ实际类似与Socket的一系列接口,和Socket的区别是:普通Socket是端对端的,而ZMQ是N:M的
- 可用于node与node间的通信,node可以是主机或进程
- 高性能的设计要点
1、无锁的队列模型:对于跨线程的交互(用户端和session)之间的数据交换通道pipe,采用无锁的队列算法CAS,在pipe两端注册有异步事件,在读或写消息到pipe时,会自动触发读写事件
2、批量处理的算法:传统的消息处理,每个消息在发送和接收的时候,都需要系统的调用,这样对于大量的消息,系统开销,ZMQ对于批量的消息,进行了适应性优化,可以批量的接收和发送消息
3、多核下的线程绑定,无须CPU切换。区别于传统的多线程并发模式,信号量或临界区,zeroMQ充分利用多核优势,每个核绑定运行一个工作线程,避免多线程之间的CPU切换开销
怎么选(RabbitMQ/ActiveMQ/RocketMQ/Kafla)
先画个表格对比一下
RabbitMQ | ActiveMQ | RocketMQ | Kafka | |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 十万级(单机万级) | 十万级 |
topic数量对吞吐量的影响 | \ | \ | topic可以达到千的级别,吞吐量会有较小幅度的下降 这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降 同等机器下,topic数量不要过多,如果topic一定要大,那么需要增加更多的机器资源 |
消息写入性能 | RAM约为RocketMQ的1/2 Disk的性能约为RAM性能的1/3 | \ | 很好 每条10字节测试:单机单broker约7w/s | 非常好 每条10字节测试:百万条/s |
可用性 | 高(主从架构) master提供服务,slave仅做备份。采用镜像模式,数据量大时可能产生性能瓶颈 | 高(主从架构) 基于ZooKeeper+LevelDB的主从实现方式 | 高(分布式架构) 支持多master、多master多slave模式,异步复制模式,同步双写 | 非常高(分布式架构) 支持replica机制,leader宕机后,备份自动顶替,并重新选举leader(基于Zookeeper) |
消息延迟 | 微秒级 | 毫秒级 | 毫秒级 | 毫秒级以内 |
消息可靠性 | 可以保证数据不丢失,有slave节点做备份 | 有较低的概率丢失数据 | 经过参数优化配置,理论上可以做到0丢失 | 经过参数优化,理论上可以做到0丢失 |
持久化能力 | 内存、文件 支持数据堆积,但数据堆积反过来影响生产速率 | 内存、文件、数据库 | 磁盘文件 | 磁盘文件 只要磁盘容量够,就可以做到无限消息堆积 |
是否有序 | 若想有序,只能使用1个Client | 可以支持有序 | 有序 | 多Client保证有序 |
消息批量操作 | 不支持 | 支持 | 支持 | 支持 |
集群 | 支持 | 支持 | 支持 | 支持 |
事务 | 不支持 | 支持 | 支持 | 不支持,但可以通过Low Level API保证仅消费一次 |
到了最终要做抉择的时候了
- ActiveMQ,最早的时候大家都用,但现在用的不是很多了,没经过大规模吞吐量场景的验证,社区不是很活跃,主流上不选择这个
- RabbitMQ较为成熟一些,在可用性、稳定性、可靠性上,RabbitMQ都要超过kafka,综合性能不错,但是erlang语言阻止了大量java工程师深入研究,且不支持事务,消息吞吐能力有限
- Kafka的性能是比RabbitMQ要更强的,RabbitMQ在有大量的消息堆积时,性能会下降,而Kafka不会,但是Kafka的设计初衷是处理日志的,可以看做一个日志系统,针对性非常强,没有具备一个成熟MQ应该具备的特性,它还是个孩子啊
- RocketMQ的思路起源于Kafka,但它对消息的可靠传输及事务性做了优化,适合一些大规模的分布式系统应用,但是生态不够成熟,会有黄掉的风险
- ZeroMQ只是一个网络编程的Pattern库,将常见的网络请求形式(分组管理、链接管理、发布订阅等)进行模式化、组件化。简单来说就是在socket之上、MQ之下。使用ZeroMQ的话,需要对自己的业务代码进行改造,不利于服务解耦
总结一下
没有万金油,看自己需求进行选择
ZeroMQ小而美,RabbitMQ大而稳,Kafka和RocketMQ快而强劲
求保险,技术挑战不高的,RabbitMQ是首选
求性能,有事务性要求高的,技术实力强的,可以试试RocketMQ
大数据领域、日志采集,Kafka是业界标准,无脑选
消息队列应用场景
消息队列在实际应用中的常用使用场景有:异步处理、应用解耦、流量削峰和消息通讯
那我们就从一个实际场景来白话的解释一下这些看起来很高大上的场景
举个栗子
购物系统中的订单支付:在订单支付成功后,需要更新订单状态、更新用户积分、通知商家有新订单、更新用户标签几个操作。
那么在没有MQ之前,我们老老实实的一步接一步做,像这样
但这样的问题是什么,慢,太慢了,非得是上一步完成了才能开始下一步
所以引入MQ后,整个流程是这样的
看见没有,订单支付只需要关注它的主要流程更新订单状态即可,然后就把消息丢到MQ,其他的不核心的东西就由MQ来通知
这就是MQ解决的最核心的问题应用解耦,也就是只关注最核心的部分
改造前订单系统依赖3个外部系统,改造后仅依赖MQ,那么后续业务的扩展,也不需要对订单系统进行修改,从而保证了核心流程的稳定性,降低了维护成本
异步通信:MQ的引入,更新用户积分、通知商家、更新用户标签这些步骤不再是串行的了,而是变成了异步执行,这减少了订单支付的整体耗时,提升了订单系统的吞吐量
流量削峰:队列转储了消息,做了一个中间站,对于超出系统承载能力的场景,"MQ"作为漏斗进行限流保护,就是说把最高的峰值削一部分给峰谷,就好像把波浪线修成直线一样,均衡一下
消息的延时消费:队列本身的特征是先进先出,也就是说是有序的,利用队列+定时任务就能实现消息的延时消费
就用网上用烂了的几张图与传统方法进行对比,显示一下MQ的优越
异步处理
场景:用户注册后,需要发送注册用剑和注册短信
传统做法:串行、并行
- 串行:注册信息放入数据库后,先发邮件,再发短信,全部完成后,返回给用户注册成功
- 并行:注册信息放入数据库后,同时进行发送右键和发送短信,全部完成后,返回给用户注册成功,节省了一部分时间
引入MQ后: 注册信息写入数据库后,将消息放入MQ,马上通知用户注册成功,发送注册邮件和短信由MQ去通知
应用解耦
场景:用户下单后,订单系统通知库存系统
传统做法:订单系统调用库存系统的接口,一旦一个系统无法使用,必定会导致另一个系统也无法使用
引入MQ后:订单系统和库存系统不再直接交互
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户下单成功
库存系统:订阅下单的队列,采用推/拉的方式,获取下单信息,根据下单信息,进行库存操作
假如其中一个系统无法正常使用,只要MQ健在,另一个系统将不受影响
流量削峰
场景: 秒杀或者团抢活动,一般因为流量过大,导致流量暴增,应用挂掉
传统方法: 加服务器
引入MQ后:在应用前端加入消息队列,秒杀时将消息不断丢入队列,等待业务根据规则读取队列中的消息进行处理
- 可以有效控制活动人数
- 可以缓解短时间内高流量,避免压垮应用
日志处理
场景:大量日志进行传输
引入MQ后: kafka的优秀展示场景
- 日志采集客户端:负责日志数据采集、定时写入Kafka队列
- Kafka消息队列:负责日志数据的接收,存储和转发
- 日志处理应用:订阅并削峰kafka队列中的日志数据
消息通讯
场景:分为点对点通讯(也就是单向私聊)和聊天室(也就是群聊或双向私聊)
点对点通讯: 两个客户端之间公用一个队列
聊天室通讯:多个客户端订阅同一主题(注意这里是订阅不是消费,说明一个消息可以被多次消费)
到此,结束,撒花,放烟花!