RabbitMQ服务异步通信

1.消息可靠性问题

2.延迟消息问题

3.消息堆积问题

4.高可用问题

1.消息可靠性

四种解决方案

生产者确认机制

mq持久化

消费者确认机制

logging:
  pattern:
    dateformat: HH:mm:ss:SSS
  level:
    cn.itcast: debug
spring:
  rabbitmq:
#    host: 192.168.188.142 # rabbitMQ的ip地址
#    port: 5672 # 端口
    addresses: 192.168.188.142:8071, 192.168.188.142:8072, 192.168.188.142:8073
    username: itcast
    password: 123321
    virtual-host: /
    # 消息回调 异步
    publisher-confirm-type: correlated
    # 开启交换机路由到队列如果失败之后的回调
    publisher-returns: true
    template:
      # 路由失败之后调用publisher-returns方法
      mandatory: true

这是交换机回调ack,全局只需要写一个,所有可以写在配置类中

这个配置类的接口,的作用是,只要ioc容器初始化后,就会执行这个方法

@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {

    /**
     * ioc容器加载完成之后会调用
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取rabbitTemplate
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        // 设置returnCallBack
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                // 如果大于0代表是延迟队列的消息
                if (message.getMessageProperties().getReceivedDelay() > 0) {
                    return;
                }
                log.info("发送的消息 ===> {},响应码 ===> {} ,失败原因 ===> {},交换机 ===>  {},交换机路由的key ===>{}",
                        message, replyCode, replyText, exchange, routingKey);
                // 记录日志,人工干预
                // 或者重新投递
            }
        });
    }
}

然后队列中的ack回调,如果消息执行失败,机会返回ack结果

  @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2SimpleQueue() throws InterruptedException {
        String routingKey = "simple";
        String message = "hello, spring amqp!";
        //  指定消息id
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 指定回调
        correlationData.getFuture().addCallback(
                new SuccessCallback<CorrelationData.Confirm>() {
                    @Override
                    public void onSuccess(CorrelationData.Confirm result) {
                        if (result.isAck()) {
                            log.info("消息投递到交换机成功,消息的id ===>{}", correlationData.getId());
                        } else {
                            log.info("消息投递到交换机失败,消息的id ===>{},失败原因 ===> {}", correlationData.getId(), result.getReason());
                        }
                    }
                },
                new FailureCallback() {
                    @Override
                    public void onFailure(Throwable ex) {
                        log.info("消息投递到交换机失败,未知原因 ===>{}", ex.getMessage());
                    }
                }
        );
        rabbitTemplate.convertAndSend("amq.topic", routingKey, message, correlationData);
    }

另外,记得发消息的时候,加入correlationData,才能生效

消费者也要开启auto的ack回调,这样,如果消息不能正常被消费,就会返回队列,但是会形成死循环,使用srping的 retry 的消息重试机制,但是重试机制后,还是无法处理,消息还还是会丢失

接下来就要,绑定一个失败交换器,在网页上查看

spring:
  rabbitmq:
#    host: 192.168.188.142 # rabbitMQ的ip地址
#    port: 5672 # 端口
    addresses: 192.168.188.142:8071, 192.168.188.142:8072, 192.168.188.142:8073
    username: itcast
    password: 123321
    virtual-host: /
    listener:
      simple:
        prefetch: 1
        # 消息投递方式
        acknowledge-mode: auto
        retry:
          # 开启本地重试
          enabled: true
          # 第一次失败重试时间
          initial-interval: 1000
          # 倍数
          multiplier: 1
          # 最大次数
          max-attempts: 3
          # 是否有状态,消费者业务如果有事务,要设置为有状态
          # 无状态:true
          # 有状态:false
          stateless: true
/**
 * @author t3rik
 */
 @Configuration
public class ErrorDirectConfig {

    /**
     * 新建交换机
     */
    @Bean
    public DirectExchange errorDirect() {
        return new DirectExchange("error.direct");
    }


    /**
     * 新建队列
     */
    @Bean
    public Queue errorQueue() {
        return new Queue("error.queue");
    }


    /**
     * 绑定
     */
    @Bean
    public Binding bindingErrorQueueToErrorDirect() {
        // return BindingBuilder.bind(errorQueue).to(errorDirect).with("error");
        return BindingBuilder.bind(errorQueue()).to(errorDirect()).with("error");
    }


    /**
     * 指定MessageRecoverer
     */
    @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
    }
}

之后就是死信队列

 @Configuration
public class DeadQueueConfig {

    /**
     * 创建交换机
     */
    @Bean
    public DirectExchange ttlDirect() {
        return new DirectExchange("ttl.direct");
    }

    /**
     * 创建队列
     * deadLetterExchange 设置死信交换机
     * deadLetterRoutingKey 设置投递到死信交换机之后,该交换机路由到绑定队列时的key
     * ttl 设置过期时间
     */
    @Bean
    public Queue ttlQueue() {
        return QueueBuilder.durable("ttl.queue")
                .deadLetterExchange("dl.direct")
                .deadLetterRoutingKey("dl")
                .ttl(10000)
                .build();
    }

    /**
     * 队列和交换机绑定
     */
    @Bean
    public Binding bindingQueueToDirectExchange() {
        return BindingBuilder.bind(ttlQueue()).to(ttlDirect()).with("ttl");
    }
}
     @RabbitListener(bindings = @QueueBinding(
             value = @Queue(name = "dl.queue"),
             exchange = @Exchange(name = "dl.direct"),
             key = "dl"
     ))
     public void listenDeadQueue(String msg) {
         log.info("消费者接收到延迟的消息:【" + msg + "】");
     }

 值得注意的如果死信队列设置了过期时间,消息也设置了过期时间,按照过期时间短的来执行


    /**
     * 发送消息到死信队列
     *
     * @throws InterruptedException
     */
    @Test
    public void testSendMessage2DeadQueue() throws InterruptedException {
        String message = "hello, deadQueue!";
        // 消息持久化
        Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .setExpiration("5000")
                .build();
        rabbitTemplate.convertAndSend("ttl.direct", "ttl", msg);
    }

  

2.3.延迟队列

利用TTL结合死信交换机,我们实现了消息发出后,消费者延迟收到消息的效果。这种消息模式就称为延迟队列(Delay Queue)模式。

延迟队列的使用场景包括:

  • 延迟发送短信

  • 用户下单,如果用户在15 分钟内未支付,则自动取消

  • 预约工作会议,20分钟后自动通知所有参会人员

  • 使用mq的插件进行延迟队列

  • 第一种写配置类

  • 第二中直接写队列

  •      @RabbitListener(bindings = @QueueBinding(
                 value = @Queue(name = "delay.queue"),
                 exchange = @Exchange(name = "delay.direct",delayed = "true"),
                 key = "delay"
         ))
         public void listenDelayQueue(String msg) {
             log.info("消费者接收到延迟队列的消息:【" + msg + "】");
         }

    发送消息的时候要额外指定头

  •    /**
         * 发送消息到延迟队列
         */
        @Test
        public void testSendMessage2DelayQueue() {
            String message = "hello, delayQueue!";
            // 消息持久化
            Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8))
                    .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                    .setHeader("x-delay", 5000)
                    .build();
            rabbitTemplate.convertAndSend("delay.direct", "delay", msg);
        }
    

    如果是插件就会造成ack不能正常返回

  •                 // 如果大于0代表是延迟队列的消息
                    if (message.getMessageProperties().getReceivedDelay() > 0) {
                        return;
                    }

    惰性队列

  • /**
     * @author t3rik
     */
     @Configuration
    public class LazyQueueConfig {
    
    
        @Bean
        public Queue normalQueue() {
            return new Queue("normal.queue");
        }
    
        @Bean
        public Queue lazyQueue() {
            return QueueBuilder
                    .durable("lazy.queue")
                    .lazy()
                    .build();
    
        }
    }

  • Java代码创建仲裁队列

  • @Bean
    public Queue quorumQueue() {
        return QueueBuilder
            .durable("quorum.queue") // 持久化
            .quorum() // 仲裁队列
            .build();
    }

    SpringAMQP连接MQ集群

  • spring:
      rabbitmq:
        addresses: 192.168.150.105:8071, 192.168.150.105:8072, 192.168.150.105:8073
        username: itcast
        password: 123321
        virtual-host: /

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值