主頁 > 後端開發 > SpringCloud(六) - RabbitMQ安裝,三種訊息發送模式,訊息發送確認,訊息消費確認(自動,手動)

SpringCloud(六) - RabbitMQ安裝,三種訊息發送模式,訊息發送確認,訊息消費確認(自動,手動)

2022-11-05 06:49:34 後端開發

1、安裝erlang語言環境

1.1 創建 erlang安裝目錄

mkdir erlang

1.2 上傳解壓壓縮包

上傳到: /root/

解壓縮# tar -zxvf otp_src_22.0.tar.gz

1.3 進入解壓縮目錄,指定目錄并安裝

進入解壓目錄,指定安裝目錄# ./configure --prefix=/usr/local/kh96/erlang

安裝# make install

添加環境變數# echo 'export PATH=$PATH:/usr/local/kh96/erlang/bin' >> /etc/profile

重繪環境變數# source /etc/profile

1.4 測驗環境

進入erlang環境#erl

退出# halt().

2、安裝RabbitMQ

2.1上傳解壓壓縮包

第一步xx.tar.xz->xx.tar # /bin/xz -d rabbitmq-server-generic-unix-3.7.15.tar.xz

第二步#tar -xvf rabbitmq-server-generic-unix-3.7.15.tar

2.2 添加環境變數

添加環境變數# echo 'export PATH=$PATH:/usr/local/kh96/rabbitmq/rabbitmq_server-3.7.15/sbin' >> /etc/profile

重繪環境變數# source /etc/profile

2.3 啟動

啟動#  rabbitmq-server -detached

查看狀態# rabbitmqctl status

查看防火墻狀態# firewall-cmd --state (建議不開)

2.4 開啟云服務埠

RabbitMQ 服務埠: 5672

RabbitMQ 監控平臺埠: 15672

開啟web插件允許監控平臺訪問 # rabbitmq-plugins enable rabbitmq_management

2.5 遠程 訪問 15672

公網ip:15672

Username: guest

Password: guest

提示這個這個賬號只允許本地訪問,所以需要添加用戶

2.6 添加用戶

顯示所有用戶# rabbitmqctl list_users

查看guest用戶權限# rabbitmqctl list_user_permissions guest

添加admin用戶及密碼# rabbitmqctl add_user admin admin

設定限權# rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

授予admin用戶administrator角色# rabbitmqctl set_user_tags admin administrator

查看admin用戶權限# rabbitmqctl list_user_permissions admin

洗掉用戶guest# rabbitmqctl delete_user guest

停止RabbitMQ# rabbitmqctl stop

2.7 登錄成功

Username: admin

Password: admin

3、SpringBoot整合

3.0 專案準備

3.0.1 jar包

<!--rabbitmq依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

3.0.2 配置資訊

# 埠
server:
  port: 8104

# RabbitMQ配置
spring:
  rabbitmq:
    host: x.xxx.xx.xx #服務器公網ip
    port: 5672
    username: admin
    password: admin

3.0.3 常量類

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQ 常量類,系統的所有佇列名,交換機名,路由鍵名等,統一進行配置管理
 */
public class RabbitMQConstant {

    //========================== 直連模式
    /**
     * Direct直連模式 佇列名
     */
    public static final String RABBITMQ_DIRECT_QUEUE_NAME_KH96 ="rabbitmq_direct_queue_name_kh96";

    /**
     * Direct直連模式 交換機名
     */
    public static final String RABBITMQ_DIRECT_EXCHANGE_KH96 ="rabbitmq_direct_exchange_kh96";

    /**
     * Direct直連模式 路由鍵
     */
    public static final String RABBITMQ_DIRECT_ROUTING_KEY_KH96 ="rabbitmq_direct_routing_key_kh96";

    //========================== 扇形模式

    /**
     * Fanout 扇形模式 佇列名one
     */
    public static final String RABBITMQ_FANOUT_QUEUE_NAME_KH96_ONE ="rabbitmq_fanout_queue_name_kh96_one";

    /**
     * Fanout 扇形模式 佇列名two
     */
    public static final String RABBITMQ_FANOUT_QUEUE_NAME_KH96_TWO ="rabbitmq_fanout_queue_name_kh96_two";

    /**
     * Fanout 扇形模式 交換機名
     */
    public static final String RABBITMQ_FANOUT_EXCHANGE_KH96 ="rabbitmq_fanout_exchange_kh96";


    //========================== 主題模式
    // -- 佇列
    /**
     * Topic 主題模式 佇列名one
     */
    public static final String RABBITMQ_TOPIC_QUEUE_NAME_KH96_ONE ="rabbitmq_topic_queue_name_kh96_one";
    /**
     * Topic 主題模式 佇列名two
     */
    public static final String RABBITMQ_TOPIC_QUEUE_NAME_KH96_TWO ="rabbitmq_topic_queue_name_kh96_two";
    /**
     * Topic 主題模式 佇列名Three
     */
    public static final String RABBITMQ_TOPIC_QUEUE_NAME_KH96_THREE ="rabbitmq_topic_queue_name_kh96_three";

    //-- 交換機
    /**
     * Topic 主題模式 交換機名
     */
    public static final String RABBITMQ_TOPIC_EXCHANGE_KH96 ="rabbitmq_topic_exchange_kh96";


    //-- 路由鍵
    /**
     * Topic 主題模式 -路由鍵-唯一匹配規則
     */
    public static final String RABBITMQ_TOPIC_ROUTING_KEY_KH96_ONLY="rabbitmq_topic_routing_key_kh96.only";

    /**
     * Topic 主題模式 -路由鍵-單詞匹配規則  * 單個詞
     */
    public static final String RABBITMQ_TOPIC_ROUTING_KEY_KH96_WORLD="rabbitmq_topic_routing_key_kh96.*";

