• 02springboot整合RabbitMQ


    TOC

    SpringBoot整合RabbitMQ

    参考:https://blog.csdn.net/ztx114/article/details/78410727

    • 引入相关依赖
    • 对application.properties进行配置

    消息发送方

    配置

    pom:RabbitMQ依赖

    amqp依赖即可

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

    application.properties配置application.yml

    • publisher-confirms,实现一个监听器用于监听Broker端给我们返回的确认请求:RabbitTemplate.ConfirmCalback;
    • publisher-returns,保证消息对Broker端是可达的,如果出现路由键不可达的情况,则使用监听器对不可达的消息进行后续的处理,保证消息的路由成功:RabbitTemplate.ReturnCalback;

    注意,在发送消息的时候对template进行配置mandatory=true保证监听有效
    生产端还可以配置其他属性,比如发送重试,超时时间、次数、间隔等

    spring:
      rabbitmq: #配置RabbitMQ
        addresses: 192.168.221.128:5672 #地址
        username: guest
        password: guest
        virtual-host: /  #虚拟机路径,/是默认地址
        publisher-confirms: true
        #connection-timeout:15000 #连接超时时间
        #publisher-returns: true
        template:
          mandatory: true
      http:
        encoding:
          charset:UTF-8
      jackson:
        date-format:yyyy-wMM-dd HH:mm:ss
        time-zone:GMT+8
        default-property-inclusion:NON NULL
    server:
      port: 8001
      servlet:  #端口号
        context-path: /  #项目路径

    config

    @Configuration
    @ComponentScan({"com.bfxy.springboot.*"})//扫描所有的包
    public class MainConfig {}

    发送简单消息

    一对一

    简单消息,队列必须对应,发送者和接收者的queue name必须一致,不然不能接收;
    消息只能被接收一次

    • 队列设置
    @Configuration
    public class RabbitConfig {
    
        @Bean
        public Queue Queue() {
            return new Queue("hello");
        }
    }
    • 发送消息
    @Component
    public class HelloSender {
        @Autowired
        private AmqpTemplate rabbitTemplate;
    
        public void send() {
            String context = "hello " + new Date();
            System.out.println("Sender : " + context);
            this.rabbitTemplate.convertAndSend("hello", context);//发送消息
        }
    }
    
    • 接收消息
    @Component
    @RabbitListener(queues = "hello") //监听队列
    public class HelloReceiver {
    
        @RabbitHandler //接收消息
        public void process(String hello) {
            System.out.println("Receiver  : " + hello);
        }
    }

    一对多发送

    多个接受者,每条消息被其中一个接收(只能接受一次)
    比如,将一条消息发送10遍多个接收方的时候,会轮询的接收

            String context = "hello " + new Date();
            for (int i = 0; i < 10; i++) {
                this.rabbitTemplate.convertAndSend("hello", context+"    ");
            }

    多对多发送

    和一对多一样,接收端仍然会均匀接收到消息

    发送对象Object

    与发送普通消息一样

    • 发送
    this.rabbitTemplate.convertAndSend("object", user);
    • 接收
    //接受者
    @RabbitHandler
    public void process(User user) {
        System.out.println("Receiver object : " + user);
    }

    Topic Exchange,根据routing_key发送

    一个消息可以被设置了routing_key的队列接收,每个队列都可以接收

    • 设置queue,exchange,bingding
    @Configuration
    public class TopicRabbitConfig {
    
        final static String message = "topic.receive1";
        final static String messages = "topic.receive2";
    
        @Bean
        public Queue queueMessage() {//设置queue
            return new Queue(TopicRabbitConfig.message);
        }
    
        @Bean
        public Queue queueMessages() {//设置queue
            return new Queue(TopicRabbitConfig.messages);
        }
    
        @Bean
        TopicExchange exchange() {//设置交换机
            return new TopicExchange("exchange");
        }
    
        @Bean  //交换机设置bingding,设置路由key
        //queueMessage只匹配"topic.message"队列
        Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) {
            return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
        }
    
        @Bean //交换机设置bingding,设置路由key
        //queueMessages同时匹配两个队列(topic.#)
        Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) {
            return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");
        }
    }
    
    • 发送测试
     //发送消息的时候要设置具体的交换和路由key
    public void send1() {
        String context = "hi, i am message 1";
        System.out.println("Sender : " + context);
        this.rabbitTemplate.convertAndSend("exchange", "topic.message", context);//两个队列都可以收到消息
    }
    
    public void send2() {
        String context = "hi, i am messages 2";
        System.out.println("Sender : " + context);
        this.rabbitTemplate.convertAndSend("exchange", "topic.messages", context);//只有queueMessages可以收到消息(topic.#)
    }
    
    • 接收测试
        @RabbitListener(queues = "topic.receive1")
        @RabbitHandler
        public void process1(String hello) {
            System.out.println("Receiver1  : " + hello);
        }
        @RabbitListener(queues = "topic.receive2")
        @RabbitHandler
        public void process2(String hello) {
            System.out.println("Receiver2  : " + hello);
        }

    Fanout Exchange广播模式或者订阅模式

    Fanout 就是我们熟悉的广播模式或者订阅模式,给Fanout交换机发送消息,绑定了这个交换机的所有队列都收到这个消息。

    • 设置队列,绑定交换机
    @Configuration
    public class FanoutRabbitConfig {
         //设置了3个队列
        @Bean
        public Queue AMessage() {
            return new Queue("fanout.A");
        }
    
        @Bean
        public Queue BMessage() {
            return new Queue("fanout.B");
        }
    
        @Bean
        public Queue CMessage() {
            return new Queue("fanout.C");
        }
     //设置了交换机
        @Bean
        FanoutExchange fanoutExchange() {
            return new FanoutExchange("fanoutExchange");
        }
     //绑定(将这三个队列都绑定到这个交换机上)
        @Bean
        Binding bindingExchangeA(Queue AMessage,FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(AMessage).to(fanoutExchange);
        }
    
        @Bean
        Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(BMessage).to(fanoutExchange);
        }
    
        @Bean
        Binding bindingExchangeC(Queue CMessage, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(CMessage).to(fanoutExchange);
        }
    }
    
    • 发送消息
    public void send() {
            String context = "hi, fanout msg ";
            System.out.println("Sender : " + context);
            this.rabbitTemplate.convertAndSend("fanoutExchange","", context);
    }

    接收的时候,三个队列都会受到消息

    发送参数讲解

    • convertAndSend
      • (Object var1):消息体
      • (String var1, Object var2) 队列,消息体
      • (String var1, String var2, Object var3)

    消息实体类

    RabbitMQ发送对象必须序列化:

    package com.bfxy.springboot.entity;
    import java.io.Serializable;
    public class Order implements Serializable {
        private static final long serialVersionUID = 9111357402963030257L;
        private String id;
        private String name;
        private String messageId;//消息id
    }

    发送消息

    import java.util.Map;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;
    import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
    import org.springframework.amqp.rabbit.support.CorrelationData;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.Message;
    import org.springframework.messaging.MessageHeaders;
    import org.springframework.messaging.support.MessageBuilder;
    import org.springframework.stereotype.Component;
    import com.bfxy.springboot.entity.Order;
    
    /**
     * RabbitMQ发送消息
     */
    @Component 
    public class RabbitSender {
    
        //自动注入RabbitTemplate模板类
        @Autowired 
        private RabbitTemplate rabbitTemplate;
    
        //回调函数: confirm确认     
        final ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
            @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            //ack是结果,true成功,false异常
            //cause是异常的时候返回的异常信息
                System.err.println("correlationData: " + correlationData);
                System.err.println("ack: " + ack);
                if (!ack) {//若是异常
                    System.err.println("异常处理....");
                }
                //若是没有异常,一般接下来是修改数据库
            }
        };
    
        //回调函数: return返回  (路由成功,不会调用这个方法)   
        final ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
            //message,重试的错误码,重试提示,交换机,路由键
            @Override public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.err.println("return exchange: " + exchange + ", routingKey: " + routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);
            }
        };
    
        //发送消息方法调用: 构建Message消息
        public void send(Object message, Map < String, Object > properties) throws Exception {
            MessageHeaders mhs = new MessageHeaders(properties);//设置消息自定义参数
            Message msg = MessageBuilder.createMessage(message, mhs);//设置消息
            rabbitTemplate.setConfirmCallback(confirmCallback);//设置回调函数
            rabbitTemplate.setReturnCallback(returnCallback);//设置return回调方法
            //id + 时间戳 全局唯一 
            CorrelationData correlationData = new CorrelationData("1234567890");
            //发送消息,交换机,路由键,消息,消息id
            rabbitTemplate.convertAndSend("exchange-1", "springboot.abc", msg, correlationData);
        }
    
        //发送消息方法调用: 构建自定义对象消息,直接发送entity消息
        public void sendOrder(Order order) throws Exception {
            rabbitTemplate.setConfirmCallback(confirmCallback);
            rabbitTemplate.setReturnCallback(returnCallback);
            //id + 时间戳 全局唯一 
            CorrelationData correlationData = new CorrelationData("0987654321");
            rabbitTemplate.convertAndSend("exchange-2", "springboot.def", order, correlationData);
        }
    
    }

    测试

        @Autowired
        private RabbitSender rabbitSender;
    
        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    
        @Test
        public void testSender1() throws Exception {
             Map<String, Object> properties = new HashMap<>();
             properties.put("number", "12345");
             properties.put("send_time", simpleDateFormat.format(new Date()));
             rabbitSender.send("Hello RabbitMQ For Spring Boot!", properties);
        }
    
        @Test
        public void testSender2() throws Exception {
             Order order = new Order("001", "第一个订单");
             rabbitSender.sendOrder(order);
        }

    消息发送成功,返回确认时,只进入ConfirmCallback
    消息发送失败,返回确认时,先进入ReturnCallback,再进入ConfirmCallback

    接收消息(consumer)

    配置文件

    不建议使用事务

    • 首先配置手工确认模式,用于ACK的手工处理,这样我们可以保证消息的可靠性送达,或者再消费端消费失败的时候可以做到重回队列、根据业务记录日志等处理
    • 可以设置消费端的监听个数和最大个数,用于控制消费端的并发情况
    ## RabbitMQ连接配置(基本配置--必须的)
    spring.rabbitmq.addresses=192.168.0.105:5672
    spring.rabbitmq.username=guest
    spring.rabbitmq.password=guest
    spring.rabbitmq.virtual-host=/
    spring.rabbitmq.connection-timeout=15000
    # RabbitMQ消费配置(接受消息的配置--监听)
    # 基本并发:5
    spring.rabbitmq.listener.simple.concurrency=5
    # 最大并发:10
    spring.rabbitmq.listener.simple.max-concurrency=10
    # 签收模式:manual手动签收--auto自动签收
    spring.rabbitmq.listener.simple.acknowledge-mode=manual
    # 限流策略:同一时间只有1条消息发送过来消费(防止消息太多,内存溢出)
    spring.rabbitmq.listener.simple.prefetch=1
    
    # Server配置
    server.servlet.context-path=/
    server.port=8082
    #若是不用web项目,下方的不用配置
    spring.http.encoding.charset=UTF-8
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    spring.jackson.default-property-inclusion=NON_NULL

    entity/config

    entity最好是与生产端一致的entity(上方的order)
    config:与发送端一致(包不一定)

    @RabbitListener消费端监听

    @Rabbitlistener 是一个组合注解,里面可以注解配置@QueueBinding@Queue@Exchange直接通过这个组合注解一次性搞定消费端交换机、队列、绑定、路由、并且配置监听功能等

    接受消息

    //com.rabbitmq.client.Channel;
    @Component
    public class OrderReceiver {
    
        /**
         * 接收消息
         *使用注解进行监听(绑定消息队列等)
         * @param order   消息体内容
         * @param headers 消息头内容
         * @param channel 网络信道
         * @throws Exception 异常
    
         */
        /*key自懂创建
         @Queue绑定队列,durable持久化
         @Exchange绑定交换机,durable持久化,type = "topic"交换机类型
         key路由key
    ignoreDeclarationExceptions 支持过滤
        */
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(value = "order-queue",durable = "true"),
                exchange = @Exchange(name = "order-exchange",type = "topic",durable="true",ignoreDeclarationExceptions = "true"),
                key = "order.*"
        ))
        @RabbitHandler //接收消息
        //是将message拆开了
        //@Payload消息体,
        //Headers消息信息
        //由于上方设置手工签收,必须设置Channel(是RabbitMQ的那个)
        public void onOrderMessage(@Payload Order order, @Headers Map<String, Object> headers, Channel channel) throws Exception {
            // 消费者操作(收到消息的处理)
            System.out.println("收到消息:");
            System.out.println("订单信息:" + order.toString());
    
            // 手动签收消息
            Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
                //通知RabbitMQ消息已签收
            //deliveryTag从头取
            //是否批量签收
            channel.basicAck(deliveryTag, false);
        }
    }
    
    

    或者

        @RabbitListener(.....)
        @RabbitHandler
        public void onMessage(Message message, Channel channel) throws Exception {
            System.err.println("--------------------------------------");
            System.err.println("消费端Payload: " + message.getPayload());//消息体
            Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);//唯一id
            //手工ACK
            channel.basicAck(deliveryTag, false);
        }

    注解使用application参数

    //yaml
    spring.rabbitmq.listener.order.queue.name=queue-2
    spring.rabbitmq.listener.order.queue.durable=true
    spring.rabbitmq.listener.order.exchange.name=exchange-2
    spring.rabbitmq.listener.order.exchange.durable=true
    spring.rabbitmq.listener.order.exchange.type=topic
    spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true
    spring.rabbitmq.listener.order.key=springboot.*

    使用

    //注解使用
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "${spring.rabbitmq.listener.order.queue.name}", 
            durable="${spring.rabbitmq.listener.order.queue.durable}"),
            exchange = @Exchange(value = "${spring.rabbitmq.listener.order.exchange.name}", 
            durable="${spring.rabbitmq.listener.order.exchange.durable}", 
            type= "${spring.rabbitmq.listener.order.exchange.type}", 
            ignoreDeclarationExceptions = "${spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions}"),
            key = "${spring.rabbitmq.listener.order.key}"
        )
    )

    此时,发送端运行一次发送消息,接收端会直接接收到:

    若是没有设置ACK,消息就不会自动签收,





  • 相关阅读:
    jquery 的 ajax 在 非阻塞 时返回 XMLHttpRequest
    关于TransactionScope出错:“与基础事务管理器的通信失败”的解决方法总结
    未能正确加载“radlangsvc.package,radlangsvc.vs,version=10.0.0,culture=neutra
    跨域iframe高度自适应(兼容IE/FF/OP/Chrome)
    Windows8不联网直接安装.Net 3.5 Framework的方法
    ubuntu创建、删除文件及文件夹,强制清空回收站方法
    Ubuntu java 环境变量
    mysql 和mssql2016中的json字段相关操作
    Windows任务计划
    配置mysql远程访问
  • 原文地址:https://www.cnblogs.com/ziyue7575/p/e7c7760d19558739257fbb795cf2cccf.html
Copyright © 2020-2023  润新知