Spring Boot整合rabbitmq

她的開呀發表於2020-05-10

yls
2020/5/10

Spring Boot整合rabbitmq

rabbitmq的基本概念和其它相關知識請自主去官網學習
rabbitmq官網
本文只介紹rabbitmq在springboot中如何使用

新增依賴包

        <!--rabbitmq客戶端 start-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>2.2.7.RELEASE</version>
        </dependency>
        <!--rabbitmq客戶端 end-->

新增配置檔案 application.yml

spring:
  rabbitmq:        #rabbit連線配置資訊
    host: 39.97.234.52
    port: 5672
    username: admin
    password: admin
    virtual-host:  /vhost_1

rabbitmq五種模式的使用

1. 簡單佇列

  1. 建立消費者
@Component
public class MQ {
    /**
     * 簡單佇列
     * autoDelete = "true"  表示沒有生產者和消費者連線時自動刪除
     * durable = "true"  表示佇列持久化,預設就是 true
     * @param msg
     */
    @RabbitListener(queuesToDeclare = @Queue(value = "simpleQueue",autoDelete = "true", durable = "true"))
    public void simpleQueue(String msg) {
        System.out.println("接收  " + msg);
    }
}
  1. 建立生產者
@SpringBootTest
public class RabbitTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void simpleQueue() {
        rabbitTemplate.convertAndSend("simpleQueue", "this is simpleQueue")
        System.out.println("simple success");
    }
}

2. 工作佇列,實現了能者多勞

  1. 建立消費者
@Component
public class MQ {
    /**
     * 工作佇列,多個消費者消費一個佇列
     * <p>
     * AMQP預設實現消費者確認模式,原文如下
     * It's a common mistake to miss the basicAck and Spring AMQP helps to avoid this through its default configuration.
     * The consequences are serious. Messages will be redelivered when your client quits (which may look like random redelivery),
     * but RabbitMQ will eat more and more memory as it won't be able to release any unacked messages.
     * <p>
     * Fair dispatch vs Round-robin dispatching
     * 官網說: AMQP預設實現消費者fair轉發,也就是能者多勞,原文如下(應該是說反了,預設的是250,但是是Round-robin dispatching)
     * However, "Fair dispatch" is the default configuration for Spring AMQP.
     * The AbstractMessageListenerContainer defines the value for DEFAULT_PREFETCH_COUNT to be 250.
     * If the DEFAULT_PREFETCH_COUNT were set to 1 the behavior would be the round robin delivery as described above.
     */
    //設定消費者的確認機制,並達到能者多勞的效果
    @Bean("workListenerFactory")
    public RabbitListenerContainerFactory myFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory containerFactory =
                new SimpleRabbitListenerContainerFactory();
        containerFactory.setConnectionFactory(connectionFactory);
        //自動ack,沒有異常的情況下自動傳送ack
        //auto  自動確認,預設是auto
        //MANUAL  手動確認
        //none  不確認,發完自動丟棄
        containerFactory.setAcknowledgeMode(AcknowledgeMode.AUTO);
        //拒絕策略,true回到佇列 false丟棄,預設是true
        containerFactory.setDefaultRequeueRejected(true);

        //預設的PrefetchCount是250,採用Round-robin dispatching,效率低
        //setPrefetchCount 為 1,即可啟用fair 轉發
        containerFactory.setPrefetchCount(1);
        return containerFactory;
    }

    /**
     * 若不使用自定義containerFactory = "workListenerFactory",預設的輪詢消費效率低
     *
     * @param s
     */
    @RabbitListener(queuesToDeclare = @Queue("workQueue"), containerFactory = "workListenerFactory")
    public void workQueue1(String s) {
        System.out.println("workQueue 1  " + s);
    }

    @RabbitListener(queuesToDeclare = @Queue("workQueue"), containerFactory = "workListenerFactory")
    public void workQueue2(String s) throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("workQueue 2  " + s);
    }
}
  1. 建立生產者
@SpringBootTest
public class RabbitTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void workQueue() {
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("workQueue", i);
        }
        System.out.println("workQueue success");
    }
}

3. 訂閱模式

  1. 建立消費者
@Component
public class MQ {

    /**
     * 訂閱模式  fanout
     */
    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue,  //臨時路由
                    exchange = @Exchange(value = "exchange1", type = ExchangeTypes.FANOUT))
    })
    public void fanout(String s) {
        System.out.println("訂閱模式1    " + s);
    }

    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue, exchange = @Exchange(value = "exchange1", type = ExchangeTypes.FANOUT))
    })
    public void fanout2(String s) {
        System.out.println("訂閱模式2    " + s);
    }
}
  1. 建立生產者
@SpringBootTest
public class RabbitTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void fanOut() {
        rabbitTemplate.convertAndSend("exchange1", "", "fan out......");
    }
}

4. 路由模式

  1. 建立消費者
@Component
public class MQ {
        /**
         * 路由模式  DIRECT
         */
        @RabbitListener(bindings = {
                @QueueBinding(value = @Queue,  //臨時路由
                        exchange = @Exchange(value = "exchange2", type = ExchangeTypes.DIRECT),
                        key = {"error", "info"}  //路由鍵
                )
        })
        public void router(String s) {
            System.out.println("路由模式1    " + s);
        }
    
