消息队列MQ(Message Queue)通俗来讲,是应用程序之间的一种通信方法。应用程序通过消息队列来读写消息。消息传递,指的是程序之间通过将数据发送到队列,而不是通过直接调用彼此来通信。队列的使用去除了接收和发送应用程序同时执行的要求。

RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、 安全。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。

01

RabbitMQ工作模型



消息队列mq 可以给安卓端使用么_API

图中,Broker是一个代理,生产者/消费者和Broker之间建立的是tcp长连接,如果每次收发消息都创建/释放一个tcp长连接,对Broker是很大的性能损耗,因此需要一个Channel消息信道(虚拟连接),只需要在保持tcp长连接的时候,创建和释放信道就可以了。RabbitMQ用队列来存放消息(有独立进程写入数据库),交换机用来分发消息。首先交换机和队列要进行绑定(多对多),建立一个绑定键(binding key),生产者发送消息携带路由键(routing key)到交换机上,交换机上有绑定列表(绑定键-队列),根据列表查找与路由键相同的绑定键,进行路由到一个或多个队列。消费者不是轮询机制,而是使用事件机制,监听队列。在一个RabbitMQ服务中,可以创建多个虚拟主机,用于不同用户使用,提高硬件资源利用率。

02

常用交换机类型

2.1. Topic(主题交换机)



消息队列mq 可以给安卓端使用么_消息队列mq 可以给安卓端使用么_02

主题交换机类型中,绑定键会使用通配符,通配符有两种:*(代表一个单词)。#(代表0个或多个单词)

channel.basicPublish("EXCHANG_TOPIC","junior.netty.jvm","mysql 1");

上面这个例子中,只有图片中的junior.#绑定键的JUNIOR_QUEUE队列能收到消息。channel.basicPublish("EXCHANG_TOPIC","senior.netty","mysql 1");

上面这个例子中,只有图片中的SENIOR_QUEUE,NETTY_QUEUE队列能收到消息。

主题交换机的应用场景:当消息有分级,或者需要消息根据不同业务过滤时使用效果较好。关注不同的消息来源,比如一个消息跟销售,资金,产品有关,A系统只关心销售,资金有关的消息,B系统只关心资金,产品有关的信息,就可以使用主题类型的交换机,将消息根据不同业务进行过滤。

2.2  Direct(直连交换机)



消息队列mq 可以给安卓端使用么_持久化_03

channel.basicPublish("EXCHANG_DIRECT","mysql","mysql 1");

在直连交换机中,路由键要与绑定键完全一致。上面的例子,只有MYSQL_QUEUE队列能够收到消息。

直连交换机的应用场景举例:比如一个销售系统,就关注OA系统的人员入职信息,消息目的特别明确的场景,适合用直连交换机。

2.3 Fanout(广播交换机)



消息队列mq 可以给安卓端使用么_posix自己搭建消息队列_04

channel.basicPublish("EXCHANG_FANOUT","","mysql 1");

 在广播交换机中,生产者发送消息是不需要路由键,消息会发送给所有绑定的队列上,上面的例子中,三个队列都会收到消息。

应用场景:所有业务都关注这个消息的场景。比如电商产品的变动,很多系统都关注这个信息,就可以使用广播交换机。

03

Spring Boot 整合RabbitMQ

在Spring Boot中搭建RabbitMQ的时候,主要需要如下4个步骤。

3.1 配置类:创建交换机,创建队列,创建绑定关系。

创建三种交换机:



消息队列mq 可以给安卓端使用么_API_05

创建队列:



消息队列mq 可以给安卓端使用么_posix自己搭建消息队列_06

建立绑定关系:



消息队列mq 可以给安卓端使用么_持久化_07

3.2 消费者:监听类(监听队列)



消息队列mq 可以给安卓端使用么_重发_08

3.3  生产者:调用template连接资源管理,发送消息(Exchange)



消息队列mq 可以给安卓端使用么_消息队列mq 可以给安卓端使用么_09

消息队列mq 可以给安卓端使用么_API_10

3.4  服务器信息配置



消息队列mq 可以给安卓端使用么_消息队列mq 可以给安卓端使用么_11

04

消息丢失和消息可靠性投递

通过RabbitMQ的工作模型我们可以看出,RabbitMQ的链路很多,因此,要想保证消息不丢失,可靠的投递到消费者,我们需要在发送消息的每一个环节去保证消息不丢失和可靠的重复投递。

根据RabbitMQ的工作模型,我们应该从生产者出发,一步步保证消息的可靠性投递。

4.1  确保消息成功发送到RabbitMQ服务器

这个环节的目的是保证Broker能够成功接收到消息。如何确保生产者生产的消息,能够发送到Broker上呢?有如下两种确认模式:

