RabbitMQ入门

前言开头

RabbitMQ是一款使用Erlang语言开发的,实现 AMQP(Advanced Message Queuing Protocol )的开源消息中间件。它支持多种客户端如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX,持久化,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 所以笔者整理了这篇RabbitMQ入门的文章根据下方思维导图来一一介绍。

什么是消息中间件

消息队列中间件是分布式系统中重要的组件,它提供了一种异步、可靠、可伸缩的消息传递机制;主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。

为什么使用消息中间件

先说一下它常见使用场景,比较核心的有 3 个:解耦、异步、削峰。

解耦

如下图所示,快递放入快递柜后,需要经历扣费、通知用户、通知快递公司三个业务动作;如果按照传统做法依次执行这些流程,那么当其中某个步骤异常,就会导致后续操作无法进行下去。 而当消息写入MQ,由后续三个子系统各自消费,那么其中某个子系统异常也不会导致其他子系统无法进行操作。

异步

如下图所示,快递投柜后直接结束了,无需等待到通知用户或通知快递公司再结束。系统会直接将消息投递到MQ,然后就直接返回响应给用户,具体的扣费以及后续的通知,都是异步操作的,无需用户关心,可以大大地提高性能。原先响应时间需要各子系统的总和,共800ms,现如今发送到MQ后交给系统异步处理用户只需要100ms就可以完成操作。

削峰

如下图所示。假设用户投递快递,高峰达到20w/s,但是我们的业务系统每秒只能处理10w个请求,对于如此庞大的请求处理不过来,导致系统瘫痪。如果使用MQ,我们可以把数据发送到MQ堆积着,然后由后台每次拉取10w个请求进行处理。

RabbitMQ的特点

  • 可靠性:支持持久化、传输确认及发布确认来保证可靠性。
  • 灵活的路由:在消息进入队列之前,通过交换机进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。
  • 扩展性:多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
  • 高可用性:队列可以在集群中的机器设置镜像,使得在部分节点出现故障时,队列仍然可用。
  • 多种协议:RabbitmMQ除了AMQP协议外,还支持STOMP、MQTT等多种消息队列协议。
  • 可视化管理界面:RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
  • 插件机制:RabbitMQ 提供了许多插件 ,可以通过插件进行扩展,也可以编写自己的插件。

RabbitMQ核心概念

RabbitMQ整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。它消息传递的过程如同,你寄快递,快递站可以暂存并且最终将快递由快递员送到收件人手上,RabbitMQ 就好比由包裹、快递站和快递员组成的一个系统,更像是一种交换机模型,其架构示例图如下:

Produer(生产者)和Consumer(消费者)

  • Producer(生产者):消息生产者,用于发布消息。
  • Consumer(消费者):消息消费者,消费队列中存储的消息。 消息一般由消息头和消息体组成。消息头(Label)用于存储与消息相关的元数据:如路由键 (RountingKey)和其他可选配置 (properties) 信息。消息体(payLoad )为实际需要传递的数据,消息体是不透明的。生产者将消息发送给RabbitMQ后,其会根据消息头把消息发送给感兴趣的Consumer。

Exchange(交换机)

消息队列交换器,根据一定的路由规则将消息路由到一个或多个队列。如果路由不到,当mandatory为true时则返回给生产者,mandatory为false时则直接丢弃。 生产者在发送消息给交换机时,会指定一个RoutingKey(路由键),用来指定路由规则,这个RoutingKey需要与交换机类型和BindingKey(绑定键)联合使用才能生效。如下图所示,RabbitMQ通过Binding(绑定)将Exchange(交换器)与Queue(消息队列)关联起来,在绑定的时候指定一个BindingKey,这样RabbitMQ就知道如何将消息正确发送到对应的队列中。绑定就是基于路由键将交换器和消息队列连接起来的路由规则,Exchange和Queue的绑定可以是多对多的关系。 生产者将消费者发送给交换机时,当RoutingKey与BindingKey相匹配,消息会被路由到对应的队列中。绑定多个队列到同一个Exchange时允许使用相同的BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如 fanout 类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。

Queue(消息队列)

用来存储消息直到发送给消费者。多个消费者可以订阅同一个队列,此时队列会将收到的消息以轮询(Round-Robin)的方式分发给多个消费者进行处理,即每条消息只发送给一个消费者,这样避免消息被重复消费。RabbiMQ不支持队列层面的广播消费。

Broker(消息中间件的服务节点)

