Spring Boot整合一之Spring Boot整合RabbitMQ

1.首先我們簡單瞭解一下消息中間件的應用場景

異步處理
場景說明:用戶註冊後,需要發註冊郵件和註冊短信,傳統的做法有兩種1.串行的方式;2.並行的方式 
(1)串行方式:將註冊信息寫入數據庫後,發送註冊郵件,再發送註冊短信,以上三個任務全部完成後才返回給客戶端。 這有一個問題是,郵件,短信並不是必須的,它只是一個通知,而這種做法讓客戶端等待沒有必要等待的東西. 

(2)並行方式:將註冊信息寫入數據庫後,發送郵件的同時,發送短信,以上三個任務完成後,返回給客戶端,並行的方式能提高處理的時間。 

假設三個業務節點分別使用50ms,串行方式使用時間150ms,並行使用時間100ms。雖然並性已經提高的處理時間,但是,前面說過,郵件和短信對我正常的使用網站沒有任何影響,客戶端沒有必要等着其發送完成才顯示註冊成功,英愛是寫入數據庫後就返回. 
(3)消息隊列 
引入消息隊列後,把發送郵件,短信不是必須的業務邏輯異步處理 

由此可以看出,引入消息隊列後,用戶的響應時間就等於寫入數據庫的時間+寫入消息隊列的時間(可以忽略不計),引入消息隊列後處理後,響應時間是串行的3倍,是並行的2倍。

 應用解耦

場景:雙11是購物狂節,用戶下單後,訂單系統需要通知庫存系統,傳統的做法就是訂單系統調用庫存系統的接口. 

這種做法有一個缺點:

  • 當庫存系統出現故障時,訂單就會失敗。
  • 訂單系統和庫存系統高耦合. 
    引入消息隊列 

訂單系統:用戶下單後,訂單系統完成持久化處理,將消息寫入消息隊列,返回用戶訂單下單成功。

庫存系統:訂閱下單的消息,獲取下單消息,進行庫操作。 
就算庫存系統出現故障,消息隊列也能保證消息的可靠投遞,不會導致消息丟失。
流量削峯
流量削峯一般在秒殺活動中應用廣泛 
場景:秒殺活動,一般會因爲流量過大,導致應用掛掉,爲了解決這個問題,一般在應用前端加入消息隊列。 
作用: 
1.可以控制活動人數,超過此一定閥值的訂單直接丟棄(我爲什麼秒殺一次都沒有成功過呢^^) 
2.可以緩解短時間的高流量壓垮應用(應用程序按自己的最大處理能力獲取訂單) 

1.用戶的請求,服務器收到之後,首先寫入消息隊列,加入消息隊列長度超過最大值,則直接拋棄用戶請求或跳轉到錯誤頁面. 

2.秒殺業務根據消息隊列中的請求信息,再做後續處理.

以上內容的來源是:https://blog.csdn.net/whoamiyang/article/details/54954780,在此感謝

2.各種消息中間件性能的比較:

TPS比較 一ZeroMq 最好,RabbitMq 次之, ActiveMq 最差。

持久化消息比較—zeroMq不支持,activeMq和rabbitMq都支持。持久化消息主要是指:MQ down或者MQ所在的服務器down了,消息不會丟失的機制。

可靠性、靈活的路由、集羣、事務、高可用的隊列、消息排序、問題追蹤、可視化管理工具、插件系統、社區—RabbitMq最好,ActiveMq次之,ZeroMq最差。

高併發—從實現語言來看,RabbitMQ最高,原因是它的實現語言是天生具備高併發高可用的erlang語言。

綜上所述:RabbitMQ的性能相對來說更好更全面,是消息中間件的首選。

3.接下來我們在springboot當中整合使用RabbitMQ

需要提前安裝RabbitMQ 可參考:https://blog.csdn.net/Keith003/article/details/82386210

第一步:導入maven依賴

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

第二步:在application.yml文件中進行RabbitMQ的相關配置

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest    # guest 3.0以後默認只能本地登陸
    password: guest
    publisher-confirms: true  #  消息發送到交換機確認機制,是否確認回調
    publisher-returns: true
    virtual-host: /
    connection-timeout: 6000
server:
  port: 8080

注:使用場景:

  • 如果消息沒有到exchange,則confirm回調,ack=false
  • 如果消息到達exchange,則confirm回調,ack=true
  • exchange到queue成功,則不回調return
  • exchange到queue失敗,則回調return(需設置mandatory=true,否則不回回調,消息就丟了)

第三步:進行相關配置

ExchangeConfig    消息交換機配置

package com.space.rabbitmq.config;
 
import org.springframework.amqp.core.DirectExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * 消息交換機配置  可以配置多個
 */
@Configuration
public class ExchangeConfig {
 
    /**
     *   1.定義direct exchange,綁定queueTest
     *   2.durable="true" rabbitmq重啓的時候不需要創建新的交換機
     *   3.direct交換器相對來說比較簡單,匹配規則爲:如果路由鍵匹配,消息就被投送到相關的隊列
     *     fanout交換器中沒有路由鍵的概念,他會把消息發送到所有綁定在此交換器上面的隊列中。
     *     topic交換器你採用模糊匹配路由鍵的原則進行轉發消息到隊列中
     *   key: queue在該direct-exchange中的key值,當消息發送給direct-exchange中指定key爲設置值時,
     *   消息將會轉發給queue參數指定的消息隊列
     */
    @Bean
    public DirectExchange directExchange(){
        DirectExchange directExchange = new DirectExchange(RabbitMqConfig.EXCHANGE,true,false);
        return directExchange;
    }
}

 QueueConfig  隊列配置

package com.space.rabbitmq.config;
 
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * 隊列配置  可以配置多個隊列
 */
@Configuration
public class QueueConfig {
 
    @Bean
    public Queue firstQueue() {
        /**
         durable="true" 持久化 rabbitmq重啓的時候不需要創建新的隊列
         auto-delete 表示消息隊列沒有在使用時將被自動刪除 默認是false
         exclusive  表示該消息隊列是否只在當前connection生效,默認是false
         */
        return new Queue("first-queue",true,false,false);
    }
 
    @Bean
    public Queue secondQueue() {
        return new Queue("second-queue",true,false,false);
    }
}

 RabbitMqConfig RabbitMq配置

package com.space.rabbitmq.config;

import com.space.rabbitmq.mqcallback.MsgSendConfirmCallBack;
import com.space.rabbitmq.mqcallback.MsgSendReturnCallback;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMq配置
 */
@Configuration
public class RabbitMqConfig {

    /** 消息交換機的名字*/
    public static final String EXCHANGE = "exchangeTest";
    /*對列名稱*/
    public static final String QUEUE_NAME1 = "first-queue";
    public static final String QUEUE_NAME2 = "second-queue";

    /*
    *
    * key: queue在該direct-exchange中的key值,當消息發送給direct-exchange中指定key爲設置值時,
    *   消息將會轉發給queue參數指定的消息隊列
    */
    /** 隊列key1*/
    public static final String ROUTINGKEY1 = "queue_one_key1";
    /** 隊列key2*/
    public static final String ROUTINGKEY2 = "queue_one_key2";

    @Autowired
    private QueueConfig queueConfig;
    @Autowired
    private ExchangeConfig exchangeConfig;

    /**
     * 連接工廠
     */
    @Autowired
    private ConnectionFactory connectionFactory;

    /**
     * 將消息隊列1和交換機進行綁定,指定隊列key1
     */
    @Bean
    public Binding binding_one() {
        return BindingBuilder.bind(queueConfig.firstQueue()).to(exchangeConfig.directExchange()).with(RabbitMqConfig.ROUTINGKEY1);
    }

    /**
     * 將消息隊列2和交換機進行綁定,指定隊列key2
     */
    @Bean
    public Binding binding_two() {
        return BindingBuilder.bind(queueConfig.secondQueue()).to(exchangeConfig.directExchange()).with(RabbitMqConfig.ROUTINGKEY2);
    }

    /**
     * queue listener  觀察 監聽模式
     * 當有消息到達時會通知監聽在對應的隊列上的監聽對象
     * @return
     */
    /*@Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer_one(){
        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
        simpleMessageListenerContainer.addQueues(queueConfig.firstQueue());
        simpleMessageListenerContainer.setExposeListenerChannel(true);
        simpleMessageListenerContainer.setMaxConcurrentConsumers(5);
        simpleMessageListenerContainer.setConcurrentConsumers(1);
        simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL); //設置確認模式手工確認
        return simpleMessageListenerContainer;
    }*/

    /**
     * 自定義rabbit template用於數據的接收和發送
     * 可以設置消息確認機制和回調
     * @return
     */
    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        // template.setMessageConverter(); 可以自定義消息轉換器  默認使用的JDK的,所以消息對象需要實現Serializable

        /**若使用confirm-callback或return-callback,
         * 必須要配置publisherConfirms或publisherReturns爲true
         * 每個rabbitTemplate只能有一個confirm-callback和return-callback
         */
        template.setConfirmCallback(msgSendConfirmCallBack());

        /**
         * 使用return-callback時必須設置mandatory爲true,或者在配置中設置mandatory-expression的值爲true,
         * 可針對每次請求的消息去確定’mandatory’的boolean值,
         * 只能在提供’return -callback’時使用,與mandatory互斥
         */
        template.setReturnCallback(msgSendReturnCallback());
        template.setMandatory(true);
        return template;
    }

    /*  關於 msgSendConfirmCallBack 和 msgSendReturnCallback 的回調說明:
        1.如果消息沒有到exchange,則confirm回調,ack=false
        2.如果消息到達exchange,則confirm回調,ack=true
        3.exchange到queue成功,則不回調return
        4.exchange到queue失敗,則回調return(需設置mandatory=true,否則不回回調,消息就丟了)
    */

    /**
     * 消息確認機制
     * Confirms給客戶端一種輕量級的方式,能夠跟蹤哪些消息被broker處理,
     * 哪些可能因爲broker宕掉或者網絡失敗的情況而重新發布。
     * 確認並且保證消息被送達,提供了兩種方式:發佈確認和事務。(兩者不可同時使用)
     * 在channel爲事務時,不可引入確認模式;同樣channel爲確認模式下,不可使用事務。
     * @return
     */
    @Bean
    public MsgSendConfirmCallBack msgSendConfirmCallBack(){
        return new MsgSendConfirmCallBack();
    }

    @Bean
    public MsgSendReturnCallback msgSendReturnCallback(){
        return new MsgSendReturnCallback();
    }
}

消息回調

package com.space.rabbitmq.mqcallback;
 
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
 
/**
 * 消息發送到交換機確認機制
 */
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {
 
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        System.out.println("MsgSendConfirmCallBack  , 回調id:" + correlationData);
        if (ack) {
            System.out.println("消息消費成功");
        } else {
            System.out.println("消息消費失敗:" + cause+"\n重新發送");
        }
    }
}
package com.space.rabbitmq.mqcallback;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("回饋消息:"+message);
    }
}

生產者/消息發送者

package com.space.rabbitmq.sender;
 
import com.space.rabbitmq.config.RabbitMqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.UUID;
 
/**
 * 消息發送  生產者1
 */
@Slf4j
@Component
public class FirstSender {
 
    @Autowired
    private RabbitTemplate rabbitTemplate;
 
    /**
     * 發送消息
     * @param uuid
     * @param message  消息
     */
    public void send(String uuid,Object message) {
        CorrelationData correlationId = new CorrelationData(uuid);
        rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE, RabbitMqConfig.ROUTINGKEY2,
                message, correlationId);
    }
}

消費者

package com.space.rabbitmq.receiver;
 
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
 
/**
 * 消息消費者1
 */
@Component
public class FirstConsumer {
 
    @RabbitListener(queues = {"first-queue","second-queue"}, containerFactory = "rabbitListenerContainerFactory")
    public void handleMessage(String message) throws Exception {
        // 處理消息
        System.out.println("FirstConsumer {} handleMessage :"+message);
    }
}

測試

package com.space.rabbitmq.controller;
 
import com.space.rabbitmq.sender.FirstSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.UUID;
 

@RestController
public class SendController {
 
    @Autowired
    private FirstSender firstSender;
 
    @GetMapping("/send")
    public String send(String message){
        String uuid = UUID.randomUUID().toString();
        firstSender.send(uuid,message);
        return uuid;
    }
}

此時,我們就可以啓動項目,進行測試了

 

轉載:https://blog.csdn.net/zhuzhezhuzhe1/article/details/80454956

           https://blog.csdn.net/qq_38455201/article/details/80308771

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章