RabbitMQ學習筆記:springboot2 amqp集成生產者消費者

1.引入依賴
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

配置application.properties文件:

spring.rabbitmq.virtual-host=/
#spring.rabbitmq.host=localhost
spring.rabbitmq.addresses=172.30.67.122:5672,172.30.67.122:5673,172.30.67.122:5674
#spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin

#producer
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.template.mandatory=true

#consumer
##手工確認消費者消費的消息
spring.rabbitmq.listener.simple.acknowledge-mode=manual
##設置Qos,即RabbitMQ服務器每次推送給消費者未ack消息的個數
spring.rabbitmq.listener.simple.prefetch=1
2.定義隊列、交換器、路由之間的綁定關係
package com.yaomy.control.rabbitmq.amqp.config;

import com.google.common.collect.Maps;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
 * @Description: RabbitMQ生產者交換器、綁定、隊列聲明
 * @Version: 1.0
 */
@SuppressWarnings("all")
@Configuration
public class RabbitConfig {
    /**
     * 聲明隊列
     */
    @Bean
    public Queue topicQueue(){
        Map<String, Object> args = Maps.newHashMap();
        /**
         * 設置消息發送到隊列之後多久被丟棄,單位:毫秒
         */
        //args.put("x-message-ttl", 60000);
        /**
         * 定義優先級隊列,消息最大優先級爲15,優先級範圍爲0-15,數字越大優先級越高
         */
        args.put("x-max-priority", 15);
        /**
         * 設置持久化隊列
         */
        return QueueBuilder.durable("test_queue2").withArguments(args).build();
    }

    /**
     * 聲明Topic類型交換器
     */
    @Bean
    public TopicExchange topicExchange(){
        TopicExchange exchange = new TopicExchange("test_exchange2");
        return exchange;
    }

    /**
     * Topic交換器和隊列通過bindingKey綁定
     * @return
     */
    @Bean
    public Binding bindingTopicExchangeQueue(){
        return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("*.topic.*");
    }
}

3.定義生產者
package com.yaomy.control.rabbitmq.amqp;

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.connection.PublisherCallbackChannel;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * @Description: RabbitMQ生產者
 * @ProjectName: spring-parent
 * @Version: 1.0
 */