    /**
     * Topic 主題模式 -路由鍵-模糊匹配規則 # 0 或 多個詞
     */
    public static final String RABBITMQ_TOPIC_ROUTING_KEY_KH96_LIKE="rabbitmq_topic_routing_key_kh96.#";

}

3.0.4 手動操作佇列關系

在測驗的時候,一定要注意交換機和佇列的系結關系,只要系結過的關系就會一直存在需要手動洗掉;如果測驗結果不正常的時候,看一些交換機和佇列與鍵值的系結關系;

選擇佇列:

洗掉佇列:

3.1 Direct 直連模式

3.1.0 核心構造方法:Queue

  • 核心構造方法:Queue(String name, boolean durable, boolean exclusive, boolean autoDelete)
  1. name引數:name – the name of the queue.
    • 指定創建的訊息佇列的名字,引數必傳,即創建佇列必須要有佇列名,
  2. durable引數:durable – true if we are declaring a durable queue (the queue will survive a server restart)
    • 指定創建的訊息佇列是否需要持久化,默認是true,如果是true,該佇列支持持久化,自動持久化到磁盤,RabbitMQ服務重啟,佇列仍然是可用的(存活的),
  3. exclusive引數:true if we are declaring an exclusive queue (the queue will only be used by the declarer's connection)
    • 指定創建的訊息佇列是否是排他佇列,默認是false,如果是true,該佇列是排他佇列,只有創建當前佇列的連接才可以使用,連接一旦斷開,佇列會自動洗掉,
  4. autoDelete引數:true if the server should delete the queue when it is no longer in use
    • 指定創建的訊息佇列是否是自動洗掉佇列,默認是false,如果是true,該佇列是自動洗掉佇列,一旦沒有訊息生產者或者消費者使用當前佇列,會被自動洗掉,

3.1.1 配置類

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: Direct直連模式,自動配置類,自動創建佇列,交換機,并將佇列系結到交換機,指定唯一路由
 */
@Configuration
public class RabbitMQDirectConfig {

    //創建 直連佇列
    @Bean
    public Queue directQueue(){
        //創建 直連佇列
        return new Queue(RabbitMQConstant.RABBITMQ_DIRECT_QUEUE_NAME_KH96,true);
    }

    //創建 直連交換機
    @Bean
    public DirectExchange directExchange(){
        // 創建支持持久化的直連交換機,指定交換機的名稱
        return new DirectExchange(RabbitMQConstant.RABBITMQ_DIRECT_EXCHANGE_KH96);
    }

   	//將直連佇列和直連交換機 進行系結,并指定系結的唯一路由鍵
    @Bean
    public Binding directBinding(){
        // 將直連佇列和直連交換機進行系結,并指定系結的唯一路由鍵
        return BindingBuilder.bind(directQueue())
                            .to(directExchange())
                            .with(RabbitMQConstant.RABBITMQ_DIRECT_ROUTING_KEY_KH96);
    }


}

3.1.2 訊息生產者

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: Direct 直連模式 訊息生產者
 */
@Slf4j
@Component
public class RabbitMQDirectProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @author : huayu
     * @date   : 1/11/2022
     * @param  : [directMsg, directExchange, directRoutingKey]
     * @return : void
     * @description : 使用直連模式,發送訊息到直連交換機,通過交換機系結的唯一路由鍵,將訊息發送到系結的佇列中
     */
    public void sendDirectMsg2DirectExchange(String directExchange,String directRoutingKey,String directMsg){
        log.info("++++++  direct模式訊息生產者,發送直連訊息:{},到交換機:{},路由鍵:{} ++++++",directMsg,directExchange,directRoutingKey);

        rabbitTemplate.convertAndSend(directExchange,directRoutingKey,directMsg);

    }

}

3.1.3 消費者

3.1.3.1 消費者One
/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: Direct 直連模式消費者 One
 */
@Slf4j
@Component
//指定接聽的 訊息佇列 名字
@RabbitListener(queues = RabbitMQConstant.RABBITMQ_DIRECT_QUEUE_NAME_KH96)
public class RabbitMQDirectConsumerOne {

    /**
     * @author : huayu
     * @date   : 1/11/2022
     * @param  : [directMsgJson]
     * @return : void
     * @description : Direct 直連模式消費者One,消費資訊
     */
    //指定訊息佇列中的訊息,交給對應的方法處理
    @RabbitHandler
    public void consumeOneDirectMsgFromDirectQueue(String directMsgJson){
        log.info("***** Direct直連模式,消費者One,消費訊息:{} ******",directMsgJson);

        // TODO 核心業務邏輯處理

    }


//    @RabbitHandler  //自動根據佇列中的訊息型別,自動區分方法
//    public void consumeOtherDirectMsgFromDirectQueue(List<String> directMsgJson){
//        log.info("***** Direct直連模式,消費者Two,消費訊息:{} ******",directMsgJson);
//
//        // TODO 核心業務邏輯處理
//
//    }


}
3.1.3.2 消費者Two
/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQDirectConsumerTwo
 */
@Slf4j
@Component
//指定監聽的訊息佇列 名字
@RabbitListener(queues = RabbitMQConstant.RABBITMQ_DIRECT_QUEUE_NAME_KH96)
public class RabbitMQDirectConsumerTwo {
    /**
     * @author : huayu
     * @date   : 1/11/2022
     * @param  : [directMsgJson]
     * @return : void
     * @description : Direct 直連模式消費者 Two,消費資訊
     */
    //指定訊息佇列中的訊息,交給對應的方法處理
    @RabbitHandler
    public void consumeOneDirectMsgFromDirectQueue(String directMsgJson){
        log.info("***** Direct直連模式,消費者Two,消費訊息:{} ******",directMsgJson);

        // TODO 核心業務邏輯處理

    }

}

3.1.4 請求測驗方法

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 測驗 RabbitMQ 訊息佇列的操作入口
 */
@Slf4j
@RestController
public class RabbitMQController {

