RabbitMq 死信队列简单实现

知识重点

1. RabbitMQ如果产生了消息堆积如何处理
         产生的背景:如果没有及时的消费者消费消息,生产者一直不断往队列服务器存放消息会导致消息堆积
         两种场景
                1.没有消费者消费的情况下: 死信队列、设置消息有效期相当于对我们的消息设置有效期,在规定的时间内如果没有消                              费的话,自动过期,过期的时候会执行客户端回调监听的方法将消息存放到数据库记录,后期实现不补偿。
                2.有一个消费者消费的情况:应该提高我们的消费者 消费实现集群

2. RabbitMQ如何彻底保证我们的消息不丢失?
          1.MQ服务器端应该消息持久化到硬盘
          2.生产者使用消息确认机制百分能够将消息投递到MQ成功
          3.消费者使用手动acm机制确认消息百分百消费成功


3.如果队列容量满了,在继续投递可能会丢失 死信队列
          死信队列:称做为备胎队列,消息中间件队列因为某种消费拒绝存放该消息,可以转移到死信队列中存放。
          死信队列产生的背景
                1.生产者投递消息到MQ中,消息过期了
                2.队列的已经达到最大长度(队列存放消息满了)MQ拒绝接受存放该消息。
                3.消费者多次消费该消息失败的情况,也会存放死信。
          死信队列不能够和正常队列存放在同一个服务器中,应该分开服务器存放

简单搭建死信队列

  1.声明订单和死信交换机、 订单和死信队列 、关键步骤订单队列绑定订单交换机和死信交换机 、死信队列绑定死信交换机

@Component
public class DeadLetterMQConfig {
    /**
     * 订单交换机
     */
    @Value("${mayikt.order.exchange}")
    private String orderExchange;
    /**
     * 订单队列
     */
    @Value("${mayikt.order.queue}")
    private String orderQueue;
    /**
     * 订单路由key
     */
    @Value("${mayikt.order.routingKey}")
    private String orderRoutingKey;
    /**
     * 死信交换机
     */
    @Value("${mayikt.dlx.exchange}")
    private String dlxExchange;
    /**
     * 死信队列
     */
    @Value("${mayikt.dlx.queue}")
    private String dlxQueue;
    /**
     * 死信路由
     */
    @Value("${mayikt.dlx.routingKey}")
    private String dlxRoutingKey;
    /**
     * 声明死信交换机
     */
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(dlxExchange);
    }
    /**
     * 声明死信队列
     */
    @Bean
    public Queue dlxQueue() {
        return new Queue(dlxQueue);
    }
    /**
     * 声明订单业务交换机
     */
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(orderExchange);
    }
    /**
     * 声明订单队列 核心操作一
     */
    @Bean
    public Queue orderQueue() {
        Map<String, Object> arguments = new HashMap<>(2);
        // 绑定我们的死信交换机
        arguments.put("x-dead-letter-exchange", dlxExchange);
        // 绑定我们的路由key
        arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
        return new Queue(orderQueue, true, false, false, arguments);
    }
    /**
     * 绑定订单队列到订单交换机
     */
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(orderRoutingKey);
    }
    /**
     * 绑定死信队列到死信交换机
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(dlxRoutingKey);
    }
}

2.application.properties文件配置 交换机队列相关名称和其他配置

server.port=8081
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#开启驼峰命名  譬如数据库create_time 自动映射pojo属性createTime
mybatis.configuration.map-underscore-to-camel-case=true

#配置virtual-host虚拟主机
spring.rabbitmq.virtual-host=/zhang_rabbit
#ip地址
spring.rabbitmq.host=127.0.0.1
#用户名  密码
spring.rabbitmq.username=zhang
spring.rabbitmq.password=zhang
#连接端口号
spring.rabbitmq.port=5672
#spring.rabbitmq.publisher-confirm-type=

#模拟演示死信队列
mayikt.dlx.exchange=mayikt_order_dlx_exchange
mayikt.dlx.queue=mayikt_order_dlx_queue
mayikt.dlx.routingKey=dlx

##备胎交换机
mayikt.order.exchange=mayikt_order_exchange
mayikt.order.queue=mayikt_order_queue
mayikt.order.routingKey=mayikt.order

3.创建producer生产者.并设置消息10秒过期,验证消息是否加入死信队列

@RestController
public class DeadLetterController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 订单交换机
     */
    @Value("${mayikt.order.exchange}")
    private String orderExchange;

    @Autowired
    private OrderMapper orderMapper;
    /**
     * 订单路由key
     */
    @Value("${mayikt.order.routingKey}")
    private String orderRoutingKey;

//    //方式一
//    @RequestMapping("/sendOrderMsg")
//    public String sendOrderMsg() {
//        rabbitTemplate.convertAndSend(orderExchange,orderRoutingKey,"订单消息", new MessagePostProcessor(){
//            @Override
//            public Message postProcessMessage(Message message) throws AmqpException {
//                //消息设置过期时间
//                message.getMessageProperties().setExpiration("10000");
//                //消息设置唯一id
//               // message.getMessageProperties().setUserId("10000");
//                return message;
//            }
//        });
//        return "success";
//    }


    //方式二  优化
    @RequestMapping("/sendOrderMsg")
    public String sendOrderMsg() {
        // 1.生产订单id
        String orderId = System.currentTimeMillis() + "";
        String orderName = "蚂蚁课堂第六期报名";
        OrderEntity orderEntity = new OrderEntity(orderName, orderId);
        String msg = JSONObject.toJSONString(orderEntity);
        sendMsg(msg); //rabbitmq生产消息 异步处理
        return orderId;
        // 后期客户端主动使用orderId调用服务器接口 查询该订单id是否在数据库中存在数据 消费成功 消费失败
    }

    //rabbit发送消息
    @Async //异步处理
    public void sendMsg(String msg) {
        rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg,messagePostProcessor());
        // 消息投递失败
    }

    //处理待发送消息
    private MessagePostProcessor messagePostProcessor(){
        return  new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        };
    }

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

4.创建订单消费者和死信消费者 

@Component //死信队列
public class OrderDlxConsumer {

    /**
     * 监听我们的死信队列
     */
    @RabbitListener(queues = "mayikt_order_dlx_queue")
    public void orderConsumer(String msg) {
        System.out.println("死信队列获取消息:" + msg);
    }
}



@Component
public class OrderConsumer {

    @Autowired
    private OrderMapper orderMapper;

    /**
     * 监听我们的订单队列
     */
    @RabbitListener(queues = "mayikt_order_queue")
    public void orderConsumer(String msg) {
        System.out.println("订单队列获取消息:" + msg);
        OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
        if (orderEntity == null) {
            return;
        }
        orderMapper.addOrder(orderEntity);
    }
}

5.注释掉订单消费者,生产消息,发现10秒后,消息转移到死信队列由死信队列消费调 ,放开订单消费者, 消息则由订单队列消费.

6.方式二优化: 生产者异步发送消息到队列,并返回订单id. 客户端根据返回订单id查询订单信息 , 查到表示消费成功 ,查不到表示正在处理或者消费失败

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