RabbitMQ消息队列工作原理及集成使用

 

目录

一、应用场景

1.1  系统间解耦 

1.2  异步提升效率

1.3  流量削峰

1.4  统一通信标准

二、什么是消息队列?

三、RabbitMQ

3.1  介绍

3.2  RabbitMQ中的重要术语

3.2.1  Exchange

3.2.2  routing key

3.2.3  Binding key

        3.3  RabbitMQ运转流程

生产者发送消息

消费者接收消息的过程

3.4  RabbitMQ 重要对象

3.5  RabbitMQ中的重要机制

3.5.1  Message acknowledgment(消息回执)

3.5.2  Message durability(消息持久化)

3.5.3  Prefetch count(预载数量)

3.5.4  RPC(远程过程调用)

3.6  Exchange Types

3.6.1  direct

3.6.2  topic

3.6.3 fanout

四、Spring boot 集成 RabbitMQ

4.1  引入maven依赖

4.2  生产消息

4.3  消费消息

五、其他

5.1  vhost 是什么?起什么作用?

5.2  如何确保消息接收方消费了消息?

5.3  如何避免消息重复投递或重复消费?

5.4  如何解决丢数据的问题?

5.5  如何确保消息正确地发送至RabbitMQ? 如何确保消息接收方消费了消息?


一、应用场景

1.1  系统间解耦 

我们遇到过这样的需求:一个商品销售系统,用户下单后,订单系统需要通知库存系统;或者一个运营后台系统新建单位或者用户时,通知对应的业务平台初始化该单位/用户的配置。遇到类似这两种需求的时候,传统的做法是订单系统调用库存系统的接口或者运营系统调用业务平台的接口,这种“直调”的方式完成功能的同时还有一些缺陷:

  • 被调用方接口一旦失败,调用方也将失败。例如库存系统无法访问,则订单减库存也将失败,从而导致订单失败
  • 被调用方系统与调用方系统耦合

这时候可以引入消息队列作为第三方媒介,用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功;库存系统订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。加上消息队列的好处就是在下单时库存系统不能正常使用,也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。

(ps:消息队列和RPC的区别与比较:RPC: 异步调用,及时获得调用结果,具有强一致性结果,关心业务调用处理结果。消息队列:两次异步RPC调用,将调用内容在队列中进行转储,并选择合适的时机进行投递(错峰流控))

1.2  异步提升效率

我们也会遇到这样的场景:系统注册新的用户后,给这个用户发确认邮件或者短信,所有都完成了返回一个注册成功的消息。无论是串行实现还是并行实现,都会影响效率,因为发短信和邮件并不是主体业务功能,真正的主体业务还要等一些非必要的功能运行完才能最终实现。这时可以引入消息对列,邮件和信息订阅对应的队列消息,进行异步处理。

1.3  流量削峰

我们也会遇到这样的场景:高并发流量大的场景,例如整点秒杀、大型直播。

大流量时MySQL可能忙不过来,比如每秒MySQL只能处理6000个请求,但是现在一秒钟来了1万个请求,服务器可能就宕机了,那就可以将过多的处理先放到消息队列(消息队列可以应付的过来),然后系统再慢慢地从队列中按照顺序消费这些信息,直到消费完所有消息。在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中,而不是像几年前一样页面卡死或报错给用户。

1.4  统一通信标准

不同进程(process)之间传递消息时,可以通过消息队列实现标准化,将消息的格式规范化。

说了这么久都在讲Mq的好处,实际上它也有槽点

  • 系统的可用性降低
    系统引入的外部依赖越多,系统越容易挂掉,本来只是A系统调用BCD三个系统接口就好,ABCD四个系统不报错整个系统会正常运行。引入了MQ之后,虽然ABCD系统没出错,但MQ挂了以后,整个系统也会崩溃。
  • 系统的复杂性提高
    引入了MQ之后,需要考虑的问题也变得多了,如何保证消息没有重复消费?如何保证消息不丢失?怎么保证消息传递的顺序?
  • 一致性问题
    A系统发送完消息直接返回成功,但是BCD系统之中若有系统写库失败,则会产生数据不一致的问题。

消息队列是一种十分复杂的架构,引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避。引入MQ系统复杂度提升了一个数量级,但是在有些场景下,就是复杂十倍百倍,还是需要使用MQ。

 

二、什么是消息队列?

消息队列(Message Queue,简称MQ),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已。详细的队列数据结构可以查看下面的文章:

https://blog.csdn.net/weixin_41231928/article/details/104840335

MQ框架非常之多,比较流行的有RabbitMq、ActiveMq、ZeroMq、kafka,以及阿里开源的RocketMQ。本文主要介绍RabbitMq。下面是各MQ框架技术选型对比:

1.从社区活跃度

按照目前网络上的资料,RabbitMQ activeM 、ZeroMQ 三者中,综合来看,RabbitMQ是首选。 

2.持久化消息比较

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

3.综合技术实现

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

RabbitMq / Kafka 最好,ActiveMq 次之,ZeroMq 最差。当然ZeroMq 也可以做到,不过自己必须手动写代码实现,代码量不小。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。

4.高并发

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

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

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

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

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

 

三、RabbitMQ

3.1  介绍

RabbitMQ是一个实现了AMQP(Advanced Message Queuing Protocol)高级消息队列协议的消息队列服务,Erlang语言编写。

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 

RabbitMQ支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面有很好表现。 

3.2  RabbitMQ中的重要术语

  • ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用;
  • Channel(信道):消息推送使用的通道;
  • Exchange(交换器):用于接受、分配消息;
  • Queue(队列):用于存储生产者的消息;
  • RoutingKey(路由键):用于把生成者的数据分配到交换器上;
  • BindingKey(绑定键):用于把交换器的消息绑定到队列上;
  • Virtual Hosts(虚拟主机:每一个RabbitMQ服务器都能创建虚拟的消息服务器,我们称之为虚拟主机(virtual host),简称为vhost。每一个vhost本质上是一个独立的小型RabbitMQ服务器,拥有自己独立的队列、交换器以及绑定关系等待,并且它拥有自己独立的权限。vhost就像是虚拟机与物理服务器一样,它们在各个实例间提供逻辑上的分离,允许你为不同程序安全保密地运行数据,它既能将同一个RabbitMQ的中众多客户区分开,又可以避免队列和交换器等命令冲突。vhost之间是绝对隔离的,你无法将vhost1中的交换器与vhost2中的队列进行绑定,这样既保证了安全性,又可以确保可移植性。如果在使用RabbitMQ达到一定规模的时候,用户可以对业务功能、场景进行归类区分,并为之分配独立的vhost。

3.2.1  Exchange

生产者将消息发送到Exchange(交换器,下图中的X),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。

RabbitMQ消息队列工作原理及集成使用_第1张图片

3.2.2  routing key

消息可以认为由payload+ routing key组成,payload就是消息内容数据,routing key来指定这个消息的路由规则。这个routing key需要与Exchange Type及binding key联合使用才能最终生效。 在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。 RabbitMQ为routing key设定的长度限制为255 bytes。

3.2.3  Binding key

RabbitMQ中通过Binding将Exchange与Queue关联起来,这样RabbitMQ就知道如何正确地将消息路由到指定的Queue了。

在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。 binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,而是将消息路由到所有绑定到该Exchange的Queue。

上面的各部分组成了RabbitMQ的整个流程:

RabbitMQ消息队列工作原理及集成使用_第2张图片

3.3  RabbitMQ运转流程

生产者发送消息

1、生产者连接到 RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)

2、生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等

3、生产者声明一个队列并设置相关属性,比如是否排他、是否持久化、是否自动删除等

4、生产者通过路由键将交换器和队列绑定起来

5、生产者发送消息至 RabbitMQ Broker,其中包含路由键、交换器等信息

6、相应的交换器根据收到的路由键查找相匹配的队列

7、如果找到,则将从生产者发送过来的消息存入相应的队列中

8、如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者

9、关闭信道

10、关闭连接

消费者接收消息的过程

1、消费者连接到 RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)

2、消费者向 RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作

3、等待 RabbitMQ Broker 回应并投递相应队列中的消息,消费者接收消息

4、消费者确认(ack)接收到的消息

5、RabbitMQ 从队列中删除相应已经被确认的消息

6、关闭信道

7、关闭连接

从概念上来说,消息路由必须有三部分:交换器、路由、绑定。生产者把消息发布到交换器上;绑定决定了消息如何从路由器路由到特定的队列;消息最终到达队列,并被消费者接收。

消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。
通过队列路由键,可以把队列绑定到交换器上。
消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则)。如果能够匹配到队列,则消息会投递到相应队列中;如果不能匹配到任何队列,消息将进入 “黑洞”。

3.4  RabbitMQ 重要对象

ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。

  1. Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑
  2. ConnectionFactory为Connection的制造工厂
  3. 大部分的业务操作在Channel这个接口中完成,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等
  4. Queue是RabbitMQ的内部对象,消息都只能存储在Queue中,生产者生产消息并最终投递到Queue中,消费者从Queue中获取消息并消费。

3.5  RabbitMQ中的重要机制