    @Autowired
    private RabbitMQDirectProducer rabbitMQDirectProducer;
    

    /**
     * @author : Administrator
     * @date   : 2022/11/1
     * @param  : [directMsg]
     * @return : com.kgc.sct.util.RequestResult<java.lang.String>
     * @description : 測驗direct直連模式,發送和消費訊息
     */
    @GetMapping("/direct")
    public RequestResult<String> testRabbitMQDirect(@RequestParam String directMsg){
        log.info("direct直連模式,發送訊息");
        //模擬發送5條直連訊息
        Stream.of(11,22,33,44,55).forEach(directNo ->{
            //模擬創建訊息物件
            Map<String,Object> directMap =new HashMap<>();
            directMap.put("directNo",directNo);
            directMap.put("directData",directMsg);
            directMap.put("directTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        //呼叫直連模式訊息生產者,發送訊息
            rabbitMQDirectProducer.sendDirectMsg2DirectExchange(RabbitMQConstant.RABBITMQ_DIRECT_EXCHANGE_KH96
                                                         ,RabbitMQConstant.RABBITMQ_DIRECT_ROUTING_KEY_KH96
                                                         ,JSON.toJSONString(directMap));

        return ResultBuildUtil.success("使用直連模式,發送訊息成功");
    }

}

3.1.5 請求測驗

發起請求

3.1.5.1 一個消費者

消費者One消費了佇列中的所有資訊(只有一個佇列);

3.1.5.2 兩個消費者

消費者One和消費者Two依次消費了佇列中的所有資訊(只有一個佇列);

3.2 Fanout 扇形模式

3.2.1 配置類

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: Fanout扇形模式,自動配置類,自動創建佇列,交換機,并將佇列系結到交換機
 */
@Configuration
public class RabbitMQFanoutConfig {

    //創建 扇形佇列One
    @Bean
    public Queue fanoutQueueOne(){
        return new Queue(RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_ONE);
    }

    //創建 扇形佇列Two
    @Bean
    public Queue fanoutQueueTwo(){
        return new Queue(RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_TWO);
    }

    // 創建扇形交換機
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange(RabbitMQConstant.RABBITMQ_FANOUT_EXCHANGE_KH96);

    }

    //系結佇列到扇形交換機,不需要 指定 路由鍵
    @Bean
    public Binding fanoutBindingQueueOne(){
        //系結佇列到扇形交換機,不需要路由鍵,訊息是廣播發送,會給多有系結的佇列群發資訊訊息(根本沒有提供with方法)
        return BindingBuilder.bind(fanoutQueueOne())
                            .to(fanoutExchange());
    }

    @Bean
    public Binding fanoutBindingQueueTwo(){
        //系結佇列到扇形交換機,不需要路由鍵,訊息是廣播發送,會給多有系結的佇列群發資訊訊息(根本沒有提供with方法)
        return BindingBuilder.bind(fanoutQueueTwo())
                            .to(fanoutExchange());
    }

}

3.2.2 訊息生產者

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQFanoutProducer
 */
@Slf4j
@Component
public class RabbitMQFanoutProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @author : huayu
     * @date   : 1/11/2022
     * @param  : [fanoutExchange, fanoutRoutingKey, fanoutMsg]
     * @return : void
     * @description : 使用扇形模式,發送訊息到扇形交換機,將訊息發送到系結的佇列中
     */
    public void sendFanoutMsg2FanoutExchange(String fanoutExchange,String fanoutRoutingKey,String fanoutMsg){
        log.info("++++++ Fanout模式訊息生產者,發送廣播訊息:{},到交換機:{},路由鍵:{} ++++++", fanoutMsg, fanoutExchange, fanoutRoutingKey);
        rabbitTemplate.convertAndSend(fanoutExchange, fanoutRoutingKey, fanoutMsg);
    }

}

3.2.3 消費者

3.2.3.1 消費者One
/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQFanoutConsumerOne
 */
@Slf4j
@Component
@RabbitListener(queues = RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_ONE)
public class RabbitMQFanoutConsumerOne {

    @RabbitHandler
    public void fanoutConsumeOneFanoutMsgFromFanoutQueueOne(String fanoutMsgJson){
        log.info("****** Fanout扇形模式,消費One,消費佇列One,訊息:{}  ******",fanoutMsgJson);

        // TODO 核心業務邏輯處理

    }

}
3.2.3.2 消費者Two
/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQFanoutConsumerTwo
 */
@Slf4j
@Component
//@RabbitListener(queues = RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_TWO)
public class RabbitMQFanoutConsumerTwo {

//    @RabbitHandler
    public void fanoutConsumeTwoFanoutMsgFromFanoutQueueTwo(String fanoutMsgJson){
        log.info("****** Fanout扇形模式,消費Two,消費佇列Two,訊息:{}  ******",fanoutMsgJson);

        // TODO 核心業務邏輯處理

    }

}

3.2.4 請求測驗方法

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 測驗 RabbitMQ 訊息佇列的操作入口
 */
@Slf4j
@RestController
public class RabbitMQController {

    @Autowired
    private RabbitMQFanoutProducer rabbitMQFanoutProducer;
    
 	/**
     * @author : huayu
     * @date   : 1/11/2022
     * @param  : [fanoutMsg]
     * @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
     * @description : 測驗扇形(廣播)模式,發送和消費資訊
     */
    @GetMapping("/fanout")
    public RequestResult<String> testRabbitMQFanout(@RequestParam String fanoutMsg){
        log.info("------- fanout 扇形模式,發送訊息 -------");
        //模擬發送5條直連訊息
        Stream.of(66,77,88,99,96).forEach(directNo ->{
            //模擬創建訊息物件
            Map<String,Object> fanoutMap =new HashMap<>();
            fanoutMap.put("directNo",directNo);
            fanoutMap.put("directData",fanoutMsg);
            fanoutMap.put("directTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

            //呼叫扇形模式訊息生產者,發送訊息
            rabbitMQFanoutProducer.sendFanoutMsg2FanoutExchange(RabbitMQConstant.RABBITMQ_FANOUT_EXCHANGE_KH96
                                                                ,null
                                                                ,JSON.toJSONString(fanoutMap));

        });

        return ResultBuildUtil.success("使用扇形模式,發送訊息成功");
        
    }

}     

3.2.5 請求測驗

3.2.5.1 一個消費者

消費者One消費了佇列One中的所有資訊;

3.2.5.2 兩個消費者

消費者One消費了佇列One中的所有資訊;

消費者Two消費了佇列Two中的所有資訊;

3.3 Topic 主題模式

3.3.1 配置類

/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: Topic 主題模式,自動配置類
 */
@Configuration
public class RabbitMQTopicConfig {

