用最简单的语言阐述最深刻的知识,纯属个人总结,有所借鉴。

何为队列

Queue是一个存储、组织数据的数据结构,其最大的特性就是FIFO;

Rabbitmq中Queue是RabbitMQ的内部对象,用于存储消息

 

JMS协议

JMS(Java Messaging Service)是Java平台上有关面向消息中间件的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发。

JMS本身只定义了一系列的接口规范,是一种与厂商无关的 API,用来访问消息收发系统。它类似于 JDBC(Java Database Connectivity)

 

为什么需要消息队列、优点

·解耦将数据丢到消息队列中,其他系统根据需求进行订阅,不需要系统做任何修改

·异步将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

·削峰将请求丢到消息队列,系统按数据库能处理的并发量批拉数据,批量处理请求

缺点

·复杂性增加:需要保证消息不会重复,保证消息可靠性,需要保证消息队列的高可用

·可用性降低:如果消息队列挂了,整个系统会受影响

 

ActiveMQ、RabbitMQ、RocketMQ、Kafka

·ActiveMQ:java开发,万级,ms级,主从架构,成熟功能强大,版本更新缓慢

·RabbitMQ:erlang开发,万级,us级,主从架构,并发能力强,性能好且管理界面丰富

·RocketMQ:java开发,十万级,ms级,分布式架构,功能完备,扩展性高

·Kafka:scala开发,十万级,ms级,分布式架构,跨平台,大数据领域专业消息中间件

 

如何选择

  • RabbitMQerlang语言具备高并发的特性,性能极好,延迟很低,管理界面方便。社区十分活跃。是现在用的比较多的,这也是我们的项目选择RabbitMQ的原因。
  • ActiveMQ现在用的不多,更新缓慢,吞吐量较低。
  • RocketMQ是阿里出品,如阿里放弃维护,一般公司定制化开发比较困难,局限性大。
  • Kafka主要用于大数据应用,MQ功能不完备,而且一般公司数据量并不是特别大。

 

1.从社区活跃度

按照目前网络资料,综合来看,RabbitMQ 是首选。

2.持久化消息

ActiveMq 和RabbitMq 都支持。

持久化消息主要是指我们机器在不可抗力因素等情况下挂掉了,消息不会丢失的机制。

3.综合技术实现

可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。

RabbitMq / Kafka 最好,ActiveMq 次之。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。

4.高并发

毋庸置疑,RabbitMQ 最高,原因是它的实现语言是天生具备高并发高可用的erlang 语言。

5.比较关注的比较, RabbitMQ 和 Kafka

RabbitMq 比Kafka 成熟,在可用性、稳定性、可靠性上,RabbitMq胜于Kafka(理论上)。

另外,Kafka 的定位主要在日志等方面, 因为Kafka 设计的初衷就是处理日志的,可以看做是一个日志(消息)系统一个重要组件,针对性很强,所以 如果业务方面还是建议选择 RabbitMq 。

还有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出来很多。

 

MQ如何保证消息队列高可用--集群

ActiveMQ

使用zk注册所有的ActiveMQ Broker,只有一个Broker被指定为Master提供服务,其他Slave处于待机状态。Slave连接Master并同步存储状态,Slave不接受客户端连接。所有的存储操作都将被复制到连接至Master的Slaves。如果Master宕了,zk会选择得到了最新更新的Slave会成为 Master。故障节点在恢复后会重新加入到集群中并连接Master进入Slave模式。

RabbitMQ

单机模式、普通集群模式、镜像集群模式

Kafka

集群包含多个broker节点,写入消息时,同一个topic的不同数据会写入到不同的partition中。并且每个机器上的partition都会在其他机器上存储副本,当某个broker宕机时,会去选一个follower作为leader,完成切换,保证数据高可用。

Kafka通过zk管理集群配置,选举leader。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。

RocketMQ

集群有多master 模式、多master多slave异步复制模式、多 master多slave同步双写模式,每种模式各有优劣点。

 

 

RabbitMQ

简介

AMQP,Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计,主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。

RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种语言,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

 

重点介绍RabbitMQ中的一些基础概念:

msmqjava消息发送成功但对列找不到数据 java mq消息队列详解_持久化

图片来源于网络;

 

 

组件构成

Producer/Publisher:数据发送方

一般一个Message有两个部分:payload(有效载荷)和label(标签)

payload是传输的数据,label是exchange的名字或者一个tag,描述payload,RabbitMQ也是通过这个label来决定把这个Message发给哪个Consumer

 

Broker:vHost载体

Broker是指一个或多个 erlang node 的逻辑分组,node上运行着 RabbitMQ 应用程序。Broker保证数据能够按照指定的方式进行传输。

