RabbitMq入门学习

目录

1. 消息队列

2.RabbitMQ简介

3.RabbitMQ的特点

4.RabbitMQ的组成部分

4.1.名词解释

4.2.RabbitMQ的工作流程

5.Exchange的类型以及用法

5.1.Direct Exchange

5.2.Fanout exchange

5.3.Topic exchange

5.4.Headers exchange

6.消息应答

6.1.自动应答

6.2.手动应答

6.3.消息自动重新入队

7.RabbitMQ持久化

7.1.交换机持久化

7.1.队列持久化

7.2.消息的持久化

8.扩展

8.1.延迟队列

8.2.死信队列

9.Demo

参考文献:


1. 消息队列

消息队列(Message Queue)是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列,消费者只负责从消息队列中取出数据消费处理。可以把消息传递的过程想象成:将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人的手上,消息队列就好比邮局、邮箱和邮递员组成的一个系统。

消息队列的作用:

  • 解耦,生产端和消费端不需要相互依赖。
  • 异步,生产端不需要等待消费端响应就直接返回,可以提高响应时间和吞吐量。
  • 削峰,打平高峰期的流量,消费端可以以自己的速度处理,同时也无需在高峰期增加太多资源,提高资源利用率。
  • 提高消费端性能。消费端可以利用buffer等机制,做批量处理,提高效率。

rabbitMq vs rocketMq vs kafka

rabbitMq

rocketMq

kafka

开发语言

erlang

java

scala

单机吞吐量

万级

十万级

十万级

topic数量影响

/

几百/几千topic影响很小

几十/几百topic吞吐量下降

时效性

微秒

毫秒

毫秒以内

可用性

高,主从

非常高,分布式

非常高,分布式

消息可靠性

基本不丢

经过参数配置0丢失

同rocketmq

功能支持

基于erlang开发,并发能力强,性能好,延迟低

性能较为完善,分布式,扩展性好

大数据实时计算和日志采集方面应用较多

2.RabbitMQ简介

RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。

AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。

AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。

RabbitMq比kafka成熟,在可用性上,稳定性上,可靠性上,RabbitMq超过kafka。

3.RabbitMQ的特点

RabbitMQ是一款使用Erlang语言开发的开源消息中间件。RabbitMQ的特点如下[1](官网):

  • 可靠性。支持持久化,传输确认,发布确认等保证了MQ的可靠性。
  • 灵活的分发消息策略。这应该是RabbitMQ的一大特点。在消息进入MQ前由Exchange(交换机)进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。
  • 支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
  • 多种协议。RabbitMQ支持多种消息队列协议,比如 STOMP、MQTT 等等。
  • 支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、.NET、Ruby 等等。
  • 可视化管理界面。RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker。
  • 插件机制。RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。

4.RabbitMQ的组成部分

RabbitMQ 的整体模型架构如图 4.1 所示:

图4.1 RabbitMQ 的整体模型架构

4.1.名词解释

Producer: 生产者,就是投递消息的一方;

Consumer: 消费者,就是接收消息的一方;

Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker(https://www.cnblogs.com/frankyou/p/5283539.html);

Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等;

Connection:publisher/consumer 和 broker 之间的 TCP 连接;

Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客 户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销;

Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发 消息到 queue 中去。常用的类型有:direct (point-to-point),topic (publish-subscribe) and fanout (multicast);

Queue:消息最终被送到这里等待 consumer 取走;

Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。

4.2.RabbitMQ的工作流程

初始状态下,生产者发送消息的时候[2]:

  1. 生产者连接到 RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel);
  2. 生产者声明一个交换器exchange,并设置相关属性,比如交换机类型、是否持久化等;
  3. 生产者声明一个队列queue并设置相关属性,比如是否排他、是否持久化、是否自动删除等;
  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. 关闭连接。

5.Exchange的类型以及用法

消息发送到RabbitMQ后首先要经过Exchange路由才能找到对应的Queue,Exchange类型有四种,根据不同的类型工作的方式也有所不同。四种类型包括:Direct Exchange(直连交换机),Fanout exchange(扇形交换机)、Topic exchange(主题交换机)、Headers exchange(头交换机)。

5.1.Direct Exchange

Direct 是 RabbitMQ 默认的交换机模式,也是最简单的模式,消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。Direct 类型交换器是完全匹配、单播的模式。

图5.1 Direct Exchange交换机

如图5.1所示,在这种绑定情况下,生产者发布消息到交换机上,绑定键为 direct.routing的消息会被发布到队列1并被消费者消费。

公共代码:

RabbitMQConfigConst:

 /**
     * RabbitMQ的队列主题名称
     */
    public static final String DIRECT_EXCHANGE_QUEUE_NAME = "direct.queue";

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

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

生产者代码:

RabbitMQConfig:

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

    @Bean
    public DirectExchange directExchange() {
        //Direct交换机
        return new DirectExchange(RabbitMQConfigConst.DIRECT_EXCHANGE_NAME, true, false);
    }

    /**
     * 定义一个队列和交换机的绑定
     *
     * @return
     */
    @Bean
    public Binding directBinding() {
        return BindingBuilder.bind(directExchangeQueue()).to(directExchange()).with(RabbitMQConfigConst.DIRECT_EXCHANGE_ROUTING);
    }

Service:

@Override
    public String sendMsg(String msg) throws Exception {
        try {
            //发送的时候携带的数据,唯一标识
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            CorrelationData correlationData = new CorrelationData(msgId);
            rabbitTemplate.convertAndSend(RabbitMQConfigConst.DIRECT_EXCHANGE_NAME, RabbitMQConfigConst.DIRECT_EXCHANGE_ROUTING, getMessage(msg), correlationData);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }

消费者代码:

    @RabbitListener(queues = {RabbitMQConfigConst.DIRECT_EXCHANGE_QUEUE_NAME})
    public void receiveDirectMessage(Map<String, Object> message, Channel channel) {
        log.info("Direct队列收到消息:" + message);
    }

效果:

启动生产者,使用postman发送一条消息:

启动消费者后,消费消息:

5.2.Fanout exchange

任何发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。简单的讲,就是把交换机(Exchange)里的消息发送给所有绑定该交换机的队列,忽略routingKey,即广播的模式。

图5.2 Fanout Exchange交换机

如图5.2所示,在这种绑定情况下,生产者发布消息到交换机上,消息会被发布所有队列上并被消费者消费。

公共代码:

RabbitMQConfigConst:

/**
     * RabbitMQ的FANOUT_EXCHANG交换机类型的队列 A 的名称
     */
    public static final String FANOUT_EXCHANGE_QUEUE_NAME_A = "fanout.queue.A";

    /**
     * RabbitMQ的FANOUT_EXCHANG交换机类型的队列 B 的名称
     */
    public static final String FANOUT_EXCHANGE_QUEUE_NAME_B = "fanout.queue.B";

    /**
     * RabbitMQ的FANOUT_EXCHANG交换机类型的名称
     */
    public static final String FANOUT_EXCHANGE_NAME = "fanout.exchange";

生产者代码:

RabbitMQConfig:

 /**
     * ************************************* FANOUT EXCHANGE *********************************
     */
    @Bean
    public Queue fanoutExchangeQueueA() {
        //队列A
        return new Queue(RabbitMQConfigConst.FANOUT_EXCHANGE_QUEUE_NAME_A, true, false, false);
    }

    @Bean
    public Queue fanoutExchangeQueueB() {
        //队列B
        return new Queue(RabbitMQConfigConst.FANOUT_EXCHANGE_QUEUE_NAME_B, true, false, false);
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        //创建FanoutExchange类型交换机
        return new FanoutExchange(RabbitMQConfigConst.FANOUT_EXCHANGE_NAME, true, false);
    }

    @Bean
    public Binding bindFanoutA() {
        //队列A绑定到FanoutExchange交换机
        return BindingBuilder.bind(fanoutExchangeQueueA()).to(fanoutExchange());
    }

    @Bean
    public Binding bindFanoutB() {
        //队列B绑定到FanoutExchange交换机
        return BindingBuilder.bind(fanoutExchangeQueueB()).to(fanoutExchange());
    }

Service:

 @Override
    public String sendMsgByFanoutExchange(String msg) throws Exception {
        Map<String, Object> message = getMessage(msg);
        try {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            CorrelationData correlationData = new CorrelationData(msgId);
            rabbitTemplate.convertAndSend(RabbitMQConfigConst.FANOUT_EXCHANGE_NAME, "", message, correlationData);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }

消费者代码:

@RabbitListener(queues = {RabbitMQConfigConst.FANOUT_EXCHANGE_QUEUE_NAME_A})
    public void receiveFanoutAMessage(Map<String, Object> message, Channel channel) {
        log.info("队列fanout.queue.A收到消息:" + message);
    }

    @RabbitListener(queues = {RabbitMQConfigConst.FANOUT_EXCHANGE_QUEUE_NAME_B})
    public void receiveFanoutBMessage(Map<String, Object> message, Channel channel) {
        log.info("队列fanout.queue.B收到消息:" + message);
    }

效果:

使用postman发送一条消息:

消费消息的记录:

5.3.Topic exchange

Topic 类型交换器转发消息和 Direct 一样,不同的是:它支持通配符转发,相比 Direct 类型更加灵活,两种通配符:*只能匹配一个单词,#可以匹配零个或多个。

如图5.3所示,如果传入的 routing key 为 a.*,则会将消息转发到队列1,不会转发到通配符c.*对应的队列2中。

图5.3 Topic Exchange交换机

公共代码:

RabbitMQConfigConst:

 /**
     * RabbitMQ的TOPIC_EXCHANGE交换机名称
     */
    public static final String TOPIC_EXCHANGE_NAME = "topic.exchange";

    /**
     * RabbitMQ的TOPIC_EXCHANGE交换机的队列A的名称
     */
    public static final String TOPIC_EXCHANGE_QUEUE_A = "topic.queue.a";

    /**
     * RabbitMQ的TOPIC_EXCHANGE交换机的队列B的名称
     */
    public static final String TOPIC_EXCHANGE_QUEUE_B = "topic.queue.b";

    /**
     * RabbitMQ的TOPIC_EXCHANGE交换机的队列C的名称
     */
    public static final String TOPIC_EXCHANGE_QUEUE_C = "topic.queue.c";

生产者代码:

RabbitMQConfig:

    /**
     * ************************************* TOPIC EXCHANGE *********************************
     */
    @Bean
    public TopicExchange topicExchange() {
        //配置TopicExchange交换机
        return new TopicExchange(RabbitMQConfigConst.TOPIC_EXCHANGE_NAME, true, false);
    }
    @Bean
    public Queue topicExchangeQueueA() {
        //创建队列1
        return new Queue(RabbitMQConfigConst.TOPIC_EXCHANGE_QUEUE_A, true, false, false);
    }
    @Bean
    public Queue topicExchangeQueueB() {
        //创建队列2
        return new Queue(RabbitMQConfigConst.TOPIC_EXCHANGE_QUEUE_B, true, false, false);
    }
    @Bean
    public Queue topicExchangeQueueC() {
        //创建队列3
        return new Queue(RabbitMQConfigConst.TOPIC_EXCHANGE_QUEUE_C, true, false, false);
    }
        @Bean
    public Binding bindTopicA() {
        //队列B绑定到TopicExchange交换机
        return BindingBuilder.bind(topicExchangeQueueB())
                .to(topicExchange())
                .with("a.*");
    }
    @Bean
    public Binding bindTopicB() {
        //队列C绑定到TopicExchange交换机
        return BindingBuilder.bind(topicExchangeQueueC())
                .to(topicExchange())
                .with("c.*");
    }
    @Bean
    public Binding bindTopicC() {
        //队列A绑定到TopicExchange交换机
        return BindingBuilder.bind(topicExchangeQueueA())
                .to(topicExchange())
                .with("rabbit.#");
    }

Service:

   @Override
    public String sendMsgByTopicExchange(String msg, String routingKey) throws Exception {
        Map<String, Object> message = getMessage(msg);
        try {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            CorrelationData correlationData = new CorrelationData(msgId);
            //发送消息
          rabbitTemplate.convertAndSend(RabbitMQConfigConst.TOPIC_EXCHANGE_NAME, routingKey, message, correlationData);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }


消费者代码:

@RabbitListener(queues = {RabbitMQConfigConst.TOPIC_EXCHANGE_QUEUE_A})
    public void receiveTopicAMessage(Map<String, Object> message, Channel channel) {
        log.info("队列topic.queue.a收到消息:" + message);

    }

    @RabbitListener(queues = {RabbitMQConfigConst.TOPIC_EXCHANGE_QUEUE_B})
    public void receiveTopicBMessage(Map<String, Object> message, Channel channel) {
        log.info("队列topic.queue.b收到消息:" + message);

    }

    @RabbitListener(queues = {RabbitMQConfigConst.TOPIC_EXCHANGE_QUEUE_C})
    public void receiveTopicCMessage(Map<String, Object> message, Channel channel) {
        log.info("队列topic.queue.c收到消息:" + message);

    }

效果:

使用postman发送一条消息:

消费消息的记录:

5.4.Headers exchange

匹配的是Header而不是路由键,在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ 会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列。

headers 类型功能和Direct完全一致,交换器性能会很差,而且也不实用,基本上不会看到它的存在。

图5.3 Headers Exchange交换机

如图5.3所示,在这种绑定情况下,生产者发布带有头部信息{"queue_a":"java","queue_a":"rabbit”}的消息到交换机上,消息会被到队列1并被消费者消费。

公共代码:

RabbitMQConfigConst:

/**
     * HEADERS_EXCHANGE交换机名称
     */
    public static final String HEADERS_EXCHANGE_NAME = "headers.exchange";

    /**
     * RabbitMQ的HEADERS_EXCHANGE交换机的队列A的名称
     */
    public static final String HEADERS_EXCHANGE_QUEUE_A = "headers.queue.a";

    /**
     * RabbitMQ的HEADERS_EXCHANGE交换机的队列B的名称
     */
    public static final String HEADERS_EXCHANGE_QUEUE_B = "headers.queue.b";

生产者代码:

RabbitMQConfig:

/**
     * ************************************* HEADERS EXCHANGE *********************************
     */
    @Bean
    public Queue headersQueueA() {
        return new Queue(RabbitMQConfigConst.HEADERS_EXCHANGE_QUEUE_A, true, false, false);
    }

    @Bean
    public Queue headersQueueB() {
        return new Queue(RabbitMQConfigConst.HEADERS_EXCHANGE_QUEUE_B, true, false, false);
    }

    @Bean
    public HeadersExchange headersExchange() {
        return new HeadersExchange(RabbitMQConfigConst.HEADERS_EXCHANGE_NAME, true, false);
    }

@Bean
    public Binding bindHeadersA() {
        Map<String, Object> map = new HashMap<>();
        map.put("queue_a1", "java");
        map.put("queue_a2", "rabbit");
        //全匹配
        return BindingBuilder.bind(headersQueueA())
                .to(headersExchange())
                .whereAll(map).match();
    }

    @Bean
    public Binding bindHeadersB() {
        Map<String, Object> map = new HashMap<>();
        map.put("queue_b1", "coke");
        map.put("queue_b2", "sky");
        //部分匹配
        return BindingBuilder.bind(headersQueueB())
                .to(headersExchange())
                .whereAny(map).match();
    }

Service:

     @Override
    public String sendMsgByHeadersExchange(String msg, Map<String, Object> map) throws Exception {
        try {
            MessageProperties messageProperties = new MessageProperties();
            //消息持久化         messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            messageProperties.setContentType("UTF-8");
            //添加消息
            messageProperties.getHeaders().putAll(map);
            Message message = new Message(msg.getBytes(), messageProperties);           rabbitTemplate.convertAndSend(RabbitMQConfigConst.HEADERS_EXCHANGE_NAME, null, message);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }

消费者代码:

     @RabbitListener(queues = {RabbitMQConfigConst.HEADERS_EXCHANGE_QUEUE_A})
    public void receiveHeadersAMessage(String message, Channel channel) {
        log.info("队列headers.queue.a收到消息:" + message);
    }

    @RabbitListener(queues = {RabbitMQConfigConst.HEADERS_EXCHANGE_QUEUE_B})
    public void receiveHeadersBMessage(String message, Channel channel) {
        log.info("队列headers.queue.b收到消息:" + message);
    }

效果:

使用postman发送一条消息:

消费消息的记录:

6.消息应答

RabbitMQ 一旦向消费者发送了一条消息,便立即将该消息标记为删除。在这种情况下,若突然有个消费者挂掉了,将丢失正在处理的消息。为了保证消息在发送过程中不丢失,引入消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。

6.1.自动应答

 不在乎消费者对消息处理是否成功,都会告诉队列删除消息。如果处理消息失败,实现自动补偿(队列投递过去 重新处理)。

6.2.手动应答

开启手动应答

spring:
  rabbitmq:
    # 确认消息已发送到队列 return
    publisher-returns: true
    # 开启消息确认机制 confirm 异步
    publisher-confirm-type: correlated
    listener:
      direct:
        # 消息开启手动确认
        acknowledge-mode: manual
        # 拒绝消息是否重回队列
        default-requeue-rejected: true

Channel.basicAck (肯定确认应答):

//deliveryTag:消息的标记, multiple:是否应用于多消息,RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
basicAck(long deliveryTag, boolean multiple);            //channel.basicAck(deliveryTag, false);

Channel.basicReject (否定确认应答)

//deliveryTag:拒绝 deliveryTag 对应的消息,requeue:是否 requeue:true 则重新入队列,false 则丢弃或者进入死信队列。该方法 reject 后,该消费者还是会消费到该条被 reject 的消息。
basicReject(long deliveryTag, boolean requeue);
//channel.basicReject(deliveryTag, false);

Channel.basicNack (用于否定确认):已拒绝处理该消息,可以将其丢弃了

//deliveryTag:拒绝 deliveryTag 对应的消息, multiple:是否应用于多消息,requeue:是否 requeue,与 basicReject 区别就是同时支持多个消息,可以拒绝签收该消费者先前接收未ack的所有消息。拒绝签收后的消息也会被自己消费到
basicNack(long deliveryTag, boolean multiple, boolean requeue);
//channel.basicNack(deliveryTag, false, true);

Channel.basicRecover

//是否恢复消息到队列,参数是是否 requeue,true 则重新入队列,并且尽可能的将之前 recover 的消息投递给其他消费者消费,而不是自己再次消费。false 则消息会重新被投递给自己。
basicRecover(boolean requeue);

6.3.消息自动重新入队

如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息未发送 ACK 确认,RabbitMQ 将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。

图6.1 消息自动重新入队示例

7.RabbitMQ持久化

rabbitmq持久化存储包含三个方面:exchange的持久化,queue的持久化,message的持久化

7.1.交换机持久化

 在申明exchange的时候,有个参数:durable。当该参数为true,则对该exchange做持久化,重启rabbitmq服务器,该exchange不会消失。durable的默认值为true

/**
         * 1、name:    交换机名称
         * 2、durable: 是否持久化
         * 3、autoDelete: 是否自动删除。
         * */
new DirectExchange(RabbitMQConfigConst.DIRECT_EXCHANGE_NAME, true, false);

7.1.队列持久化

如果我们创建的队列是非持久化的,如果重启RabbitMQ ,该队列就会被删除掉。如果要队列实现持久化需要在声明队列的时候把 durable 参数设置为true,代表开启持久化,如果之前声明的队列不是持久化的,需要把原先队列先删除,或者重新创建一个持久化的队列,不然就会出现错误。

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

7.2.消息的持久化

需要在消息生产者发布消息的时候,开启消息的持久化

只需要发送消息的时候将 deliveryMode设置为2,也就是设置为MessageDeliveryMode.PERSISTENT,其中(NON_PERSISTENT=1,PERSISTENT=2)

 MessageProperties messageProperties = new MessageProperties();
            //MessageProperties 封装消息的一些属性,属性比较多,我这里只设置以下消息的持久化,PERSISTENT-持久化  NON_PERSISTENT-非持久化        
    messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            messageProperties.setContentType("UTF-8");
                  

8.扩展

8.1.延迟队列

延时队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望 在指定时间到了以后或之前取出和处理,即延时队列就是用来存放需要在指定时间被处理的 元素的队列。

延迟队列使用场景[3]:

  1. 订单在十分钟之内未支付则自动取消
  2. 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒
  3. 用户注册成功后,如果三天内没有登陆则进行短信提醒
  4. 用户发起退款,如果三天内没有得到处理则通知相关运营人员
  5. 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

公共代码:

RabbitMQConfigConst:

/**
     * delayedDirect交换机名称
     */
    public static final String DELAYED_DIRECT_EXCHANGE = "delayed.direct.exchange";

    /**
     * delayed direct队列名称
     */
    public static final String DELAYED_DIRECT_QUEUE = "delayed.direct.queue";

    /**
     * delayed_direct路由Key
     */
    public static final String DELAYED_DIRECT_ROUTING = "delayed.direct.routing";

生产者代码:

RabbitMQConfig:

    /** ************************************** 延迟消息 ********************************* */

    /**
     * 定义一个delayed direct交换机
     *
     * @return
     */
    @Bean
    public CustomExchange delayedDirectExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(RabbitMQConfigConst.DELAYED_DIRECT_EXCHANGE, "x-delayed-message", true, false, args);

    }

    /**
     * 定义一个DELAYED direct队列
     *
     * @return
     */
    @Bean
    public Queue delayedDirectQueue() {
        return new Queue(RabbitMQConfigConst.DELAYED_DIRECT_QUEUE);
    }

    /**
     * 定义一个队列和交换机的绑定
     *
     * @return
     */
    @Bean
    public Binding delayedDirectBinding() {
        return BindingBuilder.bind(delayedDirectQueue()).to(delayedDirectExchange()).with(RabbitMQConfigConst.DELAYED_DIRECT_ROUTING).noargs();
    }

Service:

         @Override
    public String sendDelayedMessage(String msg, Integer delayTime) {
        log.info("发送延迟消息:{},延迟时间:{},当前时间:{}", msg, delayTime, new Date().toString());
        amqpTemplate.convertAndSend(RabbitMQConfigConst.DELAYED_DIRECT_EXCHANGE, RabbitMQConfigConst.DELAYED_DIRECT_ROUTING, getMessage(msg), a -> {
            a.getMessageProperties().setDelay(delayTime);
            return a;
        });
        return "ok";
    }

消费者代码:

     @RabbitListener(queues = {RabbitMQConfigConst.DELAYED_DIRECT_QUEUE})
    public void receiveDelayMessage(Map<String, Object> message) {
        log.info("接收延迟消息:" + message + ":" + new Date().toString());
    }

效果:

使用postman发送一条消息:

消费消息的记录:

8.2.死信队列

死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到queue 里了,consumer 从 queue 取出消息 进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中。如用户在商城下单成功并点击去支付后在指定时间未支付时自动失效。

图8.1 死信队列示意

如图8.1所示,含有两个消费者,一个生产者,两个队列:普通消息队列和死信队列,消息到达普通消息队列后,若出现消息 TTL 过期,队列达到最大长度或消息被拒(basic.reject 或 basic.nack) 并且 requeue = false,消息将被放入死信队列。

如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这条消息如果在 TTL 设置的时间内没有被消费,则会成为「死信」,示例如下:

公共代码:

RabbitMQConfigConst:

     //******************** TTL  *************************
    /**
     * TTL_direct交换机名称
     */
    public static final String TTL_DIRECT_EXCHANGE_NAME = "ttl.direct.exchange";

    /**
     * ttl_direct路由Key
     */
    public static final String TTL_DIRECT_EXCHANGE_ROUTING = "ttl.direct.routing";

    /**
     * ttl_direct队列名称
     */
    public static final String TTL_DIRECT_EXCHANGE_QUEUE = "ttl.direct.queue";


    // ***************** DLX ***************************

    /**
     * DLX_direct交换机名称
     */
    public static final String DLX_DIRECT_EXCHANGE = "dlx.direct.exchange";

    /**
     * dlx_direct队列名称
     */
    public static final String DLX_DIRECT_QUEUE = "dlx.direct.queue";

    /**
     * dlx_direct路由Key
     */
    public static final String DLX_DIRECT_ROUTING = "dlx.direct.routing";

生产者代码:

RabbitMQConfig:

/** ************************************** TTL EXCHANGE ********************************* */
    /**
     * /** 定义一个TTL direct交换机
     *
     * @return
     */
    @Bean
    public DirectExchange ttlDirectExchange() {
        return new DirectExchange(RabbitMQConfigConst.TTL_DIRECT_EXCHANGE_NAME);
    }

    /**
     * 定义一个TTL direct队列
     *
     * @return
     */
    @Bean
    public Queue ttlDirectExchangeQueue() {
        Map<String, Object> map = new HashMap<>();
        map.put("x-message-ttl", 50000);
        map.put("x-dead-letter-exchange", RabbitMQConfigConst.DLX_DIRECT_EXCHANGE);
        map.put("x-dead-letter-routing-key", RabbitMQConfigConst.DLX_DIRECT_ROUTING);
        map.put("x-max-length", 100);   // 队列中最多消息数量限制
        return new Queue(RabbitMQConfigConst.TTL_DIRECT_EXCHANGE_QUEUE, true, false, false, map);
    }

    /**
     * TTL定义一个队列和交换机的绑定
     *
     * @return
     */
    @Bean
    public Binding ttlDirectBinding() {
        return BindingBuilder.bind(ttlDirectExchangeQueue()).to(ttlDirectExchange()).with(RabbitMQConfigConst.TTL_DIRECT_EXCHANGE_ROUTING);
    }

    /** ************************************** DLX EXCHANGE死信队列 ********************************* */
    /**
     * /**
     * 定义一个DLX direct交换机
     *
     * @return
     */
    @Bean
    public DirectExchange dlxDirectExchange() {
        return new DirectExchange(RabbitMQConfigConst.DLX_DIRECT_EXCHANGE);
    }

    /**
     * 定义一个DLX direct队列
     *
     * @return
     */
    @Bean
    public Queue dlxDirectQueue() {
        return new Queue(RabbitMQConfigConst.DLX_DIRECT_QUEUE);
    }

    /**
     * dlx定义一个队列和交换机的绑定
     *
     * @return
     */
    @Bean
    public Binding dlxDirectBinding() {
        return BindingBuilder.bind(dlxDirectQueue()).to(dlxDirectExchange()).with(RabbitMQConfigConst.DLX_DIRECT_ROUTING);
    }

Service:

    @Override
    public String sendTTLMessage(String msg) {
        String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
        CorrelationData correlationData = new CorrelationData(msgId);
        rabbitTemplate.convertAndSend(RabbitMQConfigConst.TTL_DIRECT_EXCHANGE_NAME, RabbitMQConfigConst.TTL_DIRECT_EXCHANGE_ROUTING, getMessage(msg), correlationData);
        return "ok";
    }

消费者代码:

    @RabbitListener(queues = {RabbitMQConfigConst.DLX_DIRECT_QUEUE})
    public void receiveDlxMessage(Map<String, Object> message) {
        log.info("接收死信消息:" + message + ":" + new Date().toString());
    }

效果:

使用postman发送一条延迟消息:

50s未被消费后将被放入死信队列

死信队列消费消息的记录:

正常消费的消费记录:

9.Demo

WangLi321/RabbitMqDemo · GitHub

参考文献:

  1. Messaging that just works — RabbitMQ
  2. https://skylor-tang.github.io/2021/04/27/RabbitMQ-%E5%9F%BA%E7%A1%80%E9%83%A8%E5%88%86
  3. https://frxcat.fun/middleware/RabbitMQ/RabbitMQ_Delay_queue/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值