(1)  服务端确认-事务模式



消息队列mq 可以给安卓端使用么_持久化_12

事务模式是,生产者每发一次消息,都commit一次,然后Broker应答一次,但是这会导致性能损耗较大。

(2)  服务端确认-确认模式



消息队列mq 可以给安卓端使用么_posix自己搭建消息队列_13

有三种确认模式:

1)单条确认:效率不高,类似于事务模式

2)批量确认:提高一定的效率,但是发送多少条确认一次呢?如果发送1000条消息确认一次,失败了呢?

3)异步确认:发送完消息后,异步确认。使用效果较好

4.2 确保消息路由到正确的队列

这个环节是保证消息路由到正确的队列。当使用错误的路由键的时候,或者队列不存在的时候,会导致消息无法路由到正确的队列。

(1)添加一个returnListener,当无法路由消息时,会将消息回发给自己。然后做其他业务处理,可以报错,记录日志,记录数据库等。

 (2)指定交换机的备份交换机alternate-exchange。当消息没有路由到任何一个队列的时候,会发送到备份交换机,业务上可以报错,记录日志,记录数据库等。

4.3 确保消息在队列正确地存储

这个环节是保证消息能够正确的存储。RabbitMQ有内存节点,磁盘节点(消息存储在磁盘中),每个RabbitMQ集群至少有一个磁盘节点。

(1)队列持久化

队列是消息的载体,即使机器重启,即使没有消费者使用该队列,队列也应当持久化。队列有durable属性,可以在定义队列的方法中设置。

(2)交换机持久化

    交换机跟队列一样,也需要持久化,保证机器重启后,交换机依然存在。

(3)消息持久化。

    消息本身也要持久化,Message properties要设置成持久化,防止数据丢失。

4.4 确保消息从队列正确地投递到消费者

这个环节是保证消息能发送到消费者。Broker只有知道消费者收到消息后,消息才会从队头删掉,继续投递下一条消息。消费者必须给Broker一个应答。默认的情况下有autoAck应答,实际情况是,当消费者一开始接到消息时,就会立刻应答,但是这样只能保证消费者收到消息,不能保证消费者正常处理完毕。如果想让消费者正常处理业务后,发送应答,就需要手动发送应答,使用basicAck。



消息队列mq 可以给安卓端使用么_重发_14

如果消息一直没有ack应答,那么消息就会在队头阻塞。阻塞的处理方式可以是,如果一个消费者消息没有ack应答,那么可以选择把消息发送给另一个相同的消费者。

如果改成手动ack模式的话,需要考虑异常情况,如果发生异常,需要在finally中进行处理,可以用unack拒绝消息,或者让消息重回队列。

05

其他可靠性投递的方案

以上所讲的方式,能最终保证消费者一定正确的收到消息,一定能正确处理自己的业务逻辑吗?不一定,因为每一个环节都是不可控的。并且你永远不知道消费者的业务处理会不会发生异常。因此可以考虑以下的方案。

5.1 确认机制

(1)  消费者正确处理业务后,回发一条消息。只要生产者收到这条回信,就认为这条消息被正常处理了。也就是回执消息。

(2)  生产者定义一个API,供消费者调用,一旦调用API,生产者消息的消息状态发生变化,标记成功。

如果消费者一直没有回信呢?没有回发消息,也没有调用API呢?

5.2 补偿机制

(1)超时机制。需要设定一个超时时间,当消费者超过一定的时间,没有调用API,也没有回信。这时就需要下面的方案

(2)重发。

重发在金融业务场景中是很有必要的,比如网络的波动,专用网络外网网络不好的时候,都需要重发。

重发时也有一个重要的考虑点,如果消费者宕机了,生产者不断地重发是不可以的,会造成消息队列阻塞。因此,需要设定一个重发次数,超过重发次数进行标识。如果消费者每次都是成功接收消息,并成功处理,只是在调用生产者API,或者回发消息的时候失败了,那么就需要如下的处理。

5.3 消息幂等

  重复消息的处理。消费者首先要知道是不是重复的消息,因此,消息需要一个序列号(流水号),通过流水号,来判定消息的唯一性,在金融业务中就是重帐控制,消费者需要对重复的消息进行处理。

  当然,在金融系统中,有一个终极的数据一致性方案,就是对账,然后调账处理。

RabbitMQ已经广泛应用于各工程领域中,本文主要从RabbitMQ的工作模型开始,针对消息的可靠性投递这一点,对RabbitMQ消息投递的每一环节进行简单分析。关于RabbitMQ的其他方面,包括RabbitMQ的流量控制(生产者,消费者的流量控制),高可用普通集群,镜像集群的搭建,集群中不同消费者消息接收的方案处理等。希望有兴趣的同学能够在日后进行更深入的研究。