简单来说Broker就是消息队列服务器实体,也可以看作是RabbitMQ服务节点或RabbitMQ服务实例。

Exchange Types(交换器类型)

Direct Exchange

直链交换机说白了就是它会将消息路由到Bindingkey与RoutingKey完全匹配的Queue 中。 以上图为例,如果Producer发送消息时RotuingKey=insert时,那么这时候消息会被Exchange路由到Queue1和Queue2这两个队列。如果我们以RotuingKey=delete和RotuingKey=update发送消息时,这时消息只会被推送到 Queue2队列中。其他RoutingKey的消息将会被丢弃。

Fanout exchange

fanout 类型的交换机会将消息路由到所有与它绑定的队列中,不管RoutingKey是否匹配。它通常用来广播消息。

Topic Exchange

topic类型交换机的路由规则是一种模糊匹配,可以通过通配符满足部分规则就可以传送消息。

  • RoutingKey为一个点好"."分割的字符串(我们将被句点号 ". " 分隔开的每一段独立的字符串称为一个单词),比如"com.rabbitmq.client"、 "org.springframework.amqp"、"org.springframework.boot"。BindingKey 和 RoutingKey 一样也是点号"."分隔的字符串。
  • BdingKey中可以存在两种特殊字符""与"#",用来做模糊匹配,其中" "用于匹配一个单词,"#"用来匹配多个单词(可以是零个)。 以上图为例:
  • 当生产者发送消息RoutingKey="com.rabbitmq.client"的时候,会同时被路由到Queue1和Queue2;
  • 当RoutingKey="org.springframework.amqp"时,消息只会被路由到Queue2;
  • 当RoutingKey="org.springframework.boot"时,消息只会被路由到Queue2;
  • 当RoutingKey="java.lang.String"时,消息会被丢弃或者返回给生产者;

Headers Exchange

这种交换机不是很常用,可以认为它是直连交换机的另一个表现形式,只不过它更灵活,它的路由不是通过RoutingKey进行路由匹配,而是根据匹配请求头中所带的键值对进行路由。

  • 绑定一个队列到 Headers Exchange上,会同时绑定多个用于匹配的header。
  • 传来的消息会携带header,以及会有一个 "x-match" 参数。当 "x-match" 设置为 "any" 时,消息头的任意一个值被匹配就可以满足条件,而当 "x-match" 设置为 "all" 的时候,就需要消息头的所有值都匹配成功。

RabbitMQ初体验

RabbitMQ安装

笔者是在CentOS环境下使用Docker安装RabbitMQ,简单快捷。

查询镜像

shell 复制代码
docker search rabbitmq:management
shell 复制代码
➜  ~ docker search rabbitmq:management
NAME                                DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
macintoshplus/rabbitmq-management   Based on rabbitmq:management whit python and...   10                   [OK]
transmitsms/rabbitmq-sharded        Fork of rabbitmq:management with sharded_exc...   0
yunyan2140/rabbitmq                 docker pull rabbitmq:management                 0
➜  ~

获取镜像

shell 复制代码
docker pull rabbitmq:management

运行镜像

shell 复制代码
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management

访问管理界面

运行成功后,访问http://[宿主机IP]:15672,如何通过默认账号密码登录访问,账号和密码都是guest。 使用guest用户登录,可以看到RabbitMQ的管理界面,到这里就完成安装部署了。

RabbitMQ代码示例

服务端搭建完后,我们就要开始客户端的操作, 接下来用一个简单示例来演示一下,首先我们引入maven依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

创建一个公共配置类,用来共享一些配置,比如队列主题,交换机名称,路由匹配键名称等等。

java 复制代码
public class RabbitMQConfig {

    /**
     * RabbitMQ的队列主题名称
     */
    public static final String RABBITMQ_DEMO_TOPIC = "rabbitmq.demo.topic";

    /**
     * RabbitMQ的DIRECT交换机名称
     */
    public static final String RABBITMQ_DEMO_DIRECT_EXCHANGE = "rabbitmq.demo.direct.exchange";

    /**
     * RabbitMQ的DIRECT交换机和队列绑定的匹配键 DirectRouting
     */
    public static final String RABBITMQ_DEMO_DIRECT_ROUTING = "rabbitmq.demo.direct.routing";

    /**
     * RabbitMQ的DIRECT交换机和队列绑定的匹配键 DirectRouting(用于测试ReturnCallback)
     */
    public static final String RABBITMQ_DEMO_DIRECT_ROUTING_RETURN_CALLBACK = "rabbitmq.demo.direct.routing.return";

    /**
     * RabbitMQ的DIRECT交换机名称(用于测试ConfirmCallback)
     */
    public static final String RABBITMQ_DEMO_DIRECT_EXCHANGE_CONFIRM_CALLBACK = "rabbitmq.demo.direct.exchange.confirm";
    
     /**
     * RabbitMQ的死信交换机名称
     */
    public static final String RABBITMQ_DEAD_DIRECT_EXCHANGE = "rabbitmq.dead.direct.exchange";
    
    /**
     * RabbitMQ的死信交换机和队列绑定的匹配键 DirectRouting
     */
    public static final String RABBITMQ_DEAD_DIRECT_ROUTING = "rabbitmq.dead.direct.routing";
}

然后在application.yml上配置RabbitMQ相应的配置信息,端口15672为网页管理,5672为AMQP端口

yml 复制代码
spring:
  rabbitmq: #rabbitmq相关配置
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

创建一个Direct交换机以及队列的配置

java 复制代码
@Component
@Configuration
public class DirectRabbitConfig {

    @Bean
    public Queue rabbitmqDemoDirectQueue() {
        /**
         * 1、name:    队列名称
         * 2、durable: 是否持久化
         * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
         * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
         * */
        return new Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC, true, false, false);
    }

    @Bean
    public DirectExchange rabbitmqDemoDirectExchange() {
        //Direct交换机
        return new DirectExchange(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, true, false);
    }

    @Bean
    public Binding bindDirect() {
        //链式写法,绑定交换机和队列,并设置匹配键
        return BindingBuilder
                //绑定队列
                .bind(rabbitmqDemoDirectQueue())
                //到交换机
                .to(rabbitmqDemoDirectExchange())
                //并设置匹配键
                .with(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING);
    }
}

然后创建一个发送消息的Service类

java 复制代码
public interface RabbitMQService {
    public String sendMsg(String msg);
}


@Service
public class RabbitMQServiceImpl implements RabbitMQService {
    //日期格式化
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Override
    public String sendMsg(String msg) {
        try {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            String sendTime = simpleDateFormat.format(new Date());
            Map<String, Object> map = new HashMap<>();
            map.put("msgId", msgId);
            map.put("sendTime", sendTime);
            map.put("msg", msg);
            rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, map);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }
}

然后我们写一个Controller来模拟业务发送消息。

java 复制代码
@RestController
@RequestMapping("/test/rabbitmq")
public class RabbitMQController {
    @Resource
    private RabbitMQService rabbitMQService;
    /**
     * 发送消息
     */
    @PostMapping("/sendMsg")
    public String sendMsg(@RequestParam(name = "msg") String msg) {
        return rabbitMQService.sendMsg(msg);
    }
}

这时我们在写消费者代码,创建消费者类,然后@RabbitListener注解写上监听队列的名称。

java 复制代码
@Component
public class RabbitMQConsumer {

    enum Action {
        //处理成功
        SUCCESS,
        //可以重试的错误,消息重回队列
        RETRY,
        //无需重试的错误,拒绝消息,并从队列中删除
        REJECT
    }

    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC))
    public void process(Map msg, Message message, Channel channel) {
         System.out.println("消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:" + JSON.toJSONString(msg));
    }
}

然后我们启动程序,通过idea自带的测试工具访问接口 然后我们在RabbitMQ管理界面可以看到刚创建的队列,和一条等待消费的消息。 然后我们转向idea看一下Consumer消费消息,至此我们的小demo演示就到这里了。

RabbitMQ进阶知识

如何保证消息的可靠性

在生产环境中可能会有一些突发原因导致rabbitMQ重启,那么在RabbitMQ重启时生产者消息投递失败,导致消息丢失,这些就需要手动去处理和恢复。那么我们如何去保障消息可靠的传递?无法投递的消息又该如何处理? 从上图可以看出,消息投递需要经过以下三个对象参与:

  • 生产者
  • broker
  • 消费者 生产者发送消息到broker时,需要保证消息的可靠性,那么主要采用以下两种方案:
  • confirm确认模式
  • return 退回模式

confirm确认模式

confirm确认模式是指生产者投递消息后,如果Broker收到消息,则会给生产者一个应答,如何生产者接收到应答后,用来确认这条消息是否正常发送到broker。 一旦消息投递到队列,队列则会向生产者发送一个通知,如果设置了消息持久化到磁盘,则会等待消息持久化到磁盘之后再发送通知。下面我们用代码来演示一下confirm确认模式。

首先开启confirm机制在application.yml配置publisher-confirm-type

yml 复制代码
spring:
  rabbitmq: #rabbitmq相关配置
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    publisher-confirm-type: correlated   #开启confirm机制

设置rabbitTemplate的confirmCallback回调函数

java 复制代码
public String sendMsgConfirmCallback(String msg) {
        try {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            String sendTime = simpleDateFormat.format(new Date());
            Map<String, Object> map = new HashMap<>();
            map.put("msgId", msgId);
            map.put("sendTime", sendTime);
            map.put("msg", msg);
            rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
                @Override
                public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                    if(!ack){
                        System.out.println("系统繁忙请重新发送");
                    }
                }
            });
            //错误的交换机用来测试发送到交换机消息失败
            rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE_CONFIRM_CALLBACK, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, map);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }

controller层代码

java 复制代码
    /**
     * 发送消息
     */
    @PostMapping("/sendMsgConfirmCallback")
    public String sendMsgConfirmCallback(@RequestParam(name = "msg") String msg) {
        return rabbitMQService.sendMsgConfirmCallback(msg);
    }

然后我们用idea自带http测试工具执行下,可以看到打印日志

return回退模式

ReturnListener用于处理一些不可路由的消息,例如某些情况,我们发送消息,但是当前exchange不存在或者指定的RoutingKey路由不到,那么这个时候我们就需要监听这种路由不可达的消息,然后通知生产者。它只会通知失败,不会通知成功,如果消息正确路由到队列,则发布者不会受到任何通知,因此它无法确保消息一定成功,因为路由到队列的消息可能会丢失。下面我们用代码来演示一下return回退模式。

在application.yml配置publisher-returns

yml 复制代码
spring:
  rabbitmq: #rabbitmq相关配置
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    publisher-returns: true #开启return机制  用来捕捉虚拟主机向队列中传递信息错误

设置ReturnsCallback回调函数

java 复制代码
 public String sendMsgReturnCallback(String msg) {
        try {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            String sendTime = simpleDateFormat.format(new Date());
            Map<String, Object> map = new HashMap<>();
            map.put("msgId", msgId);
            map.put("sendTime", sendTime);
            map.put("msg", msg);
            rabbitTemplate.setMandatory(true);
            rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
                @Override
                public void returnedMessage(ReturnedMessage returned) {
                    //当交换机发送消息到队列过程中失败启动当前方法
                    System.out.println(returned.getReplyCode());
                }
            });
            //错误的路由键用来测试发送到queue消息失败
            rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING_RETURN_CALLBACK, map);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }

controller层代码

java 复制代码
   /**
     * 发送消息
     */
    @PostMapping("/sendMsgReturnCallback")
    public String sendMsgReturnCallback(@RequestParam(name = "msg") String msg) {
        return rabbitMQService.sendMsgReturnCallback(msg);
    }

然后我们用idea自带http测试工具执行下,可以看到打印日志

持久化

前面我们分析了生产者如何保障消息的可靠性传递,那么在broker中它是怎么保证消息的可靠性的呢? 假设生产者已经将消息传递到交换机,而交换机也成功将消息路由到对应的队列中,此时mq重启了,那么消息还在吗? 为了防止这种情况发生,我们可以开启RabbitMQ的持久化,即消息写入后会持久化到磁盘,此时即使MQ挂掉了,重启之后也会自动读取之前存储的数据。 1、持久化队列,durable设置为true

java 复制代码
	@Bean
    public Queue rabbitmqDemoDirectQueue() {
        /**
         * 1、name:    队列名称
         * 2、durable: 是否持久化
         * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
         * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
         * */
        return new Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC, true, false, false);
    }

2、持久化交换机,durable设置为true

java 复制代码
	@Bean
    public DirectExchange rabbitmqDemoDirectExchange() {
        //Direct交换机
        return new DirectExchange(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, true, false);
    }

3、发送持久化消息,设置deliveryMode=2;SpringBoot的话,发送消息时自动设置deliveryMode=2。注意如果消息如果不设置过期时间默认为持久化。

设置好持久化参数后,可以在管理界面上查看到队列的Features为D。

消费方消息可靠性

1、ACK确认机制(手动确认)

txt 复制代码
消费者接收到消息,但是还未处理或者还未处理完消息,此时消费者挂了,此时mq会认为消费者已经完成消息消费,就会从队列中删除这些消息,从而导致消息丢失。

上诉情况要怎么解决呢?通过RabbitMQ提供的ACK确认机制,RabbitMQ默认是自动ack,此时需要改为手动ack确认(即自己的程序确定消息已经处理完成后,手动提交ack),此时遇到上述情况,由于没有提交ack,MQ就不会删除这条消息,而会将消息发送给其他消费者进行消费,避免消息丢失。

2、ACK的实现 在application.yml中添加acknowledge-mode: manual

yml 复制代码
spring:
  rabbitmq: #rabbitmq相关配置
    listener:
      simple:
        acknowledge-mode: manual # 手动ack

消费者实现

java 复制代码
    @RabbitHandler
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC))
    public void process(Map msg, Message message, Channel channel) throws IOException {

        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        byte[] body = message.getBody();
        String s = new String(body);
        logger.info("消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:" + JSON.toJSONString(msg));
        try {
            logger.info("消息的内容:" + s);
            /*
             * basicAck:确认消息 -- rabbit服务端删除
             * long deliveryTag,第一个参数为消息的标志
             * boolean multiple 第二个参数为是否把该消费之前未确认的消息一起确认掉
             * */
            channel.basicAck(deliveryTag, true);
        } catch (Exception e) {
            /*
             * basicNack:服务继续发送消息
             * long deliveryTag, boolean multiple, 前两个参数与上面的意义一样
             * boolean requeue 是否要求rabbitmq服务器重新发送该消息
             * */
            channel.basicNack(deliveryTag, true, true);
        }
     }

延迟队列

延迟队列指消息进入队列中并不会马上被消费者消费而是到指定时间才会被消费者消费。 RabbitMQ本来并没有延迟队列,但是可以依靠TTL(消息过期时间)和死信队列来实现消息延迟。 TTL(Time To Live)即消息过期时间,是RabbitMQ中一个消息或者队列的属性,单位是毫秒。当消息设置了TTL或者进入设置TTL的队列中时,在TTL设置的时间内没被消费则变为'死信';如果不设置TTL,则表示消息永远不会过期;如果TTL为0,则表示此时可以马上投递该消息到消费者,否则直接丢弃。下面我们看一下TTL设置案例

设置消息TTL

java 复制代码
 public String sendMsgExpiration() {
        try {
            Message message = new Message("zayton".getBytes());
            message.getMessageProperties().setExpiration("5000");
            rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, message);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }

设置队列TTL

java 复制代码
    @Bean
    public Queue rabbitmqDemoDirectQueue() {
        Map<String,Object> map = new HashMap<>();
        map.put("x-message-ttl",5000);
        /**
         * 1、name:    队列名称
         * 2、durable: 是否持久化
         * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
         * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
         * */
        return new Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC, true, false, false,map);
    }

死信队列

说到延时队列就不得不说下死信队列。一般来说,生产者将消息投递到队列中,消费者会从队列中取出消息进行消费,但是有时候由于某些原因导致队列中的消息无法被消费,这样的消息如果没有被后续处理就会变成死信,当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX(Dead-Letter-Exchange),即死信交换器。 导致死信的几种原因:

  • 消息 TTL 过期
  • 消息被拒绝( basicReject /basicNack),且requeue=false
  • 队列满了,无法添加消息

队列绑定死信交换机: 代码示例

java 复制代码
	@Bean
    public Queue rabbitmqDemoDirectQueue() {
        Map<String, Object> map = new HashMap<>(2);
        // x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
        map.put("x-dead-letter-exchange", RabbitMQConfig.RABBITMQ_DEAD_DIRECT_EXCHANGE);
        // x-dead-letter-routing-key 这里声明当前队列的死信路由key
        map.put("x-dead-letter-routing-key", RabbitMQConfig.RABBITMQ_DEAD_DIRECT_ROUTING);
        /**
         * 1、name:    队列名称
         * 2、durable: 是否持久化
         * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
         * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
         * */
        return new Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC, true, false, false,map);
    }

参考

javaguide.cn/high-perfor... zhuanlan.zhihu.com/p/163790007 blog.csdn.net/2301_774446...

相关推荐
kinlon.liu9 分钟前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
王哲晓30 分钟前
Linux通过yum安装Docker
java·linux·docker
java66666888834 分钟前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存35 分钟前
源码分析:LinkedList
java·开发语言
执键行天涯36 分钟前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
Jarlen1 小时前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽1 小时前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
Reese_Cool1 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
海里真的有鱼1 小时前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq