关于SpringBoot在项目中整合RabbitMQ中间件

        在我们平时做项目的时候,我们总会需要调用消息中间件来在各个模块之间实现消息的传递,那么我们就需要充分了解SpringBoot项目整合RabbitMQ中间件的整体流程。(以谷粒商城项目为例)

步骤① 导入RabbitMQ依赖

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

这里我们需要知道,SpringBoot官方已经为我们准备好了关于RabbitMQ的启动器,都作为高级消息队列,存在于spring-boot-starter-amqp依赖中。

 步骤② 定制化RabbitTemplate

我们都知道,SpringBoot为我们提供了RabbitTemplate类,来帮助我们在Java代码中直接操作我们的RabbitMQ,但是,对于官方提供的原生RabbitTemplate我们需要为我们项目具体要求进行定制化调整(如果项目中不需要,这步骤可以忽略)

@Configuration
public class MyRabbitConfig {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    /**
     * 定制RabbitTemplate
     * @PostConstruct :MyRabbitConfig对象创建完成以后,执行这个方法
     * 1. 服务收到消息就回调:
     *      1. spring.rabbitmq.publisher-confirms=true
     *      2. 设置确认回调
     * 2. 消息正确抵达队列进行回调
     *      1. spring.rabbitmq.publisher-return=true
     *         spring.rabbitmq.template.mandatory=true
     *      2. 设置确认回调ReturnCallback
     * 3. 消费端确认(保证每一个消息被正确确认,然后服务端才能删除消息)
     *      必须加上配置:spring.rabbitmq.listener.direct.acknowledge-mode=manual
     *      1. 默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
     *          问题:我们收到一个消息,自动回复给服务器ack,只有一个消息处理成功,宕机了,发生消息丢失;
     *          消费者手动确认模式,只要我们没有明确告诉MQ,消息被签收,没有ack,消息就一直是unacked状态。即使Consumer宕机,消息不会丢失,会重新
     *          变为ready,下一次有新的Consumer连接进来就发给他
     *      2. 如何签收
     *          channel.basicAck(deliverTag,false):签收 业务成功完成就签收
     *          channel.basicNack(deliveryTag,false,true):拒签 业务失败就拒签
     *
     */
    @PostConstruct
    public void initRabbitTemplate(){
        //设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *1.只要消息抵达Broker就ack=true
             * @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
             * @param b 消息是否成功收到
             * @param s 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                /**
                 * 1.做好消息确认机制(publisher,consumer【手动ack】)
                 * 2.每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍
                 */
                //服务器收到了
                System.out.println("confirm...correlationData:" + correlationData + ",ack:" + b + ",cause:" + s);
            }
        });

        //设置消息抵达队列的确认回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有投递给指定的队列,就触发这个失败回调
             * @param message 投递失败的消息详细信息
             * @param replyCode 回复状态码
             * @param replyText 回复的文本内容
             * @param exchange 当时这个消息发给哪个交换机
             * @param routingKey 当时这个消息用哪个路由键发送的
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                //报错误了,修改数据库当前消息的状态
                System.out.println("Fail Message" + message + ",replyCode" + replyCode);
            }
        });
    }
}

其实定制化RabbitTemplate主要调用的方法就是`initRabbitTemplate()` 重写方法

步骤③ 编写配置文件


spring.rabbitmq.host=你的RabbitMQ所在的服务器地址
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/

#开启发送端确认
spring.rabbitmq.publisher-confirms=true
#开启发送端消息抵达队列确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步方式优先回调我们这个returnConfirm
spring.rabbitmq.template.mandatory=true
#手动ack消息
spring.rabbitmq.listener.direct.acknowledge-mode=manual

步骤④ 装配组件

@Configuration
public class MyMQConfig {

    @Bean
    public Queue orderDelayQueue() {
        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);
        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;
    }

    @Bean
    public Exchange orderEventExchange() {
        return new TopicExchange("order-event-exchange", true, false);
    }

    @Bean
    public Binding orderCreateOrder() {
        return new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.create.order",
                null);
    }

    @Bean
    public Binding orderReleaseOrder() {
        return new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.order",
                null);
    }

    @Bean
    public Binding orderReleaseOther() {
        return new Binding("stock.release.stock.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.other.#",
                null);
    }

    @Bean
    public Queue orderSeckillOrderQueue() {
        return new Queue("order.seckill.order.queue", true, false, false, null);
    }

    @Bean
    public Binding orderSeckillOrderQueueBinding() {
        return new Binding("order.seckill.order.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.seckill.order", null);
    }
}

这步我们需要分析出我们项目模块中所需要用到的队列(死信队列,延迟队列等)和交换机,这样我们在配置类中进行创建,在@Configuration+@Bean的注解驱动下,项目启动时就会自动将组件放到IOC容器进行管理,并且实现了动态绑定(Binding)

步骤⑤ 核心业务中使用RabbitMQ

示例代码中仅是一部分,并不代表整个业务完整流程

生产者发送消息:

    public void closeOrder(OrderEntity entity) {
        //查询当前这个订单的最新状态
        OrderEntity orderEntity = this.getById(entity.getId());

        if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){
            //关单
            OrderEntity update = new OrderEntity();
            update.setId(entity.getId());
            update.setStatus(OrderStatusEnum.CANCLED.getCode());
            this.updateById(update);
            OrderTo orderTo = new OrderTo();
            BeanUtils.copyProperties(orderEntity,orderTo);
            //发给MQ一个
            try{
                //TODO 保证消息一定会发送出去,每一个发送的消息都可以做好日志记录(给数据库保存每一个消息的详细信息)
                //TODO 定期扫描数据库将失败的消息重新发一遍
                rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);
            }catch (Exception e){
                //TODO 将没发送成功的消息进行重试发送

            }

        }
    }

消费者接受消息:

@Component
@RabbitListener(queues = "order.seckill.order.queue")
public class OrderSeckillListener {

    @Autowired
    OrderService orderService;

    @RabbitHandler
    public void listener(SeckillOrderTo seckillOrder, Channel channel, Message msg) throws IOException {
        try{
            orderService.createSeckillOrder(seckillOrder);
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        }catch (Exception e){
            channel.basicReject(msg.getMessageProperties().getDeliveryTag(),true);
        }


    }
}

注意:消费者接收消息的时候,重点是在类上标注@RabbitListener(queues="")注解,代表这个类用来监听哪个队列;在具体的消费方法上标注@RabbitHandler 标明这个方法用来消费指定队列的消息.

此篇文章仅代表个人在项目中的操作理解,并不代表完全正确,如有错误,希望大家指出!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值