前言

本文主要参考了Understanding AMQP, the protocol used by RabbitMQ 要学习rabbitmq,就要先学习amqp协议,amqp全称Advanced Message Queuing Protocol (AMQP),rabbitmq实现了amqp,当然rocketmq也实现了amqp。amqp中有几个重要的概念,producer(publisher)、exchange、queue、consumer

rabbitmq入门、springboot集成rabbitmq_消息队列

消息从发送到接收的流程

producer发送message到指定的exchange,exchange根据message header中的routing-key与队列的binding-key决定绑定到哪个队列;那么consumer怎么获取消息呢?queue push或者consumer pull,这取决于配置。

消息的结构

一个消息由header、properties、data3部分组成,header是amq规范规定的类似http规范中的header,比如后边会讲到的routing-key,properties是业务自定义的,data是二进制数据;

rabbitmq入门、springboot集成rabbitmq_消息队列_02


通常我们把message分配到queue的过程叫binding,那消息结构跟binding有什么关系呢?exchange拿着message header中的routing-key与queue中的routing-key(每个queue都有一个routing-key)做匹配。一般的consumer创建queue时,会同时创建exchange,并将queue关联到exchange,producer发送到指定exchange,从而实现消息流转;

exchange类型

exchange常见的有以下几种类型,

  • direct
  • binding key与routing key完全匹配,*#会当作普通文本处理;
  • topic
  • binding key的*#当作通配符处理,*只能匹配一个word,#匹配零个或多个点号分隔的word;
  • fanout,n. 输出(端数);展(散)开;扇出; 分列账户;
  • 收到的全部消息转发给所有队列;

比如现有一个message,它的routing key是NYSE.TECH.MSFT,exchange是topic类型,那么匹配queue的binding key结果

rabbitmq入门、springboot集成rabbitmq_消息队列_03


以上包含了绝大多数情况。实际上,一个queue可以有多个consumer,一个exchange可以有多个queue,一个queue可以有多个exchange。

应用场景

RPC(远程过程调用)

rabbitmq入门、springboot集成rabbitmq_java-rabbitmq_04


这个过程是这样的

  1. 客户端发送一个message到queue,当然了message的routing key要匹配service。
  2. exchange将message传给service,service做了一些操作并返回响应message给exchange,响应message指定了routing_key以匹配响应queue。
  3. 客户端从响应queue中获取到message

至于这个过程是blocking还是non-blockding,就取决于client library,无论是哪种都可以做到。AmqpTemplate#sendAndReceive就是对应的这种模式

发布/订阅

rabbitmq入门、springboot集成rabbitmq_rabbitmq_05

一个消息会被多个消费者消费,在activemq中叫topic。

任务分发

一个message只会被多个consumer中的一个消费,在activemq中叫p2p(点对点)。

rabbitmq入门、springboot集成rabbitmq_spring_06

最后来一张自己总结的图

rabbitmq入门、springboot集成rabbitmq_java-rabbitmq_07

jms vs amqp

参考消息队列(为什么要用消息队列,常见消息队列对比,JMS和AMQP谁更好用?),简单来说JMS是java的规范,跟语言是相关的,而amqp是协议,与语言无关,jms提供了点对点发布/订阅两种模式,amq也提供了这两种模式,并在此基础上,对路由提供了多种选择。

springboot集成rabbitmq

springboot可以很方便的集成rabbitmq,引入spring-boot-starter-amqp,然后在配置文件中添加配置

spring:
  rabbitmq:
    host: "localhost"
    port: 5672
    username: "admin"
    password: "secret"

发送消息

@Component
public class MyBean {

    private final AmqpAdmin amqpAdmin;

    private final AmqpTemplate amqpTemplate;

    public MyBean(AmqpAdmin amqpAdmin, AmqpTemplate amqpTemplate) {
        this.amqpAdmin = amqpAdmin;
        this.amqpTemplate = amqpTemplate;
    }

    // ...

}

默认的template禁用了重试,可以启用重试,注意这是发送(AmqpTemplate )重试;

rabbitmq:
    template:
      retry:
        enabled: true
        initial-interval: "2s"

接收消息

@Component
public class MyBean {

    @RabbitListener(queues = "someQueue")
    public void processMessage(String content) {
        // ...
    }

}

注意,默认的listener重试被禁用,如果listener抛出异常,那么会无限重试;有2种方法解决,第一种
设置defaultRequeueRejected 属性为false,这样的话,就不会重试或者抛出一个AmqpRejectAndDontRequeueException 来通知消息不用再重新入queue。第2种方法是打开重试并设置最大重试次数。比如设置最大重试次数,这是监听器(Listener)的重试

rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto # 超过重试次数后,自动确认,消息会从队列中删除
        retry:
          enabled: true
          max-attempts: 3
          max-interval: 6000
          initial-interval: 2000
        default-requeue-rejected: true # 如果分发失败,是否仍然进入队列,设置为true

发送可以重试,Listener也可以重试,注意区分;

消息确认、消息丢失

参考Message acknowledgment

publisher丢了消息

因为网络的不确定性,可能在publisher发送到broker时,丢了消息,通常的解决方法是publisher发送前写入数据库,如果真的mq server丢失了,再手动补偿;还可能broker在处理message时出错了,导致了消息丢失;

mq server丢了消息

如果mq server重启了,那么队列及消息都丢失了,可以配置队列及消息是持久化的,这样重启也不会丢失队列和消息。但这不是绝对的不会丢失,因为mq server收到message会暂时放入cache在合适的时机再写入磁盘,如果真的需要绝对可靠可以考虑publisher confirm

consumer丢了消息

默认的,broker发送message给了consumer后,就会立刻删掉message。如果处理这个message要花费一段时间,还未处理完时,consumer终止了,那么这个message就丢失了,还有其他已经收到还未来得及处理的message;怎么解决这种问题呢?consumer 确认机制。处理完message后,发送ack,broker收到ack再删除message,如果一直没收到ack(默认是30分钟),则会放入messages_unacknowledged 队列。