谷粒商城高级篇—RabbitMQ

延时队列(实现定时任务)

未付款订单,超时自动取消并释放占有。

常用解决方案:定时任务轮询

缺点:消耗内存,增加数据库压力,时间误差大

解决:RabbitMQ 消息TTL和死信Exchange结合

  • 消息TTL:消息存活时间,RabbitMQ可以对队列和消息分别设置TTL,同时设置取小的。

  • 下列条件,消息会进入死信路由

    1. 消息被Consumer拒收,并reject方法的参数requeue是false。
    2. 消息TTL到了,消息过期
    3. 队列的长度限制满了,前面的消息会被丢弃或扔到死信路由
  • Dead letter Exchange其实是一种普通的exchange,和创建其他exchange没有两样,只是

控制消息在一段时间变成死信,控制死信的消息被路由到某个指定交换机,实验延时队列。

延时队列实现-1

延时队列实现-2

推荐方式一给队列设置过期时间,因为RabbitMQ是惰性检查。

SpringBoot整合RabbitMQ

  1. 引入依赖

    <!--RabbitMQ-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
  2. 自动配置了 RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate

    #配置RabbitMQ
    spring.rabbitmq.host=127.0.0.1
    spring.rabbitmq.port=5672
    spring.rabbitmq.virtual-host=/

    测试 amqpAdmin、rabbitTemplate

    @Autowired
    AmqpAdmin amqpAdmin;
    @Autowired
    RabbitTemplate rabbitTemplate;
    /* *
    * 如何创建Exchange、Queue、Binding
    * 如何收发消息
    */
    @Test
    public void createExchange() {
    //Exchange
    amqpAdmin.declareExchange(new DirectExchange("hello-java-exchange",true,false));
    log.info("Exchange[{}]创建成功","hello-java-exchange");
    }
    @Test
    public void createQueue(){
    amqpAdmin.declareQueue(new Queue("hello-java-queue",true,false,false));
    log.info("Queue[{}]创建成功","hello-java-queue");
    }
    @Test
    public void createBinding(){
    amqpAdmin.declareBinding(new Binding("hello-java-queue",
    Binding.DestinationType.QUEUE,
    "hello-java-exchange",
    "hello.java",
    null));
    log.info("Binding[{}]创建成功","hello-java-binding");
    }
    @Test
    public void sendMessageTest(){
    OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
    reasonEntity.setId(1L);
    reasonEntity.setCreateTime(new Date());
    reasonEntity.setName("哈哈");
    // 如果发送消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable
    String msg = "hello world!";
    rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",reasonEntity);
    log.info("消息发送完成{}",reasonEntity);
    }
  3. @EnableRabbit

  4. 监听消息 @RabbitListener

    @RabbitListener(queues = {"hello-java-queue"})
    public void recieverMessage(Message message,
    OrderReturnReasonEntity content,
    Channel channel){
    //消息体
    byte[] body = message.getBody();
    //消息头
    MessageProperties messageProperties = message.getMessageProperties();
    System.out.println("接收到消息。。。内容:"+message+"==>类型:"+message.getClass());
    }
    • @RabbitListener 类+方法上
    • @RabbitHandler 标在方法上(重载区分不同的消息)

Queue可以很多人监听,同一个消息只能一个客户端收到

只要一个消息完全处理完,方法运行结束,可以接收下一个消息

RabbitMQ消息确认机制-可靠抵达

保证可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制。

  • publisher confirmCallBack 确认模式

    #开启发送端确认
    #spring.rabbitmq.publisher-confirms=true 已被弃用
    # NONE CORRELATED SIMPLE
    spring.rabbitmq.publisher-confirm-type=correlated
    //定制RabbitTemplate
    @PostConstruct //MyRabbitConfig对象创建完成以后,执行这个方法
    public void initRabbitTemplate(){
    //设置确认回调
    rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
    /* *
    * @param correlationData 当前消息的唯一关联数据
    * @param ack 消息是否成功收到
    * @param cause 失败的原因
    */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    System.out.println("confirm...correlationData["+correlationData+"]==>ack["+ack+"]==>cause["+cause+"]");
    }
    });
    }
  • publischer returnCallBack 未投递到 queue退回模式

    #开启发送端消息抵达确认
    spring.rabbitmq.publisher-returns=true
    #只要抵达队列,以异步方式优先回调我们这个returnconfirm
    spring.rabbitmq.template.mandatory=true
    //rabbitTemplate.setReturnsCallback();已被弃用
    rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
    System.out.println(returnedMessage);
    }
    });
  • consumer ack机制(保证每个消息被正确消费,此时broker删除这个消息)

    1、默认自动确认,只要消息接收到,客户端自动确认,服务端就会移除这个消息

    ​ 问题:收到多个消息,自动回复ack,只有一个消息处理成功,宕机,消息丢失

    ​ 解决:消费者手动确认

    #手动ack消息
    spring.rabbitmq.listener.simple.acknowledge-mode=manual

    2、如何签收?

    @RabbitHandler
    public void recieverMessage(Message message,
    OrderReturnReasonEntity content,
    Channel channel){
    //...消息处理完成
    //channel内按顺序自增的
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    // 签收,非批量模式
    try{
    if(deliveryTag%2 == 0){
    //签收货物
    channel.basicAck(deliveryTag,false);
    } else {
    //退货
    channel.basicNack(deliveryTag,false,false);
    //channel.basicReject(); 同上,只是不可批量
    //没有签收货物
    }
    } catch (Exception e) {
    //网络中断
    }
    }

延时队列关闭订单模拟

  1. 创建Exchange Queue Binding

    @Configuration
    public class MyMQConfig {
    //死信队列
    @Bean
    public Queue orderDelayQueue(){
    /*
    Queue(String name, 队列名字
    boolean durable, 是否持久化
    boolean exclusive, 是否排他
    boolean autoDelete, 是否自动删除
    Map<String, Object> arguments) 属性
    */
    HashMap<String, Object> arguments = new HashMap<>();
    arguments.put("x-dead-letter-exchange", "order-event-exchange");
    arguments.put("x-dead-letter-routing-key", "order.release.order");
    arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟
    Queue queue = new Queue("order.delay.queue", true, false, false, arguments);
    return queue;
    }
    //普通队列
    @Bean
    public Queue orderReleaseOrderQueue(){
    Queue queue = new Queue("order.release.order.queue", true, false, false);
    return queue;
    }
    //TopicExchange
    @Bean
    public Exchange orderEventExchange(){
    /*
    * String name,
    * boolean durable,
    * boolean autoDelete,
    * Map<String, Object> arguments
    * */
    return new TopicExchange("order-event-exchange", true, false);
    }
    @Bean
    public Binding orderCreateOrderBinding(){
    /*
    * String destination, 目的地(队列名或者交换机名字)
    * DestinationType destinationType, 目的地类型(Queue、Exhcange)
    * String exchange,
    * String routingKey,
    * Map<String, Object> arguments
    * */
    return new Binding("order.delay.queue",
    Binding.DestinationType.QUEUE,
    "order-event-exchange",
    "order.create.order",
    null);
    }
    @Bean
    public Binding orderReleaseOrderBinding(){
    return new Binding("order.release.order.queue",
    Binding.DestinationType.QUEUE,
    "order-event-exchange",
    "order.release.order",
    null);
    }
    }

    创建订单

    @GetMapping("/test/createOrder")
    public String creatOrderTest(){
    OrderEntity entity = new OrderEntity();
    entity.setOrderSn(UUID.randomUUID().toString());
    entity.setModifyTime(new Date());
    rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",entity);
    return "ok";
    }

    消费者,接收过期订单

    @RabbitListener(queues = "order.release.order.queue")
    public void listener(OrderEntity entity, Channel channel, Message message) throws IOException {
    System.out.println("收到过期的订单信息:准备关闭订单"+entity.getOrderSn());
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

如何保证消息可靠性

丢失

  • 网络问题,消息没到服务器

    1. try-catch
    2. 日志记录
    3. 定期发送
  • 消息抵达broker,未持久化宕机

    ​ publisher也加入确认回调机制

  • 自动ACK状态,消费者收到没来及处理宕机

    ​ 手动ACK

重复

  • 消费成功,事务提交,ack时机器宕机,Broker消息重新变为ready

  • 消费失败,重试机制 允许

    业务逻辑设计幂等性

    防重表

    消息redelivered属性

积压

  • 消费者宕机

  • 消费能力不足

  • 发送流量太大

    上线更多消费者

    上线专门的队列消费服务,批量取出消息,记录数据库,离线慢慢处理

posted @   爆辣牛筋丸  阅读(615)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示