    //======== 佇列
    //Topic 主題模式 佇列One
    @Bean
    public Queue topicQueueOne(){
        return new Queue(RabbitMQConstant.RABBITMQ_TOPIC_QUEUE_NAME_KH96_ONE,true);
    }

    //Topic 主題模式 佇列Two
    @Bean
    public Queue topicQueueTwo(){
        return new Queue(RabbitMQConstant.RABBITMQ_TOPIC_QUEUE_NAME_KH96_TWO,true);
    }

    //Topic 主題模式 佇列Three
    @Bean
    public Queue topicQueueThree(){
        return new Queue(RabbitMQConstant.RABBITMQ_TOPIC_QUEUE_NAME_KH96_THREE,true);
    }

    //======= 交換機
    //Topic 主題模式 交換機
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(RabbitMQConstant.RABBITMQ_TOPIC_EXCHANGE_KH96);

    }

    //======= 系結
    // 佇列One 系結 Topic主題模式交換機  和 路由鍵-唯一匹配規則
    @Bean
    public Binding topicBindingQueueOne(){
        return BindingBuilder.bind(topicQueueOne())
                .to(topicExchange())
                .with(RabbitMQConstant.RABBITMQ_TOPIC_ROUTING_KEY_KH96_ONLY);

    }

    // 佇列Two 系結 Topic主題模式交換機  和 路由鍵-單個單詞詞匹配規則
    @Bean
    public Binding topicBindingQueueTwo(){
        return BindingBuilder.bind(topicQueueTwo())
                .to(topicExchange())
                .with(RabbitMQConstant.RABBITMQ_TOPIC_ROUTING_KEY_KH96_WORLD);

    }

    // 佇列Two 系結 Topic主題模式交換機  和 路由鍵-模糊匹配規則
    @Bean
    public Binding topicBindingQueueThree(){
        return BindingBuilder.bind(topicQueueThree())
                .to(topicExchange())
                .with(RabbitMQConstant.RABBITMQ_TOPIC_ROUTING_KEY_KH96_LIKE);

    }


}

3.3.2 訊息生產者

/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQ 主題模式訊息生產者
 */
@Slf4j
@Component
public class RabbitMQTopicProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @author : huayu
     * @date   : 1/11/2022
     * @param  : [topicExchange, topicRoutingKey, topicMsg]
     * @return : void
     * @description : 使用主題模式,發送訊息到主題交換機,主題交換機會根據發送訊息的路由鍵 ,根據匹配規則將訊息投遞到匹配的佇列中
     */
    public void sendTopicMsg2TopicExchange(String topicExchange,String topicRoutingKey,String topicMsg){
        log.info("++++++  direct模式訊息生產者,發送直連訊息:{},到交換機:{},路由鍵:{} ++++++",topicMsg,topicExchange,topicRoutingKey);

        rabbitTemplate.convertAndSend(topicExchange,topicRoutingKey,topicMsg);

    }

}

3.3.3 消費者

3.3.3.1 消費者One
/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQTopicConsumerOne
 */
@Slf4j
@Component
@RabbitListener(queues = RabbitMQConstant.RABBITMQ_TOPIC_QUEUE_NAME_KH96_ONE)
public class RabbitMQTopicConsumerOne {

    @RabbitHandler
    public void consumeTopicMsgFromTopicQueue(String topicMapJson){
        log.info("****** Topic 主題模式,消費One,消費佇列One,訊息:{}  ******",topicMapJson);

        // TODO 核心業務邏輯處理

    }

}
3.3.3.2 消費者Two
/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQTopicConsumerTwo
 */
@Slf4j
@Component
@RabbitListener(queues = RabbitMQConstant.RABBITMQ_TOPIC_QUEUE_NAME_KH96_TWO)
public class RabbitMQTopicConsumerTwo {

    @RabbitHandler
    public void consumeTopicMsgFromTopicQueue(String topicMapJson){
        log.info("****** Topic 主題模式,消費 Two,消費佇列 Two,訊息:{}  ******",topicMapJson);

        // TODO 核心業務邏輯處理

    }

}
3.3.3.3 消費者Three
/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQTopicConsumerThree
 */
@Slf4j
@Component
@RabbitListener(queues = RabbitMQConstant.RABBITMQ_TOPIC_QUEUE_NAME_KH96_THREE)
public class RabbitMQTopicConsumerThree {

    @RabbitHandler
    public void consumeTopicMsgFromTopicQueue(String topicMapJson){
        log.info("****** Topic 主題模式,消費 Three,消費佇列 Three,訊息:{}  ******",topicMapJson);

        // TODO 核心業務邏輯處理

    }

}

3.3.4 請求測驗方法

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 測驗 RabbitMQ 訊息佇列的操作入口
 */
@Slf4j
@RestController
public class RabbitMQController {

    @Autowired
    private RabbitMQTopicProducer rabbitMQTopicProducer;
    
        @GetMapping("/topic")
    public RequestResult<String> testRabbitMQTopic(@RequestParam String topicMsg){
        log.info("------- topic 主題模式,發送訊息 -------");
        //模擬發送5條直連訊息
        Stream.of(95,96,97,98,99).forEach(directNo ->{
            //模擬創建訊息物件
            Map<String,Object> fanoutMap =new HashMap<>();
            fanoutMap.put("directNo",directNo);
            fanoutMap.put("directData",topicMsg);
            fanoutMap.put("directTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

            //呼叫主題模式訊息生產者,發送訊息
            //場景1:使用唯一路由鍵 rabbitmq_topic_routing_key_kh96.only , 發送訊息
            rabbitMQTopicProducer.sendTopicMsg2TopicExchange(RabbitMQConstant.RABBITMQ_TOPIC_EXCHANGE_KH96
                                                            ,RabbitMQConstant.RABBITMQ_TOPIC_ROUTING_KEY_KH96_ONLY
                                                            ,JSON.toJSONString(fanoutMap));

            //場景2:使用單詞匹配路由鍵 rabbitmq_topic_routing_key_kh96.* ,發送訊息
//            rabbitMQTopicProducer.sendTopicMsg2TopicExchange(RabbitMQConstant.RABBITMQ_TOPIC_EXCHANGE_KH96
//                                    ,"rabbitmq_topic_routing_key_kh96.abc"
//                                    ,JSON.toJSONString(fanoutMap));

            //場景3:0 或多詞匹配 rabbitmq_topic_routing_key_kh96.# ,發送訊息
//            rabbitMQTopicProducer.sendTopicMsg2TopicExchange(RabbitMQConstant.RABBITMQ_TOPIC_EXCHANGE_KH96
//                                                            ,"rabbitmq_topic_routing_key_kh96.abc.def"
//                                                            ,JSON.toJSONString(fanoutMap));

        });

        return ResultBuildUtil.success("使用主題模式,發送訊息成功");
    }

    
}

3.3.5 請求測驗

3.3.5.1 場景1:使用唯一路由鍵
發送訊息路由鍵名: rabbitmq_topic_routing_key_kh96.only
發起請求:

請求結果:

佇列One,Two,Three都接收到了資訊,所以對應的消費者One,Two,Three都消費了資訊;

3.3.5.2 場景2:使用單詞匹配路由鍵
發送訊息路由鍵名: rabbitmq_topic_routing_key_kh96.abc
發起請求:

請求結果:

佇列Two,Three都接收到了資訊,所以對應的消費者Two,Three都消費了資訊;

3.3.5.3 場景3:0 或多詞匹配
發送訊息路由鍵名: rabbitmq_topic_routing_key_kh96.abc.def
發起請求:

請求結果:

只有佇列Three接收到了資訊,所以只有對應的消費者Three消費了資訊;

3.3.6 主題模式小結

  1. 當生產者發送訊息到交換機,指定的路由鍵一般都是使用句點(.)作為分隔符,分割多個單詞,

    • 比如:詞1.詞...
  2. 所謂單詞:是由一個或多個單詞組成,多個單詞組成的路由鍵,就代表某種主題的關鍵資訊,路由鍵長度最多不能超過256位元組,

  3. 匹配規則格式:* 或者 #

    • *代表單個單詞,
      • 比如 佇列系結主題交換機的 路由鍵:KH96.* ,代表發送訊息的路由鍵是以KH96開頭,后面只能跟一個單詞,如:KH96.aaa,KH96.bbb等,
      • 再比如:系結路由鍵為:KH96.*.KGC,代表發送訊息路由鍵是以KH96開頭,中間可以帶一個單詞,結尾,如:KH96.aa.KGC,KH96.bb.KGC,
      • #代表0或多個單詞,比如 佇列系結主題交換機的 路由鍵:KH96.#,代表發送訊息的路由鍵是以KH96開頭,后面只能跟0個或者多個單詞,如:KH96,KH96.aaa,KH96.aaa.bbb,
      • 再比如:系結路由鍵為:KH96.#.KGC,代表發送訊息路由鍵是以KH96開頭,中間可以帶一個或多個單詞,結尾,如KH96.KGC,KH96.aa.KGC,KH96.aa.bb.KGC,
    1. 備注:

      • 如果主題交換機,佇列系結的路由鍵使用的不是模糊匹配符,主題交換機跟直連交換機一致,
      • 如果單獨使用#,代表所有佇列都可以收訊息,主題交換機跟扇形交換機一致,
  4. 提醒:

    • 主題模式下,佇列系結的路由鍵,是允許為多個的,

    • 如果路由鍵被更換,之前的路由鍵是不會洗掉,仍然會系結到當前佇列上,

    • 如果有多個路由鍵匹配,規則為:如果其中一個沒有匹配到,會自動匹配其他路由鍵,如果需要洗掉歷史路由鍵,需要在RabbitMQ控制臺洗掉,

3.4 訊息 發送確認 - 交換機,佇列 確認

3.4.1 配置資訊

# RabbitMQ配置
spring:
  rabbitmq:
    # 打開發送訊息確認配置
    publisher-confirms: true # 發送訊息到交換機確認,默認false
    publisher-returns: true # 發送訊息到佇列確認,默認是false

3.4.2 訊息發送確認配置類

  • 觸發機制
    • ConfirmCallback 函式式介面中的唯一抽象方法 confirm : 是否有交換機都會觸發;
      • 標識:true,發送到交換機正常;
      • 標識:false,發送到交換機失敗,進行特殊處理;
    • ReturnCallback 函式式介面中的唯一抽象方法 returnedMessage :交換機存在且佇列不存在才會觸發;
      • 觸發:發送到佇列失敗,進行特殊處理;
/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQ 訊息確認機制: 發送確認
 */
@Slf4j
@Configuration
public class RabbitMQSendMsgAck {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        //發送確認,訊息是通過rabbitTemplate發的,所以要重置rabbitTemplate才可以實作
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);

        //開啟觸發回呼處理方法,不論訊息推送結果是什么,都會強制觸發回呼方法
        rabbitTemplate.setMandatory(true);

        //指定訊息發送到RabbitMQ的broker節點,是否正確到達交換機確然
        //是否有交換機都會觸發
        rabbitTemplate.setConfirmCallback( (correlationData, ack, cause) ->{
            log.info("######  發送訊息確認回呼,資料:{}  ######",correlationData);
            log.info("######  發送訊息確認回呼,標識:{}  ######",ack);
            log.info("######  發送訊息確認回呼,原因:{}  ######\n",cause);

            //TODO 如果沒有到交換機,ack回傳的是false,可能是交換機被洗掉,就需要進行特殊處理的業務,比如給負責人發送資訊或郵件

        });

        //訊息是否正確到達交換機上系結的 目標佇列
        //交換機存在且佇列不存在才會觸發
        rabbitTemplate.setReturnCallback( ( message, replyCode, replyText,exchange,routingKey) ->{
            log.info("######  發送訊息回傳回呼,資料:{}  ######",message);
            log.info("######  發送訊息回傳回呼,回傳碼:{}  ######",replyCode);
            log.info("######  發送訊息回傳回呼,回傳說明:{}  ######",replyText);
            log.info("######  發送訊息回傳回呼,交換機:{}  ######",exchange);
            log.info("######  發送訊息回傳回呼,路由鍵:{}  ######\n",routingKey);

            //TODO 如果沒有到目標佇列,就需要進行特殊處理的業務,比如給負責人發送資訊或郵件

        });

        return rabbitTemplate;
    }

}

3.4.3 交換機

/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: Ack 測驗交換機,沒有系結佇列
 */
@Configuration
public class RabbitMQAckConfig {


    //ack 測驗交換機,沒有系結佇列
    @Bean
    public DirectExchange directExchange(){

        return new DirectExchange(RabbitMQConstant.RABBITMQ_ACK_EXCHANGE_KH96);

    }

}

3.4.4 請求方法

/**
* @author : huayu
* @date   : 2/11/2022
* @param  : [topicMsg]
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 直連模式測驗 Ack  不存在交換機  和 存在交換機
*/
@GetMapping("/sendMsgAck")
public RequestResult<String> RabbitMQSendMsgAck(@RequestParam String ackMsg){
    log.info("------- 直連 模式 測驗Ack,發送訊息 -------");
    //模擬發送直連訊息

    //呼叫直連模式訊息生產者,發送訊息
    //測驗1: 不存在的 交換機
    rabbitMQDirectProducer.sendDirectMsg2DirectExchange("test_noExchange"
                                                        ,RabbitMQConstant.RABBITMQ_DIRECT_ROUTING_KEY_KH96
                                                        ,JSON.toJSONString(ackMsg));

    return ResultBuildUtil.success("使用直連模式 測驗Ack,交換機不存在");

    //測驗2: 存在的交換機,但是沒有系結 佇列
    //            rabbitMQDirectProducer.sendDirectMsg2DirectExchange(RabbitMQConstant.RABBITMQ_ACK_EXCHANGE_KH96
    //                                    ,RabbitMQConstant.RABBITMQ_DIRECT_ROUTING_KEY_KH96
    //                                    ,JSON.toJSONString(ackMsg));


    //        return ResultBuildUtil.success("使用直連模式 測驗Ack,交換機 沒有系結佇列");

}

3.2.5 請求測驗

3.2.5.1 交換機不存在
發起請求:

請求結果:

交換機不存在,

觸發了ConfirmCallback 函式式介面中的唯一抽象方法 confirm ,

回傳標識 false,發送到交換機失敗,

原因,該交換機不存在;

注意:如果沒有到交換機,ack回傳的是false,可能是交換機被洗掉,就需要進行特殊處理的業務,比如給負責人發送資訊或郵件;

3.2.5.2 交換機存在,但是沒有系結 佇列
發起請求:

請求結果:

交換機存在,

觸發了ConfirmCallback 函式式介面中的唯一抽象方法 confirm ,

回傳標識 true,發送到交換機成功;

沒有系結佇列,

觸發了ReturnCallback 函式式介面中的唯一抽象方法 returnedMessage ,

回傳說明 NO_ROUT,發送到佇列失敗;

注意:如果沒有到目標佇列,就需要進行特殊處理的業務,比如給負責人發送資訊或郵件;

3.2.5.3 交換機存在,且系結了佇列
發起請求

請求結果:

交換機存在,且系結了佇列,

觸發了ConfirmCallback 函式式介面中的唯一抽象方法 confirm ,

回傳標識 true,發送到交換機成功;

沒有觸發ReturnCallback 函式式介面中的唯一抽象方法 returnedMessage ,

說明發送到佇列成功;

3.5 訊息確認

3.5.1 自動確認

3.5.1.1 配置資訊
# RabbitMQ配置
spring:
  rabbitmq:
    # 消費訊息確認配置-自動
    listener:
      simple:
        retry:
          enabled: true # 開啟消費訊息失敗重試機制
          max-attempts: 5 # 指定重試的次數
          max-interval: 10000 # 最大重試間隔時間,單位毫秒,每次重試的間隔時間,不能比當前設定的值大,如果計算間隔時間是6s,最大時間時間5s,會用5秒
          initial-interval: 1000 # 重試間隔初始時間,單位毫秒
          multiplier: 2 #乘子;重試的間隔時間 * 乘子,就是下一次重試的時間間隔市場,即:1s,2s,4s,8s,16...
3.5.1.2 消費者 模擬例外

注意:測驗時為了讓消費者One一定接收到訊息,所以注釋掉消費者Two,這樣才可以保證消費者One接收訊息,然后觸發例外,重試的效果;

/**
 * Created On : 1/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: Direct 直連模式消費者 One
 */
@Slf4j
@Component
//指定接聽的 訊息佇列 名字
@RabbitListener(queues = RabbitMQConstant.RABBITMQ_DIRECT_QUEUE_NAME_KH96)
public class RabbitMQDirectConsumerOne {

