为什么使用消息队列
MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进 行通信。
优点:解耦,异步,消峰
解耦:比如微服务中用户下单,要访问库存系统,订单系统,支付模块,如果其中一个访问失败,那么整个流程无法继续运行,耦合度较高,这个时候采取消息队列,把消息持久化,等系统恢复正常以后再去消费(数据一致性 顺序性问题可以说一下);
异步,a中三个模块独立运行,并行事务,非必要业务逻辑可以异步执行不影响主要业务流程,处理速度快;
消峰:消费端可以按照自己的处理速度来进行数据处理,没有必要一次性把数据全部处理,可以根据压力来进行拉取消息,短暂高峰积压在一定程度可以,但是持续的积压会导致消息队列出现问题,如果消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
缺点:
a.系统复杂度增加,考虑问题较多,比如一致性问题,消息不被重复消费,如何可靠传输等
b.系统可用性降低,消息队列挂掉以后可能会影响到系统可用性。
常见消息队列
ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。
RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级,如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。
RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。
Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。
如何保证消息队列的高可用(RabbitMQ)
一共三种工作模式:单机模式,普通集群模式镜像集群模式
单机模式:单个队列服务,生产模式不可用
普通集群模式:在多个服务器上部署多个MQ实例, 每台机器一个实例, 创建的每一个queue,只会存在一个MQ实例上,但是每一个实例都会同步queue的元数据, 当在进行消费的时候, 就算 连接到了其他的MQ实例上, 其也会根据内部的queue的元数据,从该queue所在实例上拉取数据过来。也就是都保存在一个节点上,其余节点可以进行访问,提高了吞吐量,但是没有高可用,缺点是MQ内部产生大量数据传输,如果queue所在节点宕机,数据还是丢失,其他实例无法进行拉取。
镜像集群模式:无论queue的元数据还是queue中的消息都会同时存在与多个实例上。要开启镜像集群模式。需要在后台新增镜像集群模式策略,即要求数据同步到所有的节点,也可以指定同步到指定数量的节点。保证了高可用,有多余存储备份,缺点是性能开销大,需要同步信息。部署可以参考RabbitMQ集群和高可用方案 - 腾讯云开发者社区-腾讯云 (tencent.com)是一种主从集群,普通集群的基础上,添加了主从备份功能,提高集群的数据可用性。
MQ常用协议
AMQP协议 AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制,优点是可靠,通用。
MQTT协议 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议,优点是格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统。
STOMP协议 STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互,优点:命令模式(非topic/queue模式)。
XMPP协议 XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同,优点:通用公开、兼容性强、可扩展、安全性高,但XML编码格式占用带宽大。
MQ的工作模式
简单模式:一个生产者一个消费者,生产则发送消息,消费者拿到消息,这个过程就结束了。
P:生产者,也就是要发送消息的程序 C:消费者:消息的接收者,会一直等待消息到来 queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息
work模式:一个生产者多个消费者,生产则发送一条消息,只能被一个消费者拿到,如果生产者发送多条消息,消费者拿到的消息是不会重复的。在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理速度,比如短信服务可以部署多个。
订阅模式:和work模式类似,一个生产者多个消费者,但是中间多了个交换机,一条消息可以被多个消费者获取。生产者将消息发给交换机,交换机把消息分配给“已绑定”的消费者,前提是消费者和交换机绑定。 P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机) C:消费者,消息的接收者,会一直等待消息到来 Queue:消息队列,接收消息、缓存消息 Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、 递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型: ➢ Fanout:广播,将消息交给所有绑定到交换机的队列 ➢ Direct:定向,把消息交给符合指定routing key 的队列 ➢ Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列 Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合 路由规则的队列,那么消息会丢失!
Routing 路由模式
P:生产者,向 Exchange 发送消息,发送消息时,会指定一个routing key X:Exchange(交换机),接收生产者的消息,然后把消息递交给与 routing key 完全匹配的队列 C1:消费者,其所在队列指定了需要 routing key 为 error 的消息 C2:消费者,其所在队列指定了需要 routing key 为 info、error、warning 的消息
Topics主题模式
Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符! Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: test.insert 通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:test.# 能够匹配 test.insert.abc 或者 test.insert,test.* 只能匹配 test.insert
RPC远程调用模式
简单说如图所示:
RabbitMQ相关概念
Broker :接受和分发消息的应用,RabbitMQ Server就是 Message Broker ;一个或多个 erlang node 的逻辑分组,且 node 上运行着 RabbitMQ 应用 程序。cluster 是在 broker 的基础之上,增加了 node 之间共享元数据的约束。
Virtual host: 出于多用户考虑,可以建立不同用户的对应的交换机和队列,和Linux多用户类似;
Connetcion: publisher/consumer 和 broker 之间的 TCP 连接 ;
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 (point-to-point), topic (publish-subscribe) and fanout (multicast);
Queue:消息最终被送到这里等待被消费之拿走;
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存 到 exchange 中的查询表中,用于 message 的分发依据 。
消息可靠性投递
RabbitMQ提供了两种方式来控制消息投递可靠性方案:
confirm 确认模式
return 退回模式
RabbitMQ整个消息投递路径为:
producer--->rabbitmq broker--->exchange--->queue--->consumer 消息从 producer 到 exchange 则会返回一个 confirmCallback 。 消息从 exchange-->queue 投递失败则会返回一个 returnCallback 。
如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息 ,
如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息 。
当消费者出现异常后,消息会不断requeue(重新入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力:我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列。
需要条件:
持久化: exchange queue message持久化
生产方 Confirm
消费方 ACK
Broker 高可用
死信队列
死信队列,英文缩写:DLX (Dead Letter Exchange,死信交换机),当消息成为Dead message后,可以 被重新发送到另一个交换机,这个交换机就是DLX。
三种情况成为死信队列:
队列消息长度达到限制;
消费者拒绝消费消息basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
原队列存在消息过期设置,消息队列达到超时未被消费。(TTL : 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断 这一消息是否过期。 两者取时间较短的为准)
延迟队列
在RabbitMQ中并未提供延迟队列功能。 但是可以使用:TTL+死信队列 组合实现延迟队列的效果
延迟队列 指消息进入队列后,可以被延迟一定时间,再进行消费。
惰性队列
从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念,也就是惰性队列。
惰性队列的特征如下:
•接收到消息后直接存入磁盘而非内存
•消费者要消费消息时才会从磁盘中读取并加载到内存
•支持数百万条的消息存储
消息堆积问题的解决方案?
队列上绑定多个消费者,提高消费速度
给消费者开启线程池,提高消费速度
使用惰性队列,可以再mq中保存更多消息
可以参考关于MQ的几件小事(六)消息积压在消息队列里怎么办 - 一条路上的咸鱼 - 博客园 (cnblogs.com)
如何保证消息的顺序性?
对于 RabbitMQ 来说,导致上面顺序错乱的原因通常是消费者是集群部署,不同的消费者消费到了同一订单的不同的消息,如消费者 A 执行了增加,消费者 B 执行了修改,消费者 C 执行了删除,但是消费者 C 执行比消费者 B 快,消费者 B 又比消费者 A 快,就会导致消费 binlog 执行到数据库的时候顺序错乱,本该顺序是增加、修改、删除,变成了删除、修改、增加。
RabbitMQ 的问题是由于不同的消息都发送到了同一个 queue 中,多个消费者都消费同一个 queue 的消息。解决这个问题,我们可以给 RabbitMQ 创建多个 queue,每个消费者固定消费一个 queue 的消息,生产者发送消息的时候,同一个订单号的消息发送到同一个 queue 中,由于同一个 queue 的消息是一定会保证有序的,那么同一个订单号的消息就只会被一个消费者顺序消费,从而保证了消息的顺序性。
参考消息队列(五)如何保证消息的顺序性?_Java_奈何花开_InfoQ写作社区
如何避免消息重复消费?
在消息生产时,MQ内部针对每条生产者发送的消息生成一个唯一id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列。在消息消费时,要求消息体中也要有一全局唯一id作为去重和幂等的依据,避免同一条消息被重复消费。
如何设计一个消息队列?
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。
三个过程:1.通讯协议2.寻址3.数据序列化
详细介绍可以参考:RPC是什么,看完你就知道了 - 知乎 (zhihu.com)
1、直接利用成熟的 RPC 框架(Dubbo 或者 Thrift),实现两个接口:发消息和读消息。
2、消息放在本地内存中即可,数据结构可以用 JDK 自带的 ArrayBlockingQueue 。