Cluster是在Broker基础上,增加了node之间共享元数据的约束。

 

Virtual Host:虚拟主机

多个不同用户使用同一个RabbitMQ Server提供的服务时,划分出多个vHost,每个用户在自己的vHost创建exchange/queue

 

Exchange:交换器

生产者将消息发送到Exchange交换器,由Exchange将消息路由到一个或多个Queue中或者丢弃。Exchange从生产者那收到消息后,Routing Key与Exchange Type及Binding key联合使用才能正确的分发消息到Queue。

 

Queue:队列

RabbitMQ中的消息都只能存储在Queue中,多个消费者可以订阅同一个Queue 

Connection与Channel:RabbitMQ对外提供的API最基本的对象

Connection是一个TCP连接,Producer和Consumer都是通过TCP连接到RabbitMQ Server,

Channel建立在上述的TCP连接中,因为建立TCP Connection的开销巨大,所以节省开销,

Channel是我们与RabbitMQ打交道的最重要的一个接口,大部分业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等

 

routing key:指定路由规则

生产者将消息发送给Exchange时,会指定routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。

在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),生产者可以在发送消息给Exchange时,指定routing key来决定消息流向哪里。 RabbitMQ为routing key设定的长度限制为255 bytes。

 

binding key:绑定

在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。

在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。 binding key并不是在所有情况下都生效,它依赖于Exchange Type。

 

Exchange Types

RabbitMQ常用的Exchange Type有fanout、direct、topic、headers这四种

 

fanout把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。

direct把消息路由到那些binding key与routing key完全匹配的Queue中。

topic在direct规则上进行了扩展,也是将消息路由到binding key与routing key相匹配的Queue中,匹配规则有些它约定:

 

·routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”

·binding key与routing key一样也是句点号“. ”分隔的字符串

·binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

 

以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。

headers不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。 在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。 该类型的Exchange没有用到过(不过也应该很有用武之地),所以不做介绍。

 

生产者消息确认机制

·AMQP提供的事务机制实现,开启事务、推送消息、提交事务等几个过程,任何一个环节出现问题都会抛出异常进行回滚,性能比较低。

·生产者消息确认机制实现,普通confirm、批量confirm、异步confirm三种模式,后两种性能相差不大。Confirm机制比事务机制性能快十倍左右。

 

RabbitMQ高可用

单机模式:demo级别

普通集群模式

每个机器都启动一个rabbitMQ实例,创建的队列queue放在其中一个实例。消费时,如果请求连接到非queue实例,该实例就会同步queue所在实例的元数据。

·性能低,如果消费者每次都随机到非queue实例,每次都要拉取数据。

·如果queue所在实例宕机,就会导致整个消息队列不可用。可以开启消息持久化,等queue实例恢复了,才可以继续服务,但也不能保证消息的完整性。

镜像集群模式高可用模式,每一个实例上都会存在queue,每次往queue写消息都会自动同步到其他实例上。

·保证消息队列高可用性,任何一个实例宕机都不会影响数据消费

·性能低,消息同步导致网络带宽压力

·非分布式,没有扩展性,再多的机器也无法线性扩展queue

 

消息可靠性

消息丢失包括三方面:生产者消息丢失、消息队列消息丢失、消费者消息丢失

RabbitMQ

1、生产者:提供transaction和confirm模式来确保生产者不丢消息

transaction机制:发送消息前开启事务,如果发送过程中出现异常,事务回滚;如果发送成功则提交事务。缺点就是吞吐量下降了。

confirm机制:一旦channel进入confirm模式,消息都会被指派唯一ID,一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了。如果rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作

2、消息队列:开启持久化磁盘的配置

持久化配置和confirm机制配合使用,在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

如何开启持久化

第一步:将queue的持久化标识durable设置为true,则代表是一个持久的队列

第二步:发送消息时将deliveryMode=2,这样即使rabbitMQ挂了,重启后也能恢复数据

3、消费者:将自动确认改为手动确认消息即可

 

消息不重复发送

先说为什么会重复消费:正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;

但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。RabbitMQ是发送一个ACK确认消息,

Kafka中消息有一个offset,消费后提交offset让队列知道自己已经消费过

解决方式:

  • 唯一主键:数据库的insert操作。唯一主键避免数据库重复写入。
  • redis原子性:redis操作是幂等操作。消费者开始消费前,先去redis中查询有无消费记录。

 

消息顺序性

  • 根据某种算法将保持先后顺序的消息放到同一队列,然后单线程去消费,可以保证顺序
  • 对消息进行编号,消费者根据编号处理消息