RabbitMQ-初見

ML李嘉圖發表於2021-09-04

什麼是中介軟體

一個企業可能同時執行著多個不同的業務系統,

這些系統可能基於不同的作業系統、不同的資料庫、異構的網路環境。

現在的問題是,如何把這些資訊系統結合成一個有機地協同工作的整體,真正實現企業跨平臺、分散式應用。

中介軟體便是解決之道,它用自己的複雜換取了企業應用的簡單。

中介軟體(Middleware)是處於作業系統和應用程式之間的軟體,也有人認為它應該屬於作業系統中的一部分。

人們在使用中介軟體時,往往是一組中介軟體整合在一起,構成一個平臺(包括開發平臺和執行平臺),

但在這組中介軟體中必須要有一個通訊中介軟體,

即中介軟體=平臺+通訊,

這個定義也限定了只有用於分散式系統中才能稱為中介軟體,同時還可以把它與支撐軟體和實用軟體區分開來。
RabbitMQ-初見

訊息佇列協議

比如我MQ傳送一個資訊,是以什麼資料格式傳送到佇列中,然後每個部分的含義是什麼,

傳送完畢以後的執行的動作,以及消費者消費訊息的動作,消費完畢的響應結果和反饋是什麼,

然後按照對應的執行順序進行處理。

例如:大家每天都在接觸的http請求協議:

1:語法:http規定了請求報文和響應報文的格式。
2:語義:客戶端主動發起請求稱之為請求。(這是一種定義,同時你發起的是post/get請求)
3:時序:一個請求對應一個響應。(一定先有請求在有響應,這個是時序)

而訊息中介軟體採用的並不是http協議,

而常見的訊息中介軟體協議有:OpenWire、AMQP、MQTT、Kafka,OpenMessage協議。

為什麼訊息中介軟體不直接使用http協議呢?

  • 因為http請求報文頭和響應報文頭是比較複雜的,包含了cookie,資料的加密解密,狀態碼,響應碼等附加的功能,

    但是對於一個訊息而言,我們並不需要這麼複雜,

    它其實就是負責資料傳遞,儲存,分發就行,

    一定要追求的是高效能。儘量簡潔,快速。

  • 大部分情況下http大部分都是短連結,

    一個請求到響應很有可能會中斷,中斷以後就不會就行持久化,就會造成請求的丟失。

    這樣就不利於訊息中介軟體的業務場景,因為訊息中介軟體可能是一個長期的獲取訊息的過程,出現問題和故障要對資料或訊息就行持久化等,

    目的是為了保證訊息和資料的高可靠和穩健的執行。

AMQP協議

AMQP:(全稱:Advanced Message Queuing Protocol) 是高階訊息佇列協議。

是一個提供統一訊息服務的應用層標準高階訊息佇列協議,是應用層協議的一個開放標準,為面向訊息的中介軟體設計。

基於此協議的客戶端與訊息中介軟體可傳遞訊息,並不受客戶端/中介軟體不同產品,不同的開發語言等條件的限制。

特性:

  • 分散式事務支援。
  • 訊息的持久化支援。
  • 高效能和高可靠的訊息處理優勢。

MQTT協議

MQTT協議:(Message Queueing Telemetry Transport)訊息佇列是IBM開放的一個即時通訊協議,物聯網系統架構中的重要組成部分。

特點:

  • 輕量

  • 結構簡單

  • 傳輸快,不支援事務

  • 沒有持久化設計。

應用場景:

  • 適用於計算能力有限
  • 低頻寬
  • 網路不穩定的場景。

OpenMessage協議

由阿里、雅虎和滴滴出行、Stremalio等公司共同參與創立的分散式訊息中介軟體、流處理等領域的應用開發標準。

特點:

  • 結構簡單
  • 解析速度快
  • 支援事務和持久化設計

Kafka協議

Kafka協議是基於TCP/IP的二進位制協議。

訊息內部是通過長度來分割,由一些基本資料型別組成。

特點是:

  • 結構簡單
  • 解析速度快
  • 無事務支援
  • 有持久化設計

訊息佇列持久化

簡單來說就是將資料存入磁碟,而不是存在記憶體中隨伺服器重啟斷開而消失,使資料能夠永久儲存。

ActiveMQ RabbitMQ Kafka RocketMQ
檔案儲存 支援 支援 支援 支援
資料庫 支援 / / /

訊息的分發策略

MQ訊息佇列有如下幾個角色

  • 生產者
  • 儲存訊息
  • 消費者

那麼生產者生成訊息以後,MQ進行儲存,消費者是如何獲取訊息的呢?

一般獲取資料的方式無外乎推(push)或者拉(pull)兩種方式,典型的git就有推拉機制,

我們傳送的http請求就是一種典型的拉取資料庫資料返回的過程。

而訊息佇列MQ是一種推送的過程,而這些推機制會適用到很多的業務場景也有很多對應推機制策略。

ActiveMQ RabbitMQ Kafka RocketMQ
釋出訂閱 支援 支援 支援 支援
輪詢分發 支援 支援 支援 /
公平分發 / 支援 支援 /
重發 支援 支援 / 支援
訊息拉取 / 支援 支援 支援

訊息佇列高可用和高可靠

所謂高可用:是指產品在規定的條件和規定的時刻或時間內處於可執行規定功能狀態的能力。

當業務量增加時,請求也過大,一臺訊息中介軟體伺服器的會觸及硬體(CPU,記憶體,磁碟)的極限,

一臺訊息伺服器你已經無法滿足業務的需求,所以訊息中介軟體必須支援叢集部署。來達到高可用的目的。

什麼是高可用機制

叢集模式1 - Master-slave主從共享資料的部署方式

RabbitMQ-初見

解說:生產者講消費傳送到Master節點,所有的都連線這個訊息佇列共享這塊資料區域,Master節點負責寫入,一旦Master掛掉,slave節點繼續服務。從而形成高可用,

叢集模式2 - Master- slave主從同步部署方式

RabbitMQ-初見

解釋:這種模式寫入訊息同樣在Master主節點上,但是主節點會同步資料到slave節點形成副本,和zookeeper或者redis主從機制很類同。

這樣可以達到負載均衡的效果,如果消費者有多個這樣就可以去不同的節點就行消費,以為訊息的拷貝和同步會暫用很大的頻寬和網路資源。

叢集模式3 - 多主叢集同步部署模式

RabbitMQ-初見

解釋:和上面的區別不是特別的大,但是它的寫入可以往任意節點去寫入。

叢集模式4 - 多主叢集轉發部署模式

RabbitMQ-初見

解釋:如果你插入的資料是broker-1中,後設資料資訊會儲存資料的相關描述和記錄存放的位置(佇列)。

它會對描述資訊也就是後設資料資訊就行同步,如果消費者在broker-2中進行消費,發現自己幾點沒有對應的訊息,

可以從對應的後設資料資訊中去查詢,然後返回對應的訊息資訊

場景:比如買火車票或者黃牛買演唱會門票,比如第一個黃牛有顧客說要買的演唱會門票,但是沒有但是他會去聯絡其他的黃牛詢問,如果有就返回。

叢集模式5 Master-slave與Breoker-cluster組合的方案

RabbitMQ-初見

解釋:實現多主多從的熱備機制來完成訊息的高可用以及資料的熱備機制,在生產規模達到一定的階段的時候,這種使用的頻率比較高。

這麼多叢集模式,他們的最終目的都是為保證:訊息伺服器不會掛掉,出現了故障依然可以抱著訊息服務繼續使用。

三句話:

  1. 要麼訊息共享,
  2. 要麼訊息同步
  3. 要麼後設資料共享

什麼是高可靠機制

所謂高可用是指:是指系統可以無故障低持續執行,比如一個系統突然崩潰,報錯,異常等等並不影響線上業務的正常執行,出錯的機率極低,就稱之為:高可靠。

在高併發的業務場景中,如果不能保證系統的高可靠,那造成的隱患和損失是非常嚴重的。

如何保證中介軟體訊息的可靠性呢?可以從兩個方面考慮:

  1. 訊息的傳輸:通過協議來保證系統間資料解析的正確性。
  2. 訊息的儲存可靠:通過持久化來保證訊息的可靠性。

RabbitMQ入門及安裝(Win)

官網:https://www.rabbitmq.com/

什麼是RabbitMQ,官方給出來這樣的解釋:

RabbitMQ是部署最廣泛的開源訊息代理。
RabbitMQ擁有成千上萬的使用者,是最受歡迎的開源訊息代理之一。從T-Mobile 到Runtastic,RabbitMQ在全球範圍內的小型初創企業和大型企業中都得到使用。
RabbitMQ輕巧,易於在內部和雲中部署。它支援多種訊息傳遞協議。RabbitMQ可以部署在分散式和聯合配置中,以滿足大規模,高可用性的要求。
RabbitMQ可在許多作業系統和雲環境上執行,併為大多數流行語言提供了廣泛的開發人員工具。


簡單概述:
RabbitMQ是一個開源的遵循AMQP協議實現的基於Erlang語言編寫,支援多種客戶端(語言)。用於在分散式系統中儲存訊息,轉發訊息,具有高可用,高可擴性,易用性等特徵。

安裝RabbitMQ

下載地址:https://www.rabbitmq.com/download.html

環境準備:CentOS7.x+ / Erlang

RabbitMQ是採用Erlang語言開發的,所以系統環境必須提供Erlang環境,第一步就是安裝Erlang。

Erlang和RabbitMQ版本的按照比較: https://www.rabbitmq.com/which-erlang.html

Erlang安裝

安裝Erlang 時要注意安裝的RabbityMQ 所依賴的Erlang版本,根據RabbitMQ的要求選擇一個版本,下載Erlang安裝包後直接安裝就可以了。

設定ERLANG_HOME 環境變數

在開始選單查詢Erlang,點選啟動 開啟如下介面,那麼Erlang就安裝成功了。

安裝RabbitMQ

可以在RabbitMQ的官方網站下載最新版本的RabbitMQ伺服器安裝程式,RabbitMQ下載地址

RabbitMQ安裝好後是作為windows service 執行在後臺。

設定環境變數 RABBITQM_SERVER變數

然後在系統的path變數中配置如下: 加入sbin的路徑

啟動 rabbitmq-server

安裝 rabbitmq_management

用下列命令安裝rabbitmq_management外掛,這款外掛是可以視覺化的方式檢視RabbitMQ 伺服器例項的狀態,以及操控RabbitMQ伺服器。

rabbitmq-plugins enable rabbitmq_management

Web頁面

現在我們在瀏覽器中輸入:http://localhost:15672 可以看到一個登入介面

這裡可以使用預設賬號guest/guest登入

在瀏覽器中輸入 http://localhost:15672/api/ 就可以看到 RabbitMQ Management HTTP API 文件

相關埠

5672:RabbitMQ的通訊埠

25672:RabbitMQ的節點間的CLI通訊埠是

15672:RabbitMQ HTTP_API的埠,管理員使用者才能訪問,用於管理RabbitMQ,需要啟動Management外掛。

1883,8883:MQTT外掛啟動時的埠。

61613、61614:STOMP客戶端外掛啟用的時候的埠。

15674、15675:基於webscoket的STOMP埠和MOTT埠

授權賬號和密碼

新增使用者

rabbitmqctl add_user admin admin

設定使用者分配操作許可權

rabbitmqctl set_user_tags admin administrator

使用者級別:

  • 1、administrator 可以登入控制檯、檢視所有資訊、可以對rabbitmq進行管理
  • 2、monitoring 監控者 登入控制檯,檢視所有資訊
  • 3、policymaker 策略制定者 登入控制檯,指定策略
  • 4、managment 普通管理員 登入控制檯

為使用者新增資源許可權

rabbitmqctl.bat set_permissions -p / admin ".*" ".*" ".*"
rabbitmqctl add_user 賬號 密碼

rabbitmqctl set_user_tags 賬號 administrator

rabbitmqctl change_password Username Newpassword 修改密碼

rabbitmqctl delete_user Username 刪除使用者

rabbitmqctl list_users 檢視使用者清單

rabbitmqctl set_permissions -p / 使用者名稱 ".*" ".*" ".*" 為使用者設定administrator角色

rabbitmqctl set_permissions -p / root ".*" ".*" ".*"

RabbitMQ入門及安裝(Linux)

[root@iZm5eauu5f1ulwtdgwqnsbZ ~]# lsb_release -a
LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description:    CentOS Linux release 8.3.2011
Release:        8.3.2011
Codename:       n/a



wget https://packages.erlang-solutions.com/erlang-solutions-2.0-1.noarch.rpm
rpm -Uvh erlang-solutions-2.0-1.noarch.rpm



yum install -y erlang



erl -v



yum install -y socat



> wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.13/rabbitmq-server-3.8.13-1.el8.noarch.rpm
> rpm -Uvh rabbitmq-server-3.8.13-1.el8.noarch.rpm



# 啟動服務
> systemctl start rabbitmq-server
# 檢視服務狀態
> systemctl status rabbitmq-server
# 停止服務
> systemctl stop rabbitmq-server
# 開機啟動服務
> systemctl enable rabbitmq-server



rabbitmq-plugins enable rabbitmq_management



systemctl restart rabbitmq-server



rabbitmqctl add_user admin admin



rabbitmqctl set_user_tags admin administrator



rabbitmqctl.bat set_permissions -p / admin ".*" ".*" ".*"

docker pull rabbitmq:management

docker run -di --name=myrabbit -p 15672:15672 rabbitmq:management

docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management


docker logs -f myrabbit



> more xxx.log  檢視日記資訊
> netstat -naop | grep 5672 檢視埠是否被佔用
> ps -ef | grep 5672  檢視程式
> systemctl stop 服務




RabbitMQ的角色分類

1:none:

  • 不能訪問management plugin

2:management:檢視自己相關節點資訊

  • 列出自己可以通過AMQP登入的虛擬機器
  • 檢視自己的虛擬機器節點 virtual hosts的queues,exchanges和bindings資訊
  • 檢視和關閉自己的channels和connections
  • 檢視有關自己的虛擬機器節點virtual hosts的統計資訊。包括其他使用者在這個節點virtual hosts中的活動資訊。

3:Policymaker

  • 包含management所有許可權
  • 檢視和建立和刪除自己的virtual hosts所屬的policies和parameters資訊。

4:Monitoring

  • 包含management所有許可權
  • 羅列出所有的virtual hosts,包括不能登入的virtual hosts。
  • 檢視其他使用者的connections和channels資訊
  • 檢視節點級別的資料如clustering和memory使用情況
  • 檢視所有的virtual hosts的全域性統計資訊。

5:Administrator

  • 最高許可權
  • 可以建立和刪除virtual hosts
  • 可以檢視,建立和刪除users
  • 檢視建立permisssions
  • 關閉所有使用者的connections

專案地址

https://gitee.com/zwtgit/rabbit-mq

RabbitMQ入門案例 - Simple 簡單模式

RabbitMQ-初見

1:jdk1.8

2:構建一個maven工程

3:匯入rabbitmq的maven依賴

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.10.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-amqp</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

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

4:啟動rabbitmq-server服務

systemctl start rabbitmq-server
或者
docker start myrabbit

5:定義生產者

package com.zwt.simple;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * @author: ML李嘉圖
 * @description: Producer 簡單佇列生產者
 * @Date : 2021/3/2
 */
public class Producer {
    public static void main(String[] args) {
        // 1: 建立連線工廠
        ConnectionFactory connectionFactory = new ConnectionFactory();

        // 2: 設定連線屬性,本機的地址,預設埠5679
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");

        Connection connection = null;
        Channel channel = null;

        try {
            // 3: 從連線工廠中獲取連線
            connection = connectionFactory.newConnection("生產者");
            // 4: 從連線中獲取通道channel
            channel = connection.createChannel();
            // 5: 申明佇列queue儲存訊息
            /*
             *  如果佇列不存在,則會建立
             *  Rabbitmq不允許建立兩個相同的佇列名稱,否則會報錯。
             *
             *  @params1: queue 佇列的名稱
             *  @params2: durable 佇列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果為true,會對當前佇列加鎖,其他的通道不能訪問,並且連線自動關閉
             *  @params4: autoDelete 是否自動刪除,當最後一個消費者斷開連線之後是否自動刪除訊息。
             *  @params5: arguments 可以設定佇列附加引數,設定佇列的有效期,訊息的最大長度,佇列的訊息生命週期等等。
             * */
            channel.queueDeclare("queue1", false, false, false, null);
            // 6: 準備傳送訊息的內容
            String message = "你好,Ml李嘉圖!!!";
            // 7: 傳送訊息給中介軟體rabbitmq-server
            // @params1: 交換機exchange
            // @params2: 佇列名稱/routing
            // @params3: 屬性配置
            // @params4: 傳送訊息的內容
            channel.basicPublish("", "queue1", null, message.getBytes());
            System.out.println("訊息傳送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("傳送訊息出現異常...");
        } finally {
            // 7: 釋放連線關閉通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

6:定義消費者

package com.zwt.simple;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author: ML李嘉圖
 * @description: Producer 簡單佇列生產者
 * @Date : 2021/3/2
 */
// 簡單模式----消費者
public class Consumer {
    public static void main(String[] args) {
        // 所有的中介軟體技術都是基於tcp/ip協議基礎上構建新型的協議規範,rabbitmq遵循的是amqp
        // 協議遵循 ip port

        // 1、建立連線工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 通過連線工廠設定賬號密碼
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        // 虛擬訪問節點
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            // 2、建立連線connection
            connection = connectionFactory.newConnection("生產者");
            // 3、通過連線獲取通道channel
            channel = connection.createChannel();
            // 4、 通過建立交換機、宣告佇列,繫結關係,路由key,傳送訊息和接受訊息
            channel.basicConsume("queue1", true, new DeliverCallback() {
                public void handle(String consumerTag, Delivery message) throws IOException {
                    System.out.println("收到訊息是:" + new String(message.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                public void handle(String consumerTag) throws IOException {
                    System.out.println("接受訊息失敗了。。。。");
                }
            });
            System.out.println("開始接受訊息");
            // 進行阻斷,接受訊息不關閉
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 7、關閉通道(先關通道,再關連線)
            if(channel!=null && channel.isOpen()){
                try {
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 8、關閉連線
            if(connection!=null && connection.isOpen()){
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

7:觀察訊息的在rabbitmq-server服務中的過程

什麼是AMQP

AMQP全稱:Advanced Message Queuing Protocol(高階訊息佇列協議)。是應用層協議的一個開發標準,為面向訊息的中介軟體設計。

RabbitMQ-初見

RabbitMQ的核心組成部分

RabbitMQ-初見

核心概念:
Server:又稱Broker ,接受客戶端的連線,實現AMQP實體服務。 安裝rabbitmq-server
Connection:連線,應用程式與Broker的網路連線 TCP/IP/ 三次握手和四次揮手
Channel:網路通道,幾乎所有的操作都在Channel中進行,Channel是進行訊息讀寫的通道,客戶端可以建立對各Channel,每個Channel代表一個會話任務。
Message :訊息:服務與應用程式之間傳送的資料,由Properties和body組成,Properties可是對訊息進行修飾,比如訊息的優先順序,延遲等高階特性,Body則就是訊息體的內容。
Virtual Host 虛擬地址,用於進行邏輯隔離,最上層的訊息路由,一個虛擬主機理由可以有若干個Exhange和Queueu,同一個虛擬主機裡面不能有相同名字的Exchange
Exchange:交換機,接受訊息,根據路由鍵傳送訊息到繫結的佇列。(不具備訊息儲存的能力)
Bindings:Exchange和Queue之間的虛擬連線,binding中可以保護多個routing key.
Routing key:是一個路由規則,虛擬機器可以用它來確定如何路由一個特定訊息。
Queue:佇列:也成為Message Queue,訊息佇列,儲存訊息並將它們轉發給消費者。

RabbitMQ整體架構是什麼樣子的?

RabbitMQ-初見

RabbitMQ的執行流程

RabbitMQ-初見 RabbitMQ-初見

RabbitMQ支援訊息的模式

參考官網:https://www.rabbitmq.com/getstarted.html

簡單模式 Simple

  • 參考上面

工作模式 Work

  • 型別:無
  • 特點:分發機制

釋出訂閱模式

  • 型別:fanout
  • 特點:Fanout—釋出與訂閱模式,是一種廣播機制,它是沒有路由key的模式。

路由模式

  • 型別:direct
  • 特點:有routing-key的匹配模式

主題Topic模式

  • 型別:topic
  • 特點:模糊的routing-key的匹配模式

引數模式

  • 型別:headers
  • 特點:引數匹配模式

rabbitmq傳送訊息一定有一個交換機

RabbitMQ入門案例 - fanout模式

釋出訂閱模式

RabbitMQ-初見
  • 型別:fanout
  • 特點:Fanout—釋出與訂閱模式,是一種廣播機制,它是沒有路由key的模式。

程式碼都是與簡單模式類似,只是呼叫的函式以及引數改變了

釋出訂閱模式:大概就是我們們的交換機選定fanout模式,binding你自己需要使用的佇列,然後就可以通過這個交換機發布訊息了。

RabbitMQ入門案例 - Direct模式

RabbitMQ-初見

路由模式(Routing key)

例如:我只想發給微信使用者,不發QQ使用者,就是增加了 過濾的條件

  • 型別:direct
  • 特點:Direct模式是fanout模式上的一種疊加,增加了路由RoutingKey的模式。

RabbitMQ入門案例 - Topic模式

RabbitMQ-初見
  • 型別:topic
  • 特點:Topic模式是direct模式上的一種疊加,增加了模糊路由RoutingKey的模式。

RabbitMQ入門案例 - Work模式 - 輪詢模式(Round-Robin)

RabbitMQ-初見

當有多個消費者時,我們的訊息會被哪個消費者消費呢,我們又該如何均衡消費者消費資訊的多少呢?

主要有兩種模式:

1、輪詢模式的分發:一個消費者一條,按均分配;

2、公平分發:根據消費者的消費能力進行公平分發,處理快的處理的多,處理慢的處理的少;按勞分配;

  • 特點:該模式接收訊息是當有多個消費者接入時,訊息的分配模式是一個消費者分配一條,直至訊息消費完成;

RabbitMQ入門案例 - Work模式 - 公平分發(Fair Dispatch)

RabbitMQ-初見

RabbitMQ使用場景

解耦、削峰、非同步

同步非同步的問題(序列)

序列方式:將訂單資訊寫入資料庫成功後,傳送註冊郵件,再傳送註冊簡訊。以上三個任務全部完成後,返回給客戶端

RabbitMQ-初見
public void makeOrder(){
    // 1 :儲存訂單 
    orderService.saveOrder();
    // 2: 傳送簡訊服務
    messageService.sendSMS("order");//1-2 s
    // 3: 傳送email服務
    emailService.sendEmail("order");//1-2 s
    // 4: 傳送APP服務
    appService.sendApp("order");    
}

並行方式 非同步執行緒池

並行方式:將訂單資訊寫入資料庫成功後,傳送註冊郵件的同時,傳送註冊簡訊。以上三個任務完成後,返回給客戶端。與序列的差別是,並行的方式可以提高處理的時間 。

RabbitMQ-初見
public void makeOrder(){
    // 1 :儲存訂單 
    orderService.saveOrder();
   // 相關傳送
   relationMessage();
}
public void relationMessage(){
    // 非同步
     theadpool.submit(new Callable<Object>{
         public Object call(){
             // 2: 傳送簡訊服務  
             messageService.sendSMS("order");
         }
     })
    // 非同步
     theadpool.submit(new Callable<Object>{
         public Object call(){
              // 3: 傳送email服務
            emailService.sendEmail("order");
         }
     })
      // 非同步
     theadpool.submit(new Callable<Object>{
         public Object call(){
             // 4: 傳送簡訊服務
             appService.sendApp("order");
         }
     })
      // 非同步
         theadpool.submit(new Callable<Object>{
         public Object call(){
             // 4: 傳送簡訊服務
             appService.sendApp("order");
         }
     })
}

存在問題:

1:耦合度高

2:需要自己寫執行緒池自己維護成本太高

3:出現了訊息可能會丟失,需要你自己做訊息補償

4:如何保證訊息的可靠性你自己寫

5:如果伺服器承載不了,你需要自己去寫高可用

非同步訊息佇列的方式

RabbitMQ-初見

好處

1:完全解耦,用MQ建立橋接

2:有獨立的執行緒池和執行模型

3:出現了訊息可能會丟失,MQ有持久化功能

4:如何保證訊息的可靠性,死信佇列和訊息轉移的等

5:如果伺服器承載不了,你需要自己去寫高可用,HA映象模型高可用。

按照以上約定,使用者的響應時間相當於是訂單資訊寫入資料庫的時間,也就是50毫秒。

註冊郵件,傳送簡訊寫入訊息佇列後,直接返回,因此寫入訊息佇列的速度很快,基本可以忽略,因此使用者的響應時間可能是50毫秒。

因此架構改變後,系統的吞吐量提高到每秒20 QPS。比序列提高了3倍,比並行提高了兩倍

public void makeOrder(){
    // 1 :儲存訂單 
    orderService.saveOrder();   
    rabbitTemplate.convertSend("ex","2","訊息內容");
}

高內聚,低耦合

流量削峰

要對流量進行削峰,最容易想到的解決方案就是用訊息佇列來緩衝瞬時流量,

把同步的直接呼叫轉換成非同步的間接推送,中間通過一個佇列在一端承接瞬時的流量洪峰,

在另一端平滑地將訊息推送出去。

訊息佇列中介軟體主要解決應用耦合,非同步訊息, 流量削鋒等問題。

常用訊息佇列系統:目前在生產環境,使用較多的訊息佇列有 ActiveMQ、RabbitMQ、 ZeroMQ、Kafka、MetaMQ、RocketMQ 等。

在這裡,訊息佇列就像“水庫”一樣,攔蓄上游的洪水,削減進入下游河道的洪峰流量,從而達到減免洪水災害的目的。

針對秒殺場景還有一種方法,就是對請求進行分層過濾,從而過濾掉一些無效的請求。 分層過濾其實就是採用“漏斗”式設計來處理請求的。

還有一些場景(後面有文章補充介紹):

分散式事務的可靠消費和可靠生產
索引、快取、靜態化處理的資料同步
流量監控
日誌監控(ELK)
下單、訂單分發、搶票

RabbitMQ-SpringBoot案例 -fanout模式

在pom.xml中引入依賴

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

在application.yml進行配置

# 服務埠
server:
  port: 8080
# 配置rabbitmq服務
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 47.104.141.27
    port: 5672

定義訂單的生產者

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
 * @author: ML
 * @description: OrderService
 * @Date : 2021/3/4
 */
@Component
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    // 1: 定義交換機
    private String exchangeName = "fanout_order_exchange";
    // 2: 路由key
    private String routeKey = "";
    public void makeOrder(Long userId, Long productId, int num) {
        // 1: 模擬使用者下單
        String orderNumer = UUID.randomUUID().toString();
        // 2: 根據商品id productId 去查詢商品的庫存
        // int numstore = productSerivce.getProductNum(productId);
        // 3:判斷庫存是否充足
        // if(num >  numstore ){ return  "商品庫存不足..."; }
        // 4: 下單邏輯
        // orderService.saveOrder(order);
        // 5: 下單成功要扣減庫存
        // 6: 下單完成以後
        System.out.println("使用者 " + userId + ",訂單編號是:" + orderNumer);
        // 傳送訂單資訊給RabbitMQ fanout
        rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer);
    }
}

繫結關係

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @Author : ML
 * @CreateTime : 2021/9/3
 * @Description :
 **/
@Configuration
public class DirectRabbitConfig {
    //佇列 起名:TestDirectQueue
    @Bean
    public Queue emailQueue() {
        // durable:是否持久化,預設是false,持久化佇列:會被儲存在磁碟上,當訊息代理重啟時仍然存在,暫存佇列:當前連線有效
        // exclusive:預設也是false,只能被當前建立的連線使用,而且當連線關閉後佇列即被刪除。此參考優先順序高於durable
        // autoDelete:是否自動刪除,當沒有生產者或者消費者使用此佇列,該佇列會自動刪除。
        //   return new Queue("TestDirectQueue",true,true,false);
        //一般設定一下佇列的持久化就好,其餘兩個就是預設false
        return new Queue("email.fanout.queue", true);
    }
    @Bean
    public Queue smsQueue() {
        // durable:是否持久化,預設是false,持久化佇列:會被儲存在磁碟上,當訊息代理重啟時仍然存在,暫存佇列:當前連線有效
        // exclusive:預設也是false,只能被當前建立的連線使用,而且當連線關閉後佇列即被刪除。此參考優先順序高於durable
        // autoDelete:是否自動刪除,當沒有生產者或者消費者使用此佇列,該佇列會自動刪除。
        //   return new Queue("TestDirectQueue",true,true,false);
        //一般設定一下佇列的持久化就好,其餘兩個就是預設false
        return new Queue("sms.fanout.queue", true);
    }
    @Bean
    public Queue weixinQueue() {
        // durable:是否持久化,預設是false,持久化佇列:會被儲存在磁碟上,當訊息代理重啟時仍然存在,暫存佇列:當前連線有效
        // exclusive:預設也是false,只能被當前建立的連線使用,而且當連線關閉後佇列即被刪除。此參考優先順序高於durable
        // autoDelete:是否自動刪除,當沒有生產者或者消費者使用此佇列,該佇列會自動刪除。
        //   return new Queue("TestDirectQueue",true,true,false);
        //一般設定一下佇列的持久化就好,其餘兩個就是預設false
        return new Queue("weixin.fanout.queue", true);
    }
    //Direct交換機 起名:TestDirectExchange
    @Bean
    public DirectExchange fanoutOrderExchange() {
        //  return new DirectExchange("TestDirectExchange",true,true);
        return new DirectExchange("fanout_order_exchange", true, false);
    }
    //繫結  將佇列和交換機繫結, 並設定用於匹配鍵:TestDirectRouting
    @Bean
    public Binding bindingDirect1() {
        return BindingBuilder.bind(weixinQueue()).to(fanoutOrderExchange()).with("");
    }
    @Bean
    public Binding bindingDirect2() {
        return BindingBuilder.bind(smsQueue()).to(fanoutOrderExchange()).with("");
    }
    @Bean
    public Binding bindingDirect3() {
        return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange()).with("");
    }
}

測試

import com.xuexiangban.rabbitmq.springbootrabbitmqfanoutproducer.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootRabbitmqFanoutProducerApplicationTests {
    @Autowired
    OrderService orderService;
    @Test
    public void contextLoads() throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            Long userId = 100L + i;
            Long productId = 10001L + i;
            int num = 10;
            orderService.makeOrder(userId, productId, num);
        }
    }
}

定義消費者

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其實就是用來確定佇列和交換機繫結關係
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是佇列名字,這個名字你可以自定隨便定義。
        value = @Queue(value = "email.fanout.queue",autoDelete = "false"),
        // order.fanout 交換機的名字 必須和生產者保持一致
        exchange = @Exchange(value = "fanout_order_exchange",
                // 這裡是確定的rabbitmq模式是:fanout 是以廣播模式 、 釋出訂閱模式
                type = ExchangeTypes.FANOUT)
))
@Component
public class EmailService {
    // @RabbitHandler 代表此方法是一個訊息接收的方法。該不要有返回值
    @RabbitHandler
    public void messagerevice(String message){
        // 此處省略發郵件的邏輯
        System.out.println("email-------------->" + message);
    }
}

消費者 - 簡訊服務

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其實就是用來確定佇列和交換機繫結關係
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是佇列名字,這個名字你可以自定隨便定義。
        value = @Queue(value = "sms.fanout.queue",autoDelete = "false"),
        // order.fanout 交換機的名字 必須和生產者保持一致
        exchange = @Exchange(value = "fanout_order_exchange",
                // 這裡是確定的rabbitmq模式是:fanout 是以廣播模式 、 釋出訂閱模式
                type = ExchangeTypes.FANOUT)
))
@Component
public class SMSService {
    // @RabbitHandler 代表此方法是一個訊息接收的方法。該不要有返回值
    @RabbitHandler
    public void messagerevice(String message){
        // 此處省略發郵件的邏輯
        System.out.println("sms-------------->" + message);
    }
}

消費者 - XX服務

啟動服務SpringbootRabbitmqFanoutConsumerApplication,檢視效果

程式碼 + @EnableRabbit

RabbitMQ-SpringBoot案例 -direct模式

//消費者 - XX服務
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其實就是用來確定佇列和交換機繫結關係
@RabbitListener(bindings =@QueueBinding(
        // email.fanout.queue 是佇列名字,這個名字你可以自定隨便定義。
        value = @Queue(value = "email.fanout.queue",autoDelete = "false"),
        // order.fanout 交換機的名字 必須和生產者保持一致
        exchange = @Exchange(value = "fanout_order_exchange",
                // 這裡是確定的rabbitmq模式是:fanout 是以廣播模式 、 釋出訂閱模式
                type = ExchangeTypes.FANOUT)
))
@Component
public class EmailService {
    // @RabbitHandler 代表此方法是一個訊息接收的方法。該不要有返回值
    @RabbitHandler
    public void messagerevice(String message){
        // 此處省略發郵件的邏輯
        System.out.println("email-------------->" + message);
    }
}

RabbitMQ-SpringBoot案例 -topic模式

程式碼其實都是類似的,主要是理解原理。

RabbitMQ-初見

相關文章