    /**
     * @author : huayu
     * @date   : 1/11/2022
     * @param  : [directMsgJson]
     * @return : void
     * @description : Direct 直連模式消費者One,消費資訊
     */
    //指定訊息佇列中的訊息,交給對應的方法處理
    @RabbitHandler
    public void consumeOneDirectMsgFromDirectQueue(String directMsgJson){
        log.info("***** Direct直連模式,消費者One,消費訊息:{} ******",directMsgJson);

        // TODO 核心業務邏輯處理

        //默認自動確認,模擬消費端消費訊息,處理例外,自動重試
        int a = 10 / 0;

    }

}
3.5.1.3 請求方法
/**
* @author : huayu
* @date   : 3/11/2022
* @param  : [ackMsg]
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 測驗 消費者自動 重試 
*/
@GetMapping("/consumeAckAuto")
public RequestResult<String> testRabbitMQConsumeAckAuto(@RequestParam String ackMsg){
    log.info("------- 直連 模式 測驗Ack 自動 重試,發送訊息 -------");
    //模擬發送直連訊息
    //消費訊息失敗重試機制
  rabbitMQDirectProducer.sendDirectMsg2DirectExchange(RabbitMQConstant.RABBITMQ_DIRECT_EXCHANGE_KH96
                                                        ,RabbitMQConstant.RABBITMQ_DIRECT_ROUTING_KEY_KH96
                                                        ,JSON.toJSONString(ackMsg));

    return ResultBuildUtil.success("使用直連模式 消費確認-自動消費成功");

}
3.5.1.4 請求測驗

發起請求:

請求結果:

一共重試了五次

間隔時間為1,2,4,8

(如果還有一次應該為10,因為最后一次計算時間16大于最大間隔時間10,按最大間隔時間10重試);

3.4.2 手動確認

注意:

  • 手動確認需要先將自動確認的配置注釋掉;
  • 使用手動確認,不能再用@RabbitListener 監聽,手動確認相關佇列,需要我們手動配置消費者;
3.4.2.1 消費訊息手動確認的監聽器
  • 獲取訊息消費的唯一標識 message.getMessageProperties().getDeliveryTag();

  • 執行業務處理

    • 每個消費者在同一個時間點,最多處理一個message,默認是0(全部) channel.basicQos(1);
    • 獲取message的訊息內容 message
    • 獲取訊息對應的目標佇列,可以實作一些靈活判斷處理message.getMessageProperties().getConsumerQueue()
      • 比如根據不同的目標佇列進行不同的處理
      • 在訊息處理的時候如果出錯會被捕獲(訊息確認失敗)
    • 訊息確認channel.basicAck(deliveryTag,false);
  • 訊息確認失敗處理

    • 根據條件判斷設定是否重回佇列 ,是否支持批量處理 channel.basicNack(deliveryTag,true,false);
/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 消費端 消費訊息手動確認的監聽器,注意它也是一個消費者,并可以通過 訊息監聽容器工廠,動態配置多個
 */
@Slf4j
@Component
public class RabbitMQConsumerManualAckListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws IOException {
        //獲取訊息消費的唯一標識,rabbitMQ在推送訊息時,會給每個訊息攜帶一個唯一標識,值是一個遞增的正整數
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        log.info("====== 消費訊息的唯一標識:{}  ======",deliveryTag);

        //執行手動確認業務處理
        try{
            //給每個消費者在同一個時間點,最多處理一個message,默認是0(全部),換句話說,在接收到消費者的 ack 確認前,不會分發新的訊息給當前的消費者
            //在接收當前訊息的ack確認前是不會發送新的訊息給它
            channel.basicQos(1);

            //獲取message的訊息內容,發送的訊息的json字串
            log.info("====== 訊息佇列中完整訊息內容:{} ======",message);

            //獲取發送的實際內容,發送訊息的json字串
            log.info("====== 發送的實際內容:{} ======",new String(message.getBody(),"utf-8"));

            //獲取訊息對應的目標佇列,可以實作一些靈活判斷
            //TODO 比如根據目標佇列不同,可以做不同的處理
            log.info("======  訊息的來源佇列:{} =======",message.getMessageProperties().getConsumerQueue());

            //模擬錯誤 ,當 deliveryTag 為1的時候,進入 報錯 ,捕獲例外,然后(如果設定了重回佇列)將訊息重回佇列
            //if(deliveryTag == 1){
            //    int num = 1/0;
            //}

            //消費訊息的手動確認,訊息確認成功-basicAck
            //第一個引數deliveryTag,訊息的唯一標識
            //第二個引數multiple,訊息是否支持批量確認,如果是true,代表可以一次性確認標識小于等于當前標識的所有訊息
            //如果是false,只會確認當前訊息
            channel.basicAck(deliveryTag,false);


        }catch (Exception e){
            //說明消費訊息處理失敗,如果不進行確認(自動確認,投遞成功即確認,消費是否正常,不關心),訊息就會丟失
            //訊息處理失敗確認,代表訊息沒有正確消費,注意:此種方式一次只能確認一個訊息
            //第一給引數是訊息的唯一標識,
            //第二個引數是代表是否重回佇列,如果是true,重新將該訊息放入佇列,再次消費
            //注意:第二個引數要謹慎,必須要結合具體業務場景,根據業務判斷是否需要重回佇列,一旦處理不當,機會導致訊息回圈入隊,訊息擠壓
            //不重回佇列 require = false
//            channel.basicReject(deliveryTag,false);
            //重回佇列 require = true
            channel.basicReject(deliveryTag,true);

            //訊息處理失敗確認,代表訊息沒有正確消費,注意,此種方式支持批量
            //第一個引數是訊息的唯一標識,
            //第二個引數是代表是否支持批量確認
            //第三給引數代表是否重回佇列
            //不重回佇列 require = false
//            channel.basicNack(deliveryTag,true,false);
            //重回佇列 require = true
//            channel.basicNack(deliveryTag,false,true);

            //TODO 手動消費例外處理

            log.error("====== 消費訊息失敗,例外資訊:{}  ======",e.getMessage());

        }

    }

}

3.4.2.2 消費訊息手動確認配置類
  • 配置消費者的數量 setConcurrentConsumers(2);
  • 最大并發消費者數量 setMaxConcurrentConsumers(5);
  • 消費訊息確認機制為手動 setAcknowledgeMode(AcknowledgeMode.MANUAL);
  • 設定監聽訊息佇列的名稱,支持多個佇列setQueueNames(RabbitMQConstant.RABBITMQ_DIRECT_QUEUE_NAME_KH96);
  • 設定訊息手動確認監聽器 setMessageListener(rabbitMQConsumerManualAckListener);
/**
 * Created On : 2/11/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: RabbitMQ  消費訊息手動確認配置類
 */
@Configuration
public class RabbitMQConsumeManualAckConfig {

    @Autowired
    private RabbitMQConsumerManualAckListener rabbitMQConsumerManualAckListener;

    /**
     * @author : huayu
     * @date   : 2/11/2022
     * @param  : [connectionFactory]
     * @return : org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
     * @description : 自定義訊息監聽器工程物件
     */
    @Bean
    public SimpleMessageListenerContainer simpleBrokerMessageHandler(ConnectionFactory connectionFactory){
        //初始化訊息監聽容器的工程物件
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);

        //初始化并發消費者的數量,比如是2,代表同時會有 兩個消費者 消費訊息
        // ,投遞標識可能會相同
        container.setConcurrentConsumers(2);

        //設定最大的并發消費者數量,數量不能低于初始化并發消費者數量
        //可以動態的設定當前容器的消費者數量,可以實作動態增加和減少消費者的演算法在 SimpleMessageListenerContainer類中實作
        container.setMaxConcurrentConsumers(5);

        //底層動態實作消費者數量的增加減少原理
        // 有consumer已連續十個周期(consecutiveActiveTrigger)處于活動狀態,并且自啟動后最后一個consumer運行至少經過了10秒鐘,則將啟動新的consumer,
        // private static final long DEFAULT_START_CONSUMER_MIN_INTERVAL = 10000;
        // 停止消費者演算法的時間間隔
        // 有consumer已連續10個周期(consecutiveIdleTrigger)連續空閑狀態,并且上一個consumer至少在60秒之前停止,那么該consumer將停止
        // private static final long DEFAULT_STOP_CONSUMER_MIN_INTERVAL = 60000;
        // 默認連續活動10個周期
        // private static final int DEFAULT_CONSECUTIVE_ACTIVE_TRIGGER = 10;
        // 默認連續空閑10個周期
        // private static final int DEFAULT_CONSECUTIVE_IDLE_TRIGGER = 10;

        //默認的消費訊息確認機制是自動,需要改為手動
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);

        //設定監聽訊息佇列的名稱,支持多個佇列(佇列名1,佇列名2...),注意前提是指定的佇列必須是存在的
        //監聽 直連模式的 RabbitMQConstant.RABBITMQ_DIRECT_QUEUE_NAME_KH96 佇列
        container.setQueueNames(RabbitMQConstant.RABBITMQ_DIRECT_QUEUE_NAME_KH96);

        //監聽 扇形模式的 
        //RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_ONE 佇列
        //和 RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_TWO 佇列
//        container.setQueueNames(RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_ONE
//                ,RabbitMQConstant.RABBITMQ_FANOUT_QUEUE_NAME_KH96_TWO);

        //指定訊息確認的處理類,會同時產生多個消費者,引數是上面設定的,
        //注意之前使用直連模式,訊息消費者,要注釋掉,防止同型別的監聽器,處理同一佇列
        //如果不是被當前訊息確認的處理類消費(使用注解@RabbitListener),會導致訊息不執行手動處理
        container.setMessageListener(rabbitMQConsumerManualAckListener);

        // 回傳訊息監聽容器工廠物件
        return container;

    }


}
3.4.2.3 請求方法
//======================
/**
* @author : huayu
* @date   : 3/11/2022
* @param  : [ackMsg]
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 測驗 消費者手動確認
*/
@GetMapping("/consumeAckManual")
public RequestResult<String> testRabbitMQConsumeAckManual(@RequestParam String ackMsg){
    log.info("------- 測驗Ack 手動 確認,發送訊息 -------");
    //訊息手動確認
    //模擬發送直連訊息
    //測驗1,2
    rabbitMQDirectProducer.sendDirectMsg2DirectExchange(
        RabbitMQConstant.RABBITMQ_DIRECT_EXCHANGE_KH96
        ,RabbitMQConstant.RABBITMQ_DIRECT_ROUTING_KEY_KH96
        ,JSON.toJSONString(ackMsg));

    return ResultBuildUtil.success("使用直連模式 手動消費確認-訊息確認成功");
    

    //測驗3
    //模擬發送扇形訊息
    //        rabbitMQFanoutProducer.sendFanoutMsg2FanoutExchange(
    //                RabbitMQConstant.RABBITMQ_FANOUT_EXCHANGE_KH96
    //                ,null
    //                ,JSON.toJSONString(ackMsg));
    //
    //
    //        return ResultBuildUtil.success("使用扇形模式 手動消費確認-訊息確認成功");

}
3.4.2.4 請求測驗
3.4.2.4.1 模擬發送直連訊息并成功確認

發送請求:

請求結果:

3.4.2.4.2 模擬發送直連訊息,拋出例外,重回佇列

發送請求:

代碼重點:

請求結果:

3.4.2.4.3 模擬發送扇形訊息并成功確認

發送請求:

請求結果:




轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/527798.html

標籤:其他

上一篇:Java守護執行緒

下一篇:[Python]解密pyc檔案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more