消息中间件之RabbitMQ死信队列

消息中间件之RabbitMQ死信队列

一、死信队列

1.1. 简述

RabbitMQ死信队列俗称备胎队列,消息中间件因为某种原因拒收该消息后,可以转移到死信队列中存放,死信队列也可以有交换机和路由key等。

1.2. 产生原因:

  • 消息队列投递的到MQ中存放,消息已经过期
  • 队列达到最大的长度,队列容器已经满了生产者拒绝接收消息
  • 消费者消费多次消息失败,就会转移到死信队列中

1.3. 注意:

死信队列最好不和和正常队列存放在同一个服务器中,应该分开服务器存放。

二、Springboot整合死信队列

2.1. Maven依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.2. 配置文件

这是需要先创建一个/longvirtual-host否则启动 报错。

spring:
  rabbitmq:
    # 连接地址
    host: 192.168.252.137
    # 端口号
    port: 5672
    # 账号
    username: guest
    # 密码
    password: guest
    # 地址
    virtual-host: /long

相关队列配置

public class RabbitVariable {
	/** 死信交换机 */
    public final static String DLX_EXCHANGE = "long_dlx_exchange";

    /** 死信队列 */
    public final static String DLX_ORDER_DLX_QUEUE = "long_order_dlx_queue";

    /** 路由key */
    public final static String DLX_ROUTING_KEY = "long_dlx_key";

	/** 订单交换机 */
    public final static String ORDER_EXCHANGE = "long_order_exchange";

    /** 订单队列 */
    public final static String ORDER_QUEUE = "long_order_queue";

    /** 路由key */
    public final static String ORDER_ROUTING_KEY = "long_order_key";

}

2.3. 配置类

@Component
public class RabbitConfig {

    /**
     * 死信交换机
     * @return
     */
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(RabbitVariable.DLX_EXCHANGE);
    }

    /**
     * 死信队列
     * @return
     */
    @Bean
    public Queue dlxQueue() {
        return new Queue(RabbitVariable.DLX_ORDER_DLX_QUEUE);
    }

    /**
     * 绑定死信队列到死信交换机
     * @return
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(dlxQueue())
                .to(dlxExchange())
                .with(RabbitVariable.DLX_ROUTING_KEY);
    }


    /**
     * 订单业务交换机
     * @return
     */
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(RabbitVariable.ORDER_EXCHANGE);
    }

    /**
     * 声明订单队列
     * @return
     */
    @Bean
    public Queue orderQueue() {
        // 订单队列绑定到我们的死信交换机
        Map<String, Object> arguments = new HashMap<>(2);
        // 绑定我们的死信交换机
        arguments.put("x-dead-letter-exchange", RabbitVariable.DLX_EXCHANGE);
        // 绑定我们的路由key
        arguments.put("x-dead-letter-routing-key", RabbitVariable.DLX_ROUTING_KEY);
        return new Queue(RabbitVariable.ORDER_QUEUE, true, false, false, arguments);
    }

    /**
     * 绑定订单队列到订单交换机
     * @return
     */
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
                .to(orderExchange())
                .with(RabbitVariable.ORDER_ROUTING_KEY);
    }



}

2.4. 死信队列消费者

/**
 * @author 墨龙吟
 * @version 1.0.0
 * @ClassName OrderDlxConsumer.java
 * @Description 死信消费者
 * @createTime 2020年03月02日 - 17:36
 */
@Component
public class OrderDlxConsumer {

    @RabbitListener(queues = RabbitVariable.DLX_ORDER_DLX_QUEUE)
    public void orderConsumer(String msg) {
        System.out.println("死信队列消费订单消息" + msg);
    }

}

2.5. 订单消费者

在测试死信对列的时候将代码注释掉就可以

@Component
public class OrderConsumer {

    /**
     * 监听队列回调的方法
     *
     * @param msg
     */
    @RabbitListener(queues = RabbitVariable.ORDER_QUEUE)
    public void orderConsumer(String msg) {
        System.out.println("正常订单消费者消息:" + msg);
    }

}

2.6. 控制器发送消息

/**
     * 投递消息,失效时间10s    Expiration  时间单位是ms
     * @return
     */
    @GetMapping("/sendMsg")
    public String sendMsg() {
        String msg = "发送消息" + UUID.randomUUID();
        System.out.println("发送消息:" + msg);
        rabbitTemplate.convertAndSend(RabbitVariable.ORDER_EXCHANGE, RabbitVariable.ORDER_ROUTING_KEY, msg, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        });
        return "success";
    }

2.7. 结果

三、RabbitMQ解决消息堆积

产生的背景:如果没有及时的消费者消费消息,生产者一直不断往队列服务器存放消息会导致消息堆积。

对应的场景:

  1. 没有消费者消费的情况下:通过死信队列、设置消息有效期(相当于对我们的消息设置有效期,在规定的时间内如果没有消费的话,自动过期,过期的时候会执行客户端回调监听的方法将消息存放到数据库记录,后期实现不补偿。)
  2. 有一个消费者消费的情况:应该提高我们的消费者,实现集群

四、消息中间件获取消费结果

解决方案: 异步+主动查询

简单实现:(对第二部分代码就行修改就可以)

4.1. 修改控制器

@GetMapping("/sendMsg")
public OrderEntity sendOrderMsg() {
    // 订单实体
    OrderEntity entity = new OrderEntity(UUID.randomUUID().toString(), "订单消息实体");
    String jsonString = JSONObject.toJSONString(entity);
    sendMsg(jsonString);
    // 返回订单实体,或者订单ID
    return entity;
}

/** 异步投递消息(使用同的话可以,使用ACK确认机制,但是效率比较低) */
@Async
public void sendMsg(String msg) {
    rabbitTemplate.convertAndSend(RabbitVariable.ORDER_EXCHANGE, RabbitVariable.ORDER_ROUTING_KEY, msg, new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setExpiration("10000");
            return message;
        }
    });
    // 投递失败可以将失败的订单信息存储到专门的表中去记录
}

/**
 * 1. 客户端主动查询接口
 * 2. 查询(投递失败表)该订单的消息是否投递失败
 * 3. 在查询订单数据库
 */
@RequestMapping("getOrder")
public Object getOrder(String orderId) {
    OrderEntity orderEntity = orderMapper.getOrder(orderId);
    if (orderEntity == null) {
        return "消息正在异步的处理中";
    }
    return orderEntity;
}

4.2. 订单消费者

@Component
public class OrderConsumer {

    /**
     * 监听队列回调的方法
     *
     * @param msg
     */
    @RabbitListener(queues = RabbitVariable.ORDER_QUEUE)
    public void orderConsumer(String msg) {
        System.out.println("正常订单消费者消息:" + msg);
        OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
        if (orderEntity == null) {
            return;
        }
        // 保存到数据库
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章