3.5.1  Message acknowledgment(消息回执

在实际应用中,可能会发生消费者收到Queue中的消息,但没有处理完成就宕机(或出现其他意外)的情况,这种情况下就可能会导致消息丢失。为了避免这种情况发生,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(Message acknowledgment)后才将该消息从Queue中移除;如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。这里不存在timeout概念,一个消费者处理消息时间再长也不会导致该消息被发送给其他消费者,除非它的RabbitMQ连接断开。 这里会产生另外一个问题,如果我们的开发人员在处理完业务逻辑后,忘记发送回执给RabbitMQ,这将会导致严重的bug——Queue中堆积的消息会越来越多;消费者重启后会重复消费这些消息并重复执行业务逻。另外pub message是没有ack的。

3.5.2  Message durability(消息持久化)

如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。

3.5.3  Prefetch count(预载数量)

如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。

3.5.4  RPC(远程过程调用)

MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。 但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。

 RabbitMQ  中实现 RPC 的机制是:

  • 客户端发送请求(消息)时,在消息的属性(MessageProperties ,在AMQP 协议中定义了14中properties ,这些属性会随着消息一起发送)中设置两个值replyTo(一个Queue 名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue中)和correlationId (此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败)
  • 服务器端收到消息并处理
  • 服务器端处理完消息后,将生成一条应答消息到replyTo 指定的Queue ,同时带上correlationId 属性
  • 客户端之前已订阅replyTo 指定的Queue ,从中收到服务器的应答消息后,根据其中的correlationId 属性分析哪条请求被执行了,根据执行结果进行后续业务处理

3.6  Exchange Types

RabbitMQ常用的Exchange Type有fanout、direct、topic这3种。

3.6.1  direct

binding key与routing key完全匹配的才路由。

RabbitMQ消息队列工作原理及集成使用_第3张图片

以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。

3.6.2  topic

direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:

  • routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
  • binding key与routing key一样也是句点号“. ”分隔的字符串
  • binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

RabbitMQ消息队列工作原理及集成使用_第4张图片

以上图中的配置为例,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。

3.6.3 fanout

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

生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。

 

四、Spring boot 集成 RabbitMQ

4.1  引入maven依赖

这里是我们私服的依赖

4.2  生产消息

@RestController
@Slf4j
public class DirectPublish {

        @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/userAdd")
    public String publish() {
        // 要发送的数据
        User user = getUser();
        // 交换机类型是fanout,路由到所有队列,因此不需要指定routing key
        rabbitTemplate.convertAndSend(FanoutRabbitConfig.GOV_UC_USER_ADD_EXCHANGE, null, user);
        log.info(user.toString());
        return "ok";
    }

    private User getUser() {
        Random random = new Random();
        return User.builder()
                .id("-6666666")
                .name("葛锋" + random.nextInt(100))
                .loginName("zk")
                .orgAccountId("670869647114347")
                .build();
    }
}

4.3  消费消息

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "GOV_THIRDPARTY", durable = "true"),
        exchange = @Exchange(name = "GOV_THIRDPARTY_EXCHANGE"),
        key = "GOV_THIRDPARTY_SEEYOU"))
@Component
public class listener {
    @RabbitHandler
    public void handle(@Payload User user, AmqpMessageHeaderAccessor messageHeaderAccessor, Channel channel) throws IOException {
        System.out.println("消费队列===>" + user.toString());
    }
}

Rabbit的集成使用还是很简单的,简单注解即可。

这里RabbitTemplate已经封装了ConnectionFactory、Channel等。

真实的过程如下:

生产消息:

public class Send {

    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        // 消息内容
        String message = user.toString();
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");

        channel.close();
        connection.close();
    }
}

消费消息:

public class Recv {

    private final static String QUEUE_NAME = "queue_work1";

    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] argv) throws Exception {

        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);

        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);

        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println("消息内容: " + message + "'");
            Thread.sleep(10);

            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

 

五、其他

5.1  vhost 是什么?起什么作用?

vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ  server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。

5.2  如何确保消息接收方消费了消息?

接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。

下面罗列几种特殊情况:

  • 如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据bizId去重)
  • 如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多消息。

5.3  如何避免消息重复投递或重复消费?

在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重和幂等的依据,避免同一条消息被重复消费。

5.4  如何解决丢数据的问题?

1.生产者丢数据

从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。

transaction机制:发送消息前,开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。然而缺点就是吞吐量下降了。

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

2.消息队列丢数据

开启持久化磁盘的配置可以防止队列丢失数据。这个持久化配置可以和confirm机制配合使用,可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

那么如何持久化呢,就下面两步

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

②、发送消息的时候将deliveryMode=2

这样设置以后,rabbitMQ就算挂了,重启后也能恢复数据。在消息还没有持久化到硬盘时,可能服务已经死掉,这种情况可以通过引入mirrored-queue即镜像队列,但也不能保证消息百分百不丢失(整个集群都挂掉)

3.消费者丢数据

启用手动确认模式可以解决这个问题

①自动确认模式,消费者挂掉,待ack的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直到处理成功。不会丢失消息,即便服务挂掉,没有处理完成的消息会重回队列,但是异常会让消息不断重试。

②手动确认模式,如果消费者来不及处理就死掉时,没有响应ack时会重复发送一条信息给其他消费者;如果监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常;如果对异常进行了捕获,但是没有在finally里ack,也会一直重复发送消息(重试机制)。

③不确认模式,acknowledge="none" 不使用确认机制,只要消息发送完成会立即在队列移除,无论客户端异常还是断开,只要发送完就移除,不会重发。

5.5  如何确保消息正确地发送至RabbitMQ? 如何确保消息接收方消费了消息?

  • 发送方确认模式

将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。
如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

  • 接收方确认机制

接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;

你可能感兴趣的:(技术)