@SuppressWarnings("all")
@Component
public class RabbitSender {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 創建一個消息是否投遞成功的回調方法
     */
    private final RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
        /**
         *
         * @param correlationData 消息的附加信息
         * @param ack true for ack, false for nack
         * @param cause 是一個可選的原因,對於nack,如果可用,否則爲空。
         */
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if(!ack){
                //可以進行日誌記錄、異常處理、補償處理等
                System.err.println("異常ack-"+ack+",id-"+correlationData.getId()+",cause:"+cause);
            }else {
                //更新數據庫,可靠性投遞機制
                System.out.println("正常ack-"+ack+",id-"+correlationData.getId());
                try{
                System.out.println(new String(correlationData.getReturnedMessage().getBody()));

                } catch (Exception e){

                }
            }
        }
    };
    /**
     * 創建一個消息是否被隊列接收的監聽對象,如果沒有隊列接收發送出的消息,則調用此方法進行後續處理
     */
    private final RabbitTemplate.ReturnCallback returnCallback = 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.err.println("spring_returned_message_correlation:"+message.getMessageProperties().getHeaders().get(PublisherCallbackChannel.RETURNED_MESSAGE_CORRELATION_KEY)
                                +"return exchange: " + exchange
                                + ", routingKey: "+ routingKey
                                + ", replyCode: " + replyCode
                                + ", replyText: " + replyText
                                + ",message:" + message);
            try {
                System.out.println(new String(message.getBody()));
            } catch (Exception e){

            }
        }
    };

    /**
     * 發送消息
     * @param exchange 交換器
     * @param route 路由鍵
     * @param message 消息
     * @param properties
     */
    public void sendMsg(String exchange, String routingKey, String message, MessageProperties properties){
        try {
            if(null == properties){
                properties = new MessageProperties();
            }
            /**
             * 設置消息唯一標識
             */
            properties.setMessageId(UUID.randomUUID().toString());
            /**
             * 創建消息包裝對象
             */
            Message msg = MessageBuilder.withBody(message.getBytes()).andProperties(properties).build();
            /**
             * 設置生產者消息publish-confirm回調函數
             */
            this.rabbitTemplate.setConfirmCallback(confirmCallback);
            /**
             * 設置消息退回回調函數
             */
            this.rabbitTemplate.setReturnCallback(returnCallback);
            /**
             * 將消息主題和屬性封裝在Message類中
             */
            Message returnedMessage = MessageBuilder.withBody(message.getBytes()).build();
            /**
             * 相關數據
             */
            CorrelationData correlationData = new CorrelationData();
            /**
             * 消息ID,全局唯一
             */
            correlationData.setId(msg.getMessageProperties().getMessageId());

            /**
             * 設置此相關數據的返回消息
             */
            correlationData.setReturnedMessage(returnedMessage);
            /**
             * 如果msg是org.springframework.amqp.core.Message對象的實例,則直接返回,否則轉化爲Message對象
             */
            this.rabbitTemplate.convertAndSend(exchange, routingKey, msg, new MessagePostProcessor() {
                /**
                 * 消息後置處理器,消息在轉換成Message對象之後調用,可以用來修改消息中的屬性、header
                 */
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    MessageProperties msgProperties = message.getMessageProperties();
                    /**
                     * 設置消息發送到隊列之後多久被丟棄,單位:毫秒
                     * 此種方案需要每條消息都設置此屬性,比較靈活;
                     * 還有一種方案是在聲明隊列的時候指定發送到隊列中的過期時間;
                     * * Queue queue = new Queue("test_queue2");
                     * * queue.getArguments().put("x-message-ttl", 10000);
                     * 這兩種方案可以同時存在,以值小的爲準
                     */
                    //msgProperties.setExpiration("10000");
                    /**
                     * 設置消息的優先級
                     */
                    msgProperties.setPriority(9);
                    /**
                     * 設置消息發送到隊列中的模式,持久化|非持久化(只存在於內存中)
                     */
                    msgProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    return message;
                }
            }, correlationData);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

4.消費者

@RabbitListener註解標註指定的方法監聽指定的隊列、也可以標註在類上結合@RabbitHandler使用;監聽方法可以使用多種參數接收消息,查看源碼可以看到是允許六種參數:

* Annotated methods are allowed to have flexible signatures similar to what
 * {@link MessageMapping} provides, that is
 * <ul>
 * <li>{@link com.rabbitmq.client.Channel} to get access to the Channel</li>
 * <li>{@link org.springframework.amqp.core.Message} or one if subclass to get access to
 * the raw AMQP message</li>
 * <li>{@link org.springframework.messaging.Message} to use the messaging abstraction
 * counterpart</li>
 * <li>{@link org.springframework.messaging.handler.annotation.Payload @Payload}-annotated
 * method arguments including the support of validation</li>
 * <li>{@link org.springframework.messaging.handler.annotation.Header @Header}-annotated
 * method arguments to extract a specific header value, including standard AMQP headers
 * defined by {@link org.springframework.amqp.support.AmqpHeaders AmqpHeaders}</li>
 * <li>{@link org.springframework.messaging.handler.annotation.Headers @Headers}-annotated
 * argument that must also be assignable to {@link java.util.Map} for getting access to
 * all headers.</li>
 * <li>{@link org.springframework.messaging.MessageHeaders MessageHeaders} arguments for
 * getting access to all headers.</li>

示例代碼:

package com.yaomy.control.rabbitmq.amqp;

import com.rabbitmq.client.Channel;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Description: RabbitMQ消息消費者
 * @Version: 1.0
 */
@SuppressWarnings("all")
@Component
public class RabbitReceiver {
    /**
     *
     * @param channel 信道
     * @param message 消息
     * @throws Exception
     */
    @RabbitListener(queues = "test_queue2")
    public void onMessage(Channel channel, Message message) throws Exception {
        System.out.println("--------------------------------------");
        System.out.println("消費端Payload: " + message.getPayload()+"-ID:"+message.getHeaders().getId()+"-messageId:"+message.getHeaders());
        Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
        //手工ACK,獲取deliveryTag
        channel.basicAck(deliveryTag, false);
    }

    /**
     *
     * @param channel 信道
     * @param message 消息
     * @throws Exception
     */
    @RabbitListener(queues = "test_queue2")
    public void onMessage(Channel channel, org.springframework.amqp.core.Message message) throws Exception {
        System.out.println("--------------------------------------");
        System.out.println("消費端Payload: " + new String(message.getBody())+"-messageId:"+message.getMessageProperties().getMessageId());
        message.getMessageProperties().getHeaders().forEach((key, value)->{
            System.out.println("header=>>"+key+"="+value);
        });
        Long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //手工ACK,獲取deliveryTag
        channel.basicAck(deliveryTag, false);
    }

    /**
     *
     * @param channel 信道
     * @param body 負載
     * @param amqp_messageId 消息唯一標識
     * @param headers 消息header
     * @throws Exception
     */
    //獲取特定的消息
    @RabbitListener(queues = "test_queue2")
    //@RabbitHandler
    public void handleMessage(Channel channel, @Payload byte[] body, @Header String amqp_messageId,  @Headers Map<String, Object> headers) throws Exception{
        System.out.println("====消費消息===amqp_messageId:"+amqp_messageId);
        headers.keySet().forEach((key)->{
            System.out.println("header=>>"+key+"="+headers.get(key));
        });
        System.out.println(new String(body));
        Long deliveryTag = NumberUtils.toLong(headers.get("amqp_deliveryTag").toString());
        /**
         * 手動Ack
         */
        channel.basicAck(deliveryTag, false);
    }

    /**
     *
     * @param channel 信道
     * @param body 負載
     * @param headers 消息header
     * @throws Exception
     */
    @RabbitListener(queues = "test_queue2")
    //@RabbitHandler
    public void handleMessage(Channel channel, @Payload byte[] body, MessageHeaders headers) throws Exception{
        System.out.println("====消費消息===amqp_messageId:"+headers);
        headers.keySet().forEach((key)->{
            System.out.println("header=>>"+key+"="+headers.get(key));
        });
        System.out.println(new String(body));
        Long deliveryTag = NumberUtils.toLong(headers.get("amqp_deliveryTag").toString());
        /**
         * 手動Ack
         */
        channel.basicAck(deliveryTag, false);
    }
}

使用MessageHeaders參數類型接收header示例如下:

header=>>amqp_receivedDeliveryMode=PERSISTENT
header=>>amqp_receivedExchange=test_exchange2
header=>>amqp_deliveryTag=1
header=>>amqp_consumerQueue=test_queue2
header=>>amqp_redelivered=false
header=>>priority=9
header=>>amqp_receivedRoutingKey=test.topic.key
header=>>number=12345
header=>>spring_listener_return_correlation=34b20ba3-9fb0-49e5-b57e-5484e773e9b3
header=>>send_time=2019-12-27 16:08:55
header=>>spring_returned_message_correlation=cba66847-5c31-4ecb-bf97-07ba65238b9a
header=>>amqp_messageId=cba66847-5c31-4ecb-bf97-07ba65238b9a
header=>>id=b58e6adb-e5b2-9513-45c5-8376311cf82f
header=>>amqp_consumerTag=amq.ctag-hPKHjdTdj0NY4awa4bFkAA
header=>>contentType=application/octet-stream
header=>>timestamp=1577434135312

其headers中有一個id屬性,這個屬性是消費者接收消息new MessageHeaders的時候生成的隨機UUID,不可以作爲整個系統中消息的唯一標識,只可以作爲消費端的唯一標識。

GitHub地址:https://github.com/mingyang66/spring-parent/tree/master/spring-boot-control-rabbitmq-service

發佈了475 篇原創文章 · 獲贊 243 · 訪問量 146萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章