        @RabbitListener(bindings = {
                @QueueBinding(value = @Queue,
                        exchange = @Exchange(value = "exchange2", type = ExchangeTypes.DIRECT),
                        key = {"error"}  //路由鍵
                )
        })
        public void router2(String s) {
            System.out.println("路由模式2    " + s);
        }
}
  1. 建立生產者
@SpringBootTest
public class RabbitTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void router() {
        rabbitTemplate.convertAndSend("exchange2", "info", "router");
        System.out.println("router");
    }
}

5. 主題模式

  1. 建立消費者
@Component
public class MQ {
            /**
             * topic  topics
             */
            @RabbitListener(bindings = {
                    @QueueBinding(value = @Queue,
                            exchange = @Exchange(value = "exchange3", type = ExchangeTypes.TOPIC),
                            key = {"user.#"}  //路由鍵
                    )
            })
            public void topic2(String s) {
                System.out.println("topic2    " + s);
            }
}
  1. 建立生產者
@SpringBootTest
public class RabbitTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void topic() {
        rabbitTemplate.convertAndSend("exchange3", "user.name", "hhh");
        System.out.println("topic");
    }
}

預設訊息是持久化的,也可以設定不持久化,以簡單佇列示例

@SpringBootTest
public class RabbitTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * AMQP 預設訊息是持久化的,但只有在佇列也是持久化時才有作用,原文如下:
     * Messages are persistent by default with Spring AMQP.
     * Note the queue the message will end up in needs to be durable as well,
     * otherwise the message will not survive a broker restart as a non-durable queue does not itself survive a restart.
     * <p>
     * MessageProperties類中原始碼如下:
     * static {
     * DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT;
     * DEFAULT_PRIORITY = 0;
     * }
     * <p>
     * 如何設定訊息不持久化?
     * 設定訊息不持久化,預設是持久化的,這裡只為記錄如何設定訊息不持久化,一般不設定
     * 傳送訊息時,新增 MessagePostProcessor即可,這裡使用 lambda 表示式
     * (message) -> {
     * message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
     * return message;
     * }
     * <p>
     * 完整示例如下:
     * rabbitTemplate.convertAndSend("simpleQueue", "this is simpleQueue",
     * (message) -> {
     * message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
     * return message;
     * });
     */
    @Test
    public void simpleQueue() {
        rabbitTemplate.convertAndSend("simpleQueue", "this is simpleQueue",
                (message) -> {
                    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
                    return message;
                });
        System.out.println("simple success");
    }
}

如何設定生產者訊息確認,避免訊息傳送失敗而丟失(確認分為兩步,一是確認是否到達交換器,二是確認是否到達佇列。)

參考資料:https://www.cnblogs.com/wangiqngpei557/p/9381478.html

  1. 在配置檔案中新增
spring:
  rabbitmq:        #rabbit連線配置資訊
    publisher-returns: true             #開啟訊息從 交換機----》佇列傳送失敗的回撥
    publisher-confirm-type: correlated  #開啟訊息從 生產者----》交換機的回撥
  1. 新增配置類
@Component
public class ProducerConfig {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void pre() {
        /**
         *
         *  訊息傳送到交換機的回撥
         *
         *   public void confirm(CorrelationData correlationData, boolean b, String s) {
         *
         * 		System.out.println("訊息唯一標識:"+correlationData);
         * 		System.out.println("確認結果:"+ b);
         * 		System.out.println("失敗原因:"+ s);
         *    }
         *
         */
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            System.out.println("setConfirmCallback-------------------");
            System.out.println("correlationData:  " + correlationData);
            System.out.println(ack);
            System.out.println(cause);
            if (ack) {
                System.out.println("傳送成功");
            } else {
                System.out.println("傳送失敗");
                // 可以記錄下來,也可以重新傳送訊息。。。
            }

        });

        /**
         *
         * 訊息從交換機傳送到佇列的回撥,只有傳送失敗時才會回撥
         *  public void returnedMessage(Message message, int i, String s, String s1, String s2) {
         * 		System.out.println("訊息主體 message : "+message);
         * 		System.out.println("訊息主體 message : "+ i);
         * 		System.out.println("描述:"+ s);
         * 		System.out.println("訊息使用的交換器 exchange : "+ s1);
         * 		System.out.println("訊息使用的路由鍵 routing : "+ s2);
         *    }
         */
        rabbitTemplate.setReturnCallback((Message message, int replyCode, String replyText, String exchange, String routingKey) -> {
            System.out.println("setReturnCallback---------------------");
            System.out.println("訊息主體 message : " + message);
            System.out.println("響應碼 replyCode: " + replyCode);
            System.out.println("響應內容 replyText:" + replyText);
            System.out.println("訊息使用的交換器 exchange : " + exchange);
            System.out.println("訊息使用的路由鍵 routeKey : " + routingKey);

            //也可以重新傳送訊息
            rabbitTemplate.convertAndSend(exchange, routingKey, new String(message.getBody()));
            System.out.println("重新傳送訊息: -----" + new String(message.getBody()));
        });

        /**
         * 網上都說必須設定rabbitTemplate.setMandatory(true),才能觸發ReturnCallback回撥,
         * 我嘗試了一下,並不需要設定為true,交換機傳送訊息給佇列失敗時,也能觸發回撥
         */
        //rabbitTemplate.setMandatory(true);
    }
}

程式碼github地址:https://github.com/1612480331/Spring-Boot-rabbitmq

相關文章