RabbitMQ消息中間件技術精講
導航:
- RabbitMQ消息中間件技術精講
- 一. 主流消息中間件介紹
- 二. RabbitMQ核心概念及AMQP協議
- 三. RabbitMQ的安裝與使用
- 3.1 安裝
- 3.2 安裝過程
- 3.3 安裝文檔:
- 3.4 命令行與管控臺--基礎到高級操作
- 3.5 RabbitMQ可視化界面
- 3.6 急速入門-消息生產與消費
- 3.7 實戰步驟:
- 3.8 Exchange交換機
- 3.9 交換機屬性
- 3.10 Direct Exchange
- 3.11 Topic Exchange
- 3.12 Fanout Exchange
- 3.12 Binding - 綁定
- 3.13 Queue-- 消息隊列
- 3.14 Message--消息
- 3.15 Virtual host-虛擬主機
- 四. RabbitMQ的高級特性
- 4.1 本章導航
- 4.2 消息如何保障100%的投遞成功?
- 4.3 冪等性概念
- 4.4 Confirm 確認消息
- 4.5 Return消息機制
- 4.6 消費端自定義監聽
- 4.7 消費端限流
- 4.8 消息ACK與重回隊列
- 4.9 TTL隊列/消息
- 4.10 死信隊列(這個消息沒有被任何人消費,它就變成了死信隊列)
- 五. RabbitMQ高級整合應用
- 5.1 本章導航
- 5.2 RabbitMQ整合Spring AMQP實戰
- 5.3 RabbitAdmin
- 5.4 Spring的整合
- 5.5 RabbitAdmin
- 5.6 SpringAMQP聲明
- 5.7 消息模板 RabbitTemplate
- 5.8 簡單消息監聽容器:SimpleMessageListenerContainer
- 5.9 消息監聽適配器:MessageListenerAdapter
- 5.10 MessageConverter消息轉換器
- 5.11 SpringBoot整合配置詳解
- 5.12 SpringBoot整合配置詳解2
- 5.13 Spring Cloud Stream 整合
- 5.14 Spring Cloud Stream 實戰
- 5.15 本章小結:
- 六. Rabbit的集羣架構
- 七. 互聯網SET化架構
- 七. 一線大廠的MQ組件實現思路和架構設計方案
- 7.1 本章導航
- 7.2
- 7.3 消息發送模式- 迅速消息發送
- 7.4 消息發送模式- 確認消息發送
- 7.5 消息發送模式- 批量消息發送
- 7.6 消息發送模式- 延遲消息發送
- 7.7 消息發送模式- 順序消息(一)
- 7.8 消息發送模式- 事務消息發送(一)
- 7.9 消息發送模式- 事務消息發送(二)
- 7.10 消息發送模式- 事務消息發送(五)
- 7.11 消息的冪等性的必要性
- 7.12 消息的冪等性的設計
- 7.13 本章小結
- 八. RabbitMQ課程總結
一. 主流消息中間件介紹
1.1 ActiveMQ
- ActiveMQ是Apache出品,最流行的,能力強勁的開源消息總線,並且它是一個完全支持JMS規範的消息中間件。
- 其豐富的API,多種集羣構建模式使得他成爲業界老牌消息中間件,在中小型企業中應用廣泛!
- MQ衡量指標:服務性能、數據存儲、集羣架構;
優點:老牌,穩定性高,成熟度高,集羣架構模式好(Zookeeper),對性能要求不高的可以使用,它只需要引入依賴即可,SpringBoot自身集成了。
缺點:性能低,延遲高;
- 集羣架構圖:
1.2 Kafka
- Kafka是LinkedLn開源的分佈式發佈-訂閱消息系統,目前歸屬於Apache頂級項目。Kafka主要特點是基於Pull的模式來處理消息消費,追求高吞吐量,一開始的目的就是用於日誌收集和傳輸。0.8版本開始支持複製,不支持事務,對消息的重複、丟失、錯誤沒有嚴格要求,適合產生大量數據的互聯網服務的數據收集業務。
只要有足夠大的內存,就能承擔很大的數據傳輸;
- Kafka集羣模式:(集羣複製,數據就不容易丟失)
1.3 RocketMQ
-
RocketMQ是阿里開源的消息中間件,目前也已經孵化爲Apache頂級項目,它是純java開發,具有高吞吐量、高可用性、適合大規模分佈式系統應用的特點。RocketMQ思路起源於Kafka,它對消息的可靠傳輸以及事務性做了優化,目前在阿里集團被廣泛應用於交易、充值、流計算、消息推送、日誌流式處理、binglog分發等場景。
-
集羣拓撲:(替換了Zookeeper,因爲它性能低,換成了Name Server)
它的分佈式事務等很多功能是需要商業版纔能有的,需要收費。它高性能,可靠性,支持水平擴展,它有一個最大的問題:商業收費。
1.4 RabbitMQ
- RabbitMQ是使用Erlang語言開發的開源消息隊列系統個,基於APMQ協議來實現。AMQP的主要特徵是面向消息、隊列、路由(包括點對點和發佈/訂閱)、可靠性、安全。AMQP協議更多用在企業系統內,對數據一致性、穩定性和可靠性要求很高的場景,對性能和吞吐量的要求還在其次。
- 集羣架構圖:
能保障數據不丟失,可做高可用,負載均衡,性能也很高,建議使用。
二. RabbitMQ核心概念及AMQP協議
2.1 本章導航
- 互聯網大廠爲什麼選擇RabbitMQ
- RabbitMQ的高性能之道是如何做到的?
- 什麼是AMQP高級協議?
- AMQP核心概念是什麼?
- RabbitMQ整體架構模型是什麼樣子的?
- RabbitMQ消息是如何流轉的?
- RabbitMQ安裝與使用?
- 命令行與管控臺?
- RabbitMQ消息生產與消費?
- RabbitMQ交換機詳解?
- RabbitMQ隊列、綁定、虛擬主機、消息
2.2 初始RabbitMQ
-
RabbitMQ是一個開源的消息代理和隊列服務器,用來通過普通協議在完全不同的應用之間共享數據,RabbitMQ是使用Erlang語言來編寫的,並且RabbitMQ是基於AMQP協議的;
-
很多大廠如滴滴、美團、頭條等,因爲:
- 開源、性能優秀、穩定性保障
- 提供可靠性消息投遞模式(confirm)、返回模式(return)
- 與SpringAMQP完美的整合、API豐富。
- 集羣模式豐富、表達式配置、HA模式、鏡像隊列模型
- 保證數據不丟失的前提做到高可靠性、可用性。
-
RabbitMQ高性能的原因?
- Erlang語言在交換機的交互方面性能優秀的(Erlang語言最初在於交換機領域的架構模式,這樣使得RabbitMQ在Broker之間進行數據交互的性能是非常優秀的)
- Erlang有着和原生Socket一樣的延遲
-
什麼是AMQP高級消息 隊列協議?
- AMQP定義:具有現代特徵的二進制協議。是一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。
-
AMQP協議模型:
Publisher 推送消息前先與Server建立連接,找到Virtual host,然後將消息推送至Exchange交換機。而交換機與Message Queue有綁定關係(一個交換機相當於一個獨立的虛擬機,而這個虛擬機內的各種獨立的應用就相當於一個Queue,這個Queue與交換機綁定),Consumer通過綁定的對隊列,而交換機也綁定了隊列。發送者將消息發送給交換機,這樣就能完成消息的推送了。
2.3 AMQP核心概念
- Server:又稱Broker,接收客戶端的連接,實現AMQP實體服務。
- Connection:連接,應用程序與Broker的網絡連接。
- Channel:網絡信道,幾乎所有的操作都在Channel中進行,Channel是進行消息讀寫的通道。客戶端可建立多個Channel,每個Channel代表一個會話任務。(它是一個核心的概念)
- Message:消息,服務器和應用程序之間傳送的數據,由Properties和Body組成。Properties可以對消息進行修飾,比如消息的優先級、延遲等高級特性;Body則就是消息體內容。
- Virtual host: 虛擬地址,用於進行邏輯隔離,最上層的消息路由。一個Virtual Host裏面可以有若干個Exchange和Queue,同一個Virtual Host裏面不能有相同名稱的Exchange或Queue
- Exchange:交換機,接收消息,根據路由鍵轉發消息到綁定的隊列。
- Binding:Exchange和Queue之間的虛擬連接,binding中可以包含routing key
- Routing Key:一個路由規則,虛擬機可用它來確定如何路由一個特定消息。
- Queue:也稱爲Message Queue,消息隊列,保存消息並將它們轉發給消費者。
2.4 RabbitMQ的整體架構?
2.5 消息流轉圖:
生產者將消息發送至交換機,交換機將消息發送至指定的隊列,而消費者則通過綁定的隊列拿到此消息。
三. RabbitMQ的安裝與使用
3.1 安裝
- 先下載Erlang,然後下載RabbitMQ
- 官網地址:http://www.rabbitmq.com/
- 提前準備:安裝Linux必要依賴包
- 下載RabbitMQ必須安裝包
- 配置文件修改
下載的版本不一定找最新的版本,要找最穩定的(版本的升級會伴隨一定的風險)。同時要照顧到整體架構,比如其他的是否支持最新版本等,在升級上也要注意是否值得升級。
3.2 安裝過程
- 先查看ErlangVersion: Erlang與RabbitMQ版本對照
rpm一鍵安裝是最簡單的安裝方式,初學者可以使用此方式
- 啓動Linux系統:
3.下載相關的包到相關的路徑:
erlang要先安裝;
- 輸入命令安裝Erlang:
rmp -ivh erlang-18.3-1.e17.centos.x86_64.rpm
- 輸入命令安裝RabbitMQ:
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
- 安裝時出現如下問題,說明我們要先安裝密鑰:
- 輸入密鑰安裝命令:
rpm -ivh socat-1.7.3.2-5.e17.lux.x86.rpm
- 重新輸入安裝RabbitMQ命令:
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
- 配置RabbitMQ:
- 進入目錄並編輯:
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
- 部分配置內容截圖如下:
- 進入目錄並編輯:
在配置文件中即可配置端口號,內存等操作。後面細講,這裏先做到能使用即可。
3.3 安裝文檔:
- 安裝前環境準備:
yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
- 下載:
wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.e17.centos.x86_64.rpm wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.e17.lux.x86_64.rpm wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm
- 配置文件:
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
比如修改密碼、配置等等,例如:loopback_users中的<<“guest”>>,只保留guest
- 服務啓動和停止:
- 啓動:
rabbitmq-server start &
- 停止:
rabbitmqctl app_stop
- 啓動:
- 管理插件:
rabbitmq-plugins enable rabbitmq_management
- 訪問地址: ip:15672/
3.4 命令行與管控臺–基礎到高級操作
- rabbitmqctl stop_app: 關閉應用
- rabbitmqctl start_app: 啓動應用
- rabbitmqctl status: 節點狀態
- rabbitmqctl add_user username password: 添加用戶
- rabbitmqctl list_users:列出所有用戶
- rabbitmqctl delete_user username:刪除用戶
- rabbitmqctl clear_permissions -p vhostpath username:清除用戶權限
- rabbitmqctl list_user_permissions username: 列出用戶權限
- rabbitmqctl change_password username newpassword:修改密碼
- rabbitmqctl set_permissions -p vhostpath username “."".”".*": 設置用戶權限
- rabbitmqctl add_vhost vhostpath:創建虛擬主機
- rabbitmqctl list_vhosts: 列出所有虛擬主機
- rabbitmqctl list_permissions -p vhostpath:列出虛擬主機上所有權限
- rabbitmqctl list_permissions -p vhostpath: 列出虛擬主機上所有權限
- rabbitmqctl list_queues:查看所有隊列信息
- rabbitmqctl -p vhostpath purge_queue blue:清除隊列裏的消息
- rabbitmqctl reset: 移除所有數據,要在rabbitmqctl stop_app之後使用
- rabbitmqctl join_cluster [–ram]: 組成集羣命令
- rabbitmqctl cluster_status:查看集羣狀態
- rabbitmqctl change_cluster_node_type disc | ram 修改集羣節點的存儲形式
- rabbitmqctl forget_cluster_node [–offline] 忘記節點(摘除節點)
集羣配置失敗,故障轉移等情況下可以將啓動失敗的節點給移除掉。它可以在不啓動的情況下對節點的摘除
- rabbitmqctl rename_cluster_node oldnode1 newnode1 [oldnode2] [newnode2…] (修改節點名稱)
命令行的操作能做的,可視化界面也可以做的。
3.5 RabbitMQ可視化界面
-
右上角:
-
可視化界面:
-
Type類型:
- direct: 直連的方式
- fanout:廣播的方式
- headers:請求頭方式
- topic: 主題模式
-
在Admin中可以配置賬戶以及賬戶的權限(如操作虛擬主機的權限)。(右邊有選擇框)
-
導入導出遷移備份:
3.6 急速入門-消息生產與消費
- ConnectionFactory:獲取連接工廠
- Connection: 一個連接
- Channel:數據通信信道,可發送和接收消息。
- Queue:具體的消息存儲隊列
- Producer&Consumer生產和消費者
3.7 實戰步驟:
- 創建SpringBoot項目,並引入相關的依賴使程序跑起來
- 引入RabbitMQ依賴:
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>3.6.5</version> </dependency>
- 創建一個生產者
public class Consumer{ public static void main(String[] args) throws Exception{ // 1. 創建一個ConnectionFactory, 並進行配置 ConnectionFactory connectionFactory=new ConnectionFactory(); connectionFactory.setHost("192.168.11.76"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2. 通過連接工廠創建連接 Connection connection =connectionFactory.newConnection(); //3. 通過connection創建一個Channel Channel channel=connection.craeteChannel(); //4. 通過Channel發送數據 for(int i=0;i<5;i++){ String msg="Hello RabbitMQ!"; channel.basicPublish("","test001",null,msg.getBytes()); } //5. 記得要關閉相關的連接 channel.close(); connection.close(); } }
- 創建一個消費者
public class Consumer{ //1. 創建一個ConnectionFactory,並進行配置 ConnectionFactory connectionFactory=new ConnectionFactory(); connectionFactory.setHost("192.168.11.76"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2. 通過連接工廠創建連接 Connection connection=connectionFactory.newConnection(); //3. 通過connection創建一個Channel Channel channel = connection.createChannel(); //4. 聲明(創建)一個隊列 String queueName="test001"; channel.queueDeclare("test001",true,false,false,null); //5. 創建消費者 QueueingConsumer queueingConsumer=new QueueingConsumer(channel); //6. 設置Channel channel.basicConsume(queueName,true,queueingConsumer); //7. 獲取消息 while(true){ Delivery delivery=queueingConsumer.nextDelivery(); String msg=new String(delivery.getBod()); //Evelope envelope=delivery.getEnvelope(); System.out.println("消費端:"+msg); }}}
我們在發送消息的過程中必須指定一個Exchange,如果指定的Exchange爲空的話,它會使用默認的Exchange;
3.8 Exchange交換機
- Exchange: 接收消息,並根據路由鍵轉發消息所綁定的隊列。
- 圖示:
3.9 交換機屬性
- Name:交換機名稱
- Type:交換機類型:direct、topic、fanout、headers
- Durability:是否需要持久化,true爲持久化。
- Auto Delete:當最後一個綁定到Exchange上的隊列刪除後,自動刪除該Exchange。
- Internal:當前Exchange是否用於RabbitMQ內部使用,默認爲false;(一般爲false,因爲這個主要用於自己熟悉Erlang語言,並自己構建交換機擴展自定義插件等使用。)
- Arguments:擴展參數,用於擴展AMQP協議自制定化使用。
3.10 Direct Exchange
- 所有發送到Direct Exchange的消息被轉發到RouteKey中指定的Queue。
注意:Direct模式可以使用RabbitMQ自帶的Exchange:default Exchange,所以不需要將Exchange進行任何綁定(binding)操作,消息傳遞時,RouteKey必須完全匹配纔會被隊列接收,否則該消息會被拋棄。
- 是否支持重連,如圖:
3.11 Topic Exchange
- 所有發送到Topic Exchange的消息被轉發到所有關心RouteKey中指定Topic的Queue上
- Exchange將RouteKey和某Topic進行模糊匹配;此時隊列需要綁定一個Topic;
- 如圖所示:
符合條件的,被匹配到的都可以被接收到消息。
3.12 Fanout Exchange
- 不處理路由鍵,只需要簡單的將隊列綁定到交換機上。
- 發送到交換機的消息都會被轉發到與該交換機綁定的所有隊列上。
- Fanout交換機轉發消息是最快的。
- 如圖所示:
還有一些其他的但不是很常用,這裏不贅述了;
3.12 Binding - 綁定
- Exchange 和Exchange、Queue之間的連接關係
- Binding中可以包含RoutingKey或者參數
3.13 Queue-- 消息隊列
- 消息隊列:實際存儲消息數據
- Durability:是否持久化,Durable:是,Transient:否
- Auto delete:如選Yes,代表當最後一個監聽被移除之後,該Queue會自動被刪除。
3.14 Message–消息
-
服務器和應用程序之間傳送的數據;
-
本質上就是一段數據,由Properties和Payload(Body)組成
-
常用屬性:delivery mode、headers(自定義屬性)
-
其他屬性:
- content_type、content_encoding、priority
- correlation_id、reply_to、expiration、message_id
- timestamp、type、user_id、app_id、cluster_id
-
生產者自定義Header代碼示例:
public class Procuder{ public static void main(String[] args) throws Exception{ // 1. 創建一個ConnectionFactory ,並進行配置 ConnectionFactory connectionFactory=new ConnectionFactory(); connectionFactory.setHost("192.168.11.76"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2. 通過連接工廠連接 Connection connection=connectionFactory.newConnection(); //3. 通過connection創建一個Channel Channel channel=connection.createChannel(); Map<String,Object> headers=new HashMap<>(); headers.put("my1","111"); headers.put("my2","222"); AMQP.BasicProperties properties=new AMQP.BasicProperties.Builder() .deliveryMode(2) .contentEncoding("UTF-8") .expiration("10000") .headers(headers) .build(); //4. 通過Channel發送數據 channel.basicPublish("","test001",null,msg.getBytes()); }
-
消費者自定義獲取header裏信息代碼示例:
public class Consumer{ //1. 創建一個ConnectionFactory,並進行配置 ConnectionFactory connectionFactory=new ConnectionFactory(); connectionFactory.setHost("192.168.11.76"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2. 通過連接工廠創建連接 Connection connection=connectionFactory.newConnection(); //3. 通過connection創建一個Channel Channel channel = connection.createChannel(); //4. 聲明(創建)一個隊列 String queueName="test001"; channel.queueDeclare("test001",true,false,false,null); //5. 創建消費者 QueueingConsumer queueingConsumer=new QueueingConsumer(channel); //6. 設置Channel channel.basicConsume(queueName,true,queueingConsumer); //7. 獲取消息 while(true){ Delivery delivery=queueingConsumer.nextDelivery(); String msg=new String(delivery.getBod()); //Evelope envelope=delivery.getEnvelope(); System.out.println("消費端:"+msg); }}}
3.15 Virtual host-虛擬主機
- 虛擬地址,用於進行邏輯隔離,最上層的消息路由。
- 一個Virtual Host裏面可以有若干個Exchange和Queue
- 同一個Virtual Host裏面不能有相同名稱的Exchange或Queue;
本章小結: RabbitMQ的概念、安裝與使用、管控臺操作,結合RabbitMQ的特性、Exchange、Queue、Binding、RoutingKey、Message進行核心API的講解,通過本章的學習,希望大家對RabbitMQ有一個初步的認知!
四. RabbitMQ的高級特性
4.1 本章導航
- 消息如何保障100%投遞成功?
- 冪等性概念詳解
- 在海量訂單產生的業務高峯期,如何避免消息的重複消費問題?
- Confirm確認消息、Return返回消息。
- 自定義消費者
- 消息的ACK域重回隊列
- 消息的限流
- TTL消息
- 死信隊列
4.2 消息如何保障100%的投遞成功?
-
什麼是生產端的可靠性投遞?
- 保障消息的成功發出
- 保障MQ節點的成功接收
- 發送端收到MQ節點(Broker)確認應答
- 完善的消息進行補償機制
-
生產端:
- 消息落庫,對消息狀態進行打標
這裏的打標,我們可以在消息將要發出的時候,將發出消息的狀態修改,當確認收到了消息之後再修改狀態。做一個定期輪詢檢查是否漏發,如果有則重新發送。
簡單來說,就是先將要發送消息的訂單入庫,然後再發送消息,如果消息未發送成功則進行補償重發(延遲檢查如五分鐘後),最好是不做事務(影響性能),少入DB。 - 消息的延遲投遞,做二次確認,回調檢查
- 消息落庫,對消息狀態進行打標
4.3 冪等性概念
-
冪等性是什麼?
- 我們可以借鑑數據庫的樂觀鎖機制:
- 比如我們執行一條更新庫存的SQL語句:
- UPDATE T_REPS SET COUNT=COUNT-1,VERSION=VERSION+1 WHERE VERSION=1
-
消費端-冪等性保障
- 在海量訂單產生的業務高峯期,如何避免消息的重複消費問題?
- 消費端實現冪等性,就意味着,我們的消息永遠不會消費多次,即使我們收到了多條哦一樣的消息
- 業界主流的冪等性操作:
- 唯一ID+指紋嗎機制,利用數據庫主鍵去重。
- 唯一ID+指紋碼機制,利用數據庫主鍵去重
- SELECT COUNT(1) FROM T_ORDER WHERE ID= 唯一ID+指紋碼
- 好處:實現簡單
- 壞處:高併發下有數據庫寫入的性能瓶頸
- 解決方案:跟進ID進行分庫分表進行算法路由
- 利用Redis的原子性去實現。
- 使用Redis進行冪等,需要考慮的問題
- 第一:我們是否要進行數據落庫,如果落庫的話,關鍵解決的問題是數據庫和緩存如何做到原子性?
在業務邏輯中,如果使用了數據庫和Redis,在進行數據的流轉中,Redis和數據庫的事務不是一樣的,要考慮到如何使事務一致性,同時成功同時失敗等問題。
- 第二:如果不進行落庫,那麼都存儲到緩存中,如何設置定時同步的策略?
- 唯一ID+指紋嗎機制,利用數據庫主鍵去重。
- 在海量訂單產生的業務高峯期,如何避免消息的重複消費問題?
4.4 Confirm 確認消息
- 理解Confirm消息確認機制:
- 消息的確認,是指生產者投遞消息後,如果Broker收到消息,則會給我們生產者一個應答。
- 生產者接收應答,用來確定這條消息是否正常的發送到Broker,這種方式也是消息的可靠性投遞的核心保障!
- 確認機制流程圖:
- 如何實現Confirm確認消息?
- 第一步:在channel上開啓確認模式:channel.confirmSelect()
- 第二步:在Channel上添加監聽:addConfirmListener,監聽成功和失敗的返回結果,根據具體的結果對消息進行重新發送、或記錄日誌等後續處理!
- 代碼實現如下: 生產端
public class Producer{ public static void main(String[] args){ //1. 創建ConnectionFactory ConnectionFactory connectionFactory=new ConnectionFactory(); connectionFactory.setHost("192.168.11.76") connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); // 2. 獲取Connection Connection connection=ConnectionFactory.newConnection(); // 3. 通過Connection創建一個新的Channel Channel channel=connection.createChannel(); // 4. 指定我們的消息投遞模式:消息的確認模式 channel.confirmSelect(); String exchangeName="test_confirm_exchange"; String routingKey="confirm.save"; // 5. 發送一條消息 String msg="Hello RabbitMQ Send confirm message!"; channel.basicPublish(exchangeName,routingKey,null,msg.getBytes()); // 6. 添加一個確認監聽 channel.addConfirmListener(new ConfirmListener(){ @Override public void handleNack(long deliveryTag,boolean multiple) throws IOException{ System.err.println("-------no ack!-------"); } @Override public void handleAck(long deliveryTag,boolean multiple) throws IOException{ System.err.println("---------ack!----------"); } }) } }
- 消費端:
public class Producer{ public static void main(String[] args)throws Exception{ // 1. 創建ConnectionFactory ConnectionFactory connectionFactory=new ConnectionFactory(); connectionFactory.setHost("192.168.11.76"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2. 獲取connection Connection connection =connectionFactory.newConnection(); //3. 通過Conneciton創建一個新的Channle String exchangeName="test_confirm_exchange"; String routingKey="confirm.#"; String queueName="test_confirm_queue"; // 4. 聲明交換機和隊列 然後進行綁定設置,最後制定路由key channel.exchangeDeclare(exchangeName,"topic",true); channel.queueDeclare(queueName,true,false,false,null); channel.queueBind(queueName,exchangeName,routingKey); // 5.創建消費者 QueueingConsumer queueingConsumer=new QueueingConsumer(channel); channel.basicConsume(queueName,true,queueingConsumer); while(true){ Delivery delivery=queueingConsumer.nextDelivery(); String msg=new String(delivery.getBody()); System.err.println("消費端"+msg); }} }
4.5 Return消息機制
-
Return Lis tener 用於處理一些不可路由的消息!
-
我們的消息生產者,通過指定一個Exchange和RoutingKey,把消息送達到某一個隊列中去,然後我們的消費者監聽隊列,進行消費處理操作!
-
但是在某些情況下,如果我們在發送消息的時候,當前的Exchange不存在或者指定的路由key路由不到,這個時候如果我們需要監聽這種不可達的消息,就要使用Return Listener!
-
在基礎API中有一個關鍵的配置項:
-
Mandatory:如果爲true,則監聽器會接收到路由不可達的消息,然後進行後續處理,如果爲false,那麼broker端自動刪除該消息!
默認爲false,當我們使用Return 消息機制的時候,我們需要將它設置爲true;
- 接收端接收到了消息:
4.6 消費端自定義監聽
- 自定義監聽的原因:
- 我們一般就是在代碼中編寫while循環,進行consumer.nextDelivery方法進行獲取下一條消息,然後進行消費處理!
- 但是我們使用自定義的Consumer更加的方便,解耦性更加的強,也是在實際工作中最常用的使用方式!
- 非常簡單,消費者只需要繼承DefaultConsumer類,然後重寫handleDelivery方法即可;
繼承DefaultConsumer的此類被寫出後,需要進行綁定。(在交換機綁定時綁定自定義的Consumer);
4.7 消費端限流
- 假設一個場景,首先,我們RabbitMQ服務器上有上萬條未處理的消息,我們隨便打開一個消費者客戶端,會出現下面情況:
- 巨量的消息瞬間全部推送過來,但是我們單個客戶端無法同時處理這麼多數據;
- RabbitMQ提供了一種qos(服務質量保證)功能,即在非自動確認消息的前提下,如果一定數目的消息(通過基於consume或者channel設置Qos的值)未被確認前,不進行消費新的消息。
- void BasicQos(uint prefetchSize,ushort prefetchCount,bool global);
- prefetchSize:0 #這裏爲0表示不限制
- prefetchCount: 會告訴RabbitMQ不要同時給一個消費者推送多於N個消息,即一旦有N個消息還沒有ack,則該consumer將block掉,直到有消息ack; (prefetchCount等於1即可)
- global:true\false 是否將上面設置應用於channel
- 簡單來說,就是上面限制是channel級別的還是consumer級別;
4.8 消息ACK與重回隊列
- 消費端的手工ACK和NACK
- 消費端進行消費的時候,如果由於業務異常我們可以進行日誌的記錄,然後進行補償!
- 如果由於服務器宕機等嚴重問題,那我們就需要手工進行ACK保障消費端消費成功!
- 消費端的重回隊列
- 消費端重回隊列是爲了對沒有處理成功的消息,把消息重新會遞給Broker!
- 一般我們在實際應用中,都會關閉重回隊列,也就是設置爲False;
4.9 TTL隊列/消息
-
TTL:
- TTL是Time To Live的縮寫,也就是生存時間
- RabbitMQ支持消息的過期時間,在消息發送時可以進行指定。
- RabbitMQ支持隊列的過期時間,從消息入隊列開始計算,只要超過了隊列的超時時間配置,那麼消息會自動的刪除。
-
圖示:
這裏可以配置隊列的相關參數配置;
交換機與交換機之間也可以進行綁定
4.10 死信隊列(這個消息沒有被任何人消費,它就變成了死信隊列)
- 利用DLX,當消息在一個隊列中變成死信(dead message)之後,它能被重新publish到另一個Exchange,這個Exchange就是DLX;
- 消息變成死信有以下幾種情況:
- 消息被拒絕(basic.reject/basic.nack) 並且requeue=false;
- 消息TTL過期
- 隊列達到最大長度
- 詳細解說:
- DLX也是一個正常的Exchange,和一般的Exchange沒有區別,它能在任何的隊列上被指定,實際上就是設置某個隊列的屬性。
- 當這個隊列中有死信時,RabbitMQ就會自動的將這個消息重新發布到設置的Exchange上去,進而被路由到另一個隊列。
- 可以監聽這個隊列中消息做相應的處理,這個特性可以彌補RabbitMQ3.0以前支持的immediate參數的功能;
- 然後我們進行正常聲明交換機、隊列、綁定,只不過我們需要在隊列上加上一個參數即可:arguments.put(“x-dead-letter-exchange”,“dlx.exchange”);
- 這樣消息在過期、requeue、隊列在達到最大長度時,消息就可以直接路由到死信隊列。
五. RabbitMQ高級整合應用
5.1 本章導航
- RabbitMQ整合SpringAMQP實戰
- RabbitMQ整合SpringBoot實戰
- RabbitMQ整合SpringCloud實戰
5.2 RabbitMQ整合Spring AMQP實戰
- RabbitAdmin
- Spring AMQP聲明
- RabbitTemplate
- SimpleMessageListenerContainer
- MessageListenerAdapter
- MessageConverter
5.3 RabbitAdmin
- RabbitAdmin類可以很好的操作RabbitMQ,在Spring中直接進行注入即可
- 核心代碼如下:
@Bean public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){ RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory); rabbitAdmin.setAutoStartup(true); return rabbitAdmin; }
- 然後使用RabbitTemplate的execute方法執行對應的聲明、修改、刪除等一系列RabbitMQ基礎功能操作
- 例如:添加一個交換機、刪除一個綁定、清空一個隊列裏的消息等等。
5.4 Spring的整合
- 假定已經創建好了項目,過程如下
- 引入依賴:
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
- 添加配置類:RabbitMQConfig:
@Configuration @ComponentScan("com.bfxy.spring.*") //讓此類被掃描到 public class RabbitMQConfig{ @Bean public ConnectionFactory connectionFactory(){ //如果bean沒有給name,就默認爲方法的名稱 CachingConnectionFactory connectionFactory=new CachingConnectionFactory(); connectionFactory.setAddress("192.168.11.76:5672"); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); connectionFactory.setVirtualHost("/"); return connectionFactory; } @Bean public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){ RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory); rabbitAdmin.setAutoStartup(true); return rabbitAdmin; } }
- 寫一個測試類
@RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTests{ @Test public void contextLoads(){} } @Autowired private RabbitAdmin rabbitAdmin; @Test public void testAdmin() throws Exception{ rabbitAdmin.declareExchange(new DirectExchange("test.direct",false,false)); rabbitAdmin.declareExchange(new TopicExchange("test.topic",false,false)); rabbitAdmin.declareExchange(new FanoutExchange("test.fanout",false,false)); rabbitAdmin.declareQueue(new Queue("test.direct.queue",false)); rabbitAdmin.declareQueue(new Queue("test.topic.queue",false)); rabbitAdmin.declareQueue(new Queue("test.fanout.queue",false)); rabbitAdmin.declareBinding(new Binding("test.direct.queue",Binding.DestinationType.QUEUE,"test.direct","direct",new HashMap<>())); rabbitAdmin.declareBinding( BindingBuilder .bind(new Queue("test.topic.queue",false)); //直接創建隊列 .to(new TopicExchange("test.topic",false,false)) //直接創建交換機,建立關聯關係 .with("user.#")); rabbitAdmin.declarebinding( BindingBuilder .bind(new Queue("test.fanout.queue",false)) .to(new FanoutExchange("test.fanout",false,false)); ) //清空隊列數據 rabbitAdmin.purgeQueue("test.topic.queue",false); }
常見的隊列:1. FanoutExchange:將消息分發到所有的綁定隊列,無RoutingKey的概念 2.HeadersExchange:通過添加屬性key-value匹配 3. DirectExchange:按照routingKey分發到指定隊列 4. TopicExchange:多關鍵詞匹配
5.5 RabbitAdmin
- 注意: autoStartup 必須要設置爲true,否則Spring容器不會加載RabbitAdmin類
- RabbitAdmin底層實現就是從Spring容器中獲取Exchange、Bingding、RoutingKey以及Queue的@Bean聲明;
- 然後使用RabbitTemplate的execute方法執行對應的聲明、修改、刪除等一系列RabbitMQ基礎功能操作;
5.6 SpringAMQP聲明
- 使用SpringAMQP去聲明,就需要使用SpringAMQP的如下模式,即聲明@Bean方式
- 代碼如下:
@Bean public TopicExchange exchange(){ return new TopicExchange("topic001",true,false); } @Bean public Queue queue(){ return new Queue("queue001",true); //隊列持久 } @Bean public Binding binding(){ return BindingBuilder.bind(queue()).to(exchange()).with("spring.*"); //這裏的with內容爲routKey,可自定義 }
5.7 消息模板 RabbitTemplate
- RabbitTemplate,即消息模板。我們在與SpringAMQP整合的時候進行發送消息的關鍵類
- 該類提供了豐富的發送消息的方法,包括可靠性投遞消息方法、回調監聽消息接口ConfirmCallback、返回值確認接口ReturnCallback等等。同樣我們需要進入注入到Spring容器中,然後直接使用;(同樣需要注入到Spring容器中進行使用)
- 在與Spring整合時需要實例化,但是在與SpringBoot整合時,在配置文件裏添加配置即可;
-
RabbitMQConfig.java 核心代碼如下:
@Configuration @ComponentScan({com.bfxy.spring.*}) //這裏的路徑換成自己的路徑 public class RabbitMQConfig{ @Bean public ConnectionFactory connectionFactory(){ CachingConnectionFactory connectionFactory=new CachingConnectionFactory(); connectionFactory.setAddress("192.168.11.76:5672"); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); connectionFactory.setVirtualHost("/"); return connectionFactory; } @Bean public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){ RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory); rabbitAdmin.setAutoStartup(true); return rabbitAdmin; } @Bean public TopicExchange exchange(){ return new TopicExchange("topic001",true,false); } @Bean public Queue queue(){ return new Queue("queue001",true); //隊列持久 } @Bean public Binding binding(){ return BindingBuilder.bind(queue()).to(exchange()).with("spring.*"); //這裏的with內容爲routKey,可自定義 } @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){ RabbitTemplate rabbitTemplate =new RabbitTemplate(connectionFactory); return rabbitTemplate; //在這裏的上面可以對RabbitTemplate設置一些屬性,最後再返回; } }
-
測試類中測試
@Autowired private RabbitTemplate rabbitTemplate; @Test public void testSendMessage() throws Exception{ //1. 創建消息 MessageProperties messageProperties =new MessageProperties(); messageProperties.getHeaders().put("desc","信息描述..."); messageProperties.getHeaders().put("type","自定義消息類型.."); Message message=new Message("Hello RabbitMQ".getBytes(),messageProperteis); rabbitTemplate.converAndSend("topic001","spring.amqp",message,new MessagePostProcessor(){ @Overide public Message postProcessMessage(Message message) throws AmqpException{ System.err.println("----------添加額外的配置-------"); message.getMessageProperties().getHeaders().put("desc","額外修改的信息描述"); message.getMessageProperties().getHeaders().put("attr","額外新加的屬性"); //新加的屬性不一定設置在Header,我們也可以設置在別的地方 return message; } }); } // 簡單版 最後面的方法是可以不要的 @Test public void testSendMessage2() throws Exception{ //創建消息 MessageProperties messageProperties=new MessageProperties(); messageProperteis.setContentType("text/plain"); //文本類型 Message message=new Message("mq 消息".getBytes(),messageproperties); rabbitTemplate.converAndSend("topic001","spring.amqp",message); }
// 最簡單版(消息體也可以不要,可直接傳文本內容)
@Test
public void testSendMessage3() throws Exception{
rabbitTemplate.converAndSend(“topic001”,“spring.amqp”,“我是一段消息內容”);
}
}
5.8 簡單消息監聽容器:SimpleMessageListenerContainer
- 這個類非常的強大,我們可以對他進行很多設置,對於消費者的配置項,這個類都可以滿足
- 監聽隊列(多個隊列)、自動啓動、自動聲明功能
- 設置事務特性、事務管理器、事務屬性、事務容量(併發)、是否開啓事務、回滾消息等
- 設置消費者數量、最小最大數量、批量消費
- 設置消息確認和自動確認模式、是否重回隊列、異常捕獲handler函數
- 設置消費者標籤生成策略、是否獨佔模式、消費者屬性等
- 設置具體的監聽器、消息轉換器等等。
注意:SimpleMessageListenerContainer可以進行動態設置,比如在運行中的應用可以動態的修改其消費者數量的大小、接收消息的模式等。很多基於RabbitMQ的自制定化後端管控臺在進行動態設置的時候,也是根據這一特性去實現的。所以可以看出SpringAMQP非常的強大;
思考一下:SimpleMessageListenerContainer爲什麼可以動態感知配置變更?
- 代碼示例: (在RabbiMQConfig.java上面的這個配置類中繼續添加Bean)
@Bean public SimpleMessageListenerContainer messageContainer(ConnectioNFactory connectionFactory){ SimpleMessageListenerContainer container=new SimpleMessageListenerContainer(connectionFactory); container.setQueues(queue001(),queue002()); //包含的隊列 container.setConcurrentConsumers(1); //當前消費者數量 container.setMaxConcurrentConsumers(5); //最大數量 container.setDefaultRequeueRejected(false); container.setAcknowledgeMode(AcknowledgMode.AUTO); container.setConsumerTagStrategy(new ConsumerTagStrategy(){ @Overide public String createConsumerTag(String queue){ return queue+"_"+UUID.randomUUID().toString(); } }); container.setMessageListener(new ChannelAwareMessageListener(){ @Override public void onMessage(Message message,Channel channel) throws Exception{ String msg=new String(message.getBody()); System.err.println("----消費者:---"+msg); } }) }
- 部分圖示:
我們在SimpleMessageListenerContainer 中給隊列名設置了隊列+UUID的形式,於是在可視化界面中就看到了;
5.9 消息監聽適配器:MessageListenerAdapter
- 通過messageListenerAdapter的代碼我們可以看出如下核心屬性:
- defaultListenerMethod默認監聽方法名稱:用於設置監聽方法名稱
- Delegate委託對象:實際真實的委託對象,用於處理消息、
- queueOrTagToMethodName: 隊列標識與方法名稱組成的集合
- 可以一一進行隊列與方法名稱的匹配;
- 隊列和方法名稱綁定,即指定隊列裏的消息會被綁定的方法所接收處理;
比如我們上面使用SimpleMessageListenerContainer 定義了同一個隊列的不同消費者(隊列名+UUID),我們可以使這每個消費者分別執行不同的方法,或者進行負載均衡等操作,可以使用此消息監聽器實現;具體可百度;
5.10 MessageConverter消息轉換器
- 我們在進行發送消息的時候,正常情況下消息體爲二進制的數據方式進行傳輸,如果希望內部幫我們進行轉換,或者指定自定義的轉換器,就需要用到MessageConverter;
- 自定義常用轉換器:MessageConverter,一般來講都需要實現這個接口
- 重寫下面兩個方法:
- toMessage:java對象轉換爲Message
- fromMessage:Message對象轉換爲java對象
- MessageConverter消息轉換器:
- Json轉換器:Jackson2JsonMessageConverter:可以進行java對象的轉換功能;
- DefaultJackson2JavaTypeMapper映射器:可以進行java對象的映射關係;
- 自定義二進制轉換器:比如圖片類型、PDF、PPT、流媒體等
使用轉換器的目的是當傳入不同的類型的數據(如json,類,PDF,圖片等)時,在消息的接收方接收到時也總是以傳入的類型接收結果對象;我們通過寫入不同的轉換器以達到此種效果。具體可百度。
- 部分代碼示例:
5.11 SpringBoot整合配置詳解
- publisher-confirms,實現一個監聽器用於監聽Broker端給我們返回的確認請求:RabbitTemplate.ConfirmCallback
- publisher-returns,保證消息對Broker端是可達的,如果出現路由鍵不可達的情況,則使用監聽器對不可達的消息進行後續的處理,保證消息的路由成功: RabbitTemplate.ReturnCallback
注意一點,在發送消息的時候對template進行配置mandatory=true保證監聽有效;生產端還可以配置其他屬性,比如發送重試,超時時間、次數、間隔等。
- 代碼如下:
-
配置application.properties:
spring.rabbitmq.publisher-confirms=true spring.rabbitmq.publisher-returns=true spring.rabbitmq.template.mandatory=true
-
創建RabbitMQConfig.java(此步驟省略,參照以前代碼)
-
創建生產者RabbitSender:
@Component public class RabbitSender{ @Autowired private RabbitTemplate rabbitTemplate; // ack確認 final ConfirmCallback confirmCallback=new RabbitTemplate.ConfirmCallback(){ @Override public void confirm(CorrelationData correlationData,boolean ack,String cause){ System.err.println("correlationData:"+ correlationData); System.err.println("ack:"+ack); if(!ack){ System.err.println("異常處理..."); } } }; // 消息發送後的返回 final ReturnCallback returnCallback=new RabbitTemplate.ReturnCallback(){ @Override public void returnedMessage(org.springframwork.amqp.core.Message message, int replyCode,String replyText, String exchange,String routingKey){ System.err.println("return exchange: "+ exchange+",routingKey:"+routingKey); } }; public void send(Object message,Map<String,Object> properties) throws Exception{ MessageHeaders mhs=new MessageHeaders(properteis); Message msg=MessageBuilder.createMessage(message,mhs); rabbitTemplate.setConfirmCallback(confirmCallback); rabbitTemplate.setReturnCallback(returnCallback); CorrelationData cd=new CorrelationData("123456789"); //這裏的id值一定要唯一。我們爲了測試隨便寫的。用於生產環境可使用:id+時間戳等方式 rabbitTemplate.converAndSend("exchange-1","springboot.hello",msg); } }
ack確認中的if(!ack){}裏面,我們可以實現自己的方法。比如做事務的時候,當消息發送不成功,即ack=false時,我們可以將消息發送不成功的狀態錄入數據庫。做定時任務對數據庫中這些發送不成功的消息進行消息重試(重新發送),以保證消息最終都能成功發送,不會漏發;而returnCallback這裏的方法,當消息返回失敗的時候,它能告訴我們消息發送失敗的原因等。 CorrelationData這裏是爲了確定此消息的唯一性。當我們確定消息發送失敗等,我們可以對此指定的消息進行操作。所以裏面的id要保證全局唯一。
-
測試類:
@RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTests{ @Test public void contextLoads(){} @Autowired private RabbitSender rabbitSender; private static SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @Test public void testSender1() throws Exception{ Map<String,Object> properties =new HashMap<>(); properties.put("number","12345"); properties.put("send_time",simpleDateFormat.format(new Date())); rabbitSender.send("Hello RabbitMQ",properties); } }
這裏的SimpleDateFormat是一個線程不安全的時間解析類,此處僅用於功能實現,生產環境使用請注意。
-
5.12 SpringBoot整合配置詳解2
-
消費端核心配置:
spring.rabbitmq.listener.simple.acknowledge-mode=MANUAL spring.rabbitmq.listener.simple.concurrency=1 spring.rabbitmq.listener.simple.max-concurrency=5;
-
其他要點:
- 首先配置手工確認模式,用於ACK的手工處理,這樣我們可以保證消息的可靠性送達,或者在消費端消費失敗的時候可以做到重回隊列、根據業務記錄日誌等處理
- 可以設置消費端的監聽個數和最大個數,用於控制消費端的併發情況。
-
@RabbitListener註解的使用
- 消費端監聽@RabbitMQListener註解,這個對於在實際工作中非常的好用。
- @RabbitListener是一個組合註解,裏面可以註解配置
- @QueueBinding、@Queue、@Exchange直接通過這個組合註解一次性搞定消費端交換機、隊列、綁定、路由、並且配置監聽功能等。
-
核心兩個代碼:
@RabbitListener(bindings=@QueueBinding( value=@Queue(value="queue-1",durable="true"), exchange=@Exchange(value="exchange-1",durable="true"), type="topic", ignoreDeclarationExceptions="true"),,key="springboot.*"))
@RabbitHandler public void onMessage(Message message,Channel channel) throws Exception{]
由於類配置寫在代碼裏非常不友好,所以強烈建議大家使用配置文件配置。
-
配置文件寫法,代碼如下:
- 生產端代碼配置:
- 消費端代碼配置:
- 消息接收端:
- 消息發送端:
完整代碼請參考上面一小節的發送端代碼。
- 生產端代碼配置:
-
使用實體類接收和發送消息代碼演示:
- 實體類:
- 消費監聽的配置:
- 消費者代碼編寫:
- 生產者方法:
- 消息發送示例:
這裏是引入生產者的發送消息的方法,直接將此對象傳輸;
- 實體類:
5.13 Spring Cloud Stream 整合
-
SpringCloud ,這個全家桶框架在整個中小型互聯網公司異常的火爆,那麼相對應着,Spring Cloud Stream就漸漸的被大家所重視起來,這一節課主要來介紹Spring Cloud Stream 如何與RabbitMQ進行集成。
-
Spring Cloud Stream 整體架構核心概念圖
-
Spring Cloud Stream 整體架構核心概念圖
-
Barista接口:
-
Barista接口是定義來作爲後面類的參數,這一接口定義來通道類型和通道名稱,通道名稱是作爲配置用,通道類型則決定了app會使用這一通道進行發送消息還是從中接收消息
-
@Output:輸出註解,用於定義發送消息接口
-
@Input: 輸入註解,用於定義消息的消費者接口
-
@StreamListener:用於定義監聽方法的註解
-
-
使用Spring Cloud Stream 非常簡單,只需要使用好這3個註解即可,在實現高性能消息的生產和消費的場景非常適合,但是使用Spring Cloud Stream 框架有一個非常大的問題就是不能實現可靠性的投遞,也就是沒法保證消息的100%可靠性,會存在少量消息丟失問題。
存在消息丟失的問題我們可以通過補償機制進行解決;
- 爲什麼存在消息丟失?
- 這個原因是因爲Spring Cloud Stream 框架爲了和Kafka兼顧所以在實際工作中使用它的目的就是針對高性能的消息通信的! 這點就是在當前版本Spring Cloud Stream 的定位。
5.14 Spring Cloud Stream 實戰
- xml中核心依賴:
- Brista接口:
- 相關application.yml配置:
- 生產端代碼:
-
消費端XML引入的核心依賴:
-
實體類(類名可以改的,不一定都叫這名,這是官方的名字)
-
消費端配置application.properties:
requeue-rejected 是否支持return; acknowledge-model=MANUAL (手動簽收) ; recovery-interval 服務不穩定多少毫秒後進行重連 durable-subscription 是否啓動持久化訂閱; max-concurrenty=5 最大監聽數
-
消費端監聽消息代碼:
這裏如果換成Kafka,大部分代碼也是不需要改變的。並且我們的發送端可以使用Kafka,接收端可以使用RabbitMQ這種非同種消息類型的消息。
-
發送消息測試:
Spring Cloud Stream 這裏就相當於多了一箇中間層,它將底層是Kafka或者RabbitMQ或者是其他的作爲配置不侵入代碼。消息的接收和發送都通過管道進行。在以後如果需要替換或者同時使用多種消息類型,都是可以的。
5.15 本章小結:
- 本章我們學習了Spring AMQP 的相關知識,通過學習,我們對RabbitMQ集成Spring有了一個深入的認識,這樣爲我們後續的學習、工作使用都打下了堅實的基礎。最後我們整合了SpringBoot 與Spring Cloud Stream,更方便更高效的集成到我們的應用服務中去!
六. Rabbit的集羣架構
6.1 本章導航
- 首先是瞭解RabbitMQ集羣架構模式
- 接下來從零開始構建一個高可靠的RabbitMQ集羣
- 集羣的配置文件與集羣運維故障、失敗轉移講解
- 高級插件的使用
6.2 RabbitMQ集羣架構模式
- 主備模式:實現RabbitMQ的高可用集羣,一般在併發和數據量不高的情況下,這種模型非常的好用且簡單。主備模式也稱之爲Warren模式;
- 所謂的rabbitMQ另外一種模式就是warren(兔子窩),就是一個主/備方案(主節點如果掛了,從節點提供服務而已,和activeMQ利用zookeeper做主/備一樣)
- 架構圖示如下:
- HaProxy配置:(tcp級別的代理)
- 遠程模式:遠程模式
-
遠距離通信和複製,所謂Shovel就是我們可以把消息進行不同數據中心的複製工作,我們可以跨地域的讓兩個mq集羣互聯。我們下面看一下Shovel架構模型;
-
圖示:
在使用了shovel插件後,模型變成了近端同步確認,遠端異步確認的方式,大大提高了訂單確認速度,並且還能保證可靠性。
-
細節圖示:
正常隊列壓力過大的時,會將訂單複製到遠端中心,在遠端進行數據消費進行異步確認;用的不是特別多的原因是因爲我們目前已經有了更好的遠端模式,這個是比較早期的使用方式;
-
Shovel集羣的配置,首先啓動rabbitMQ插件,命令如下:
rabbitmq-plugins enable amqp-client
rabbitmq-plugins enable rabbitmq_shovel
-
步驟:
- 創建rabbitmq.config:touch /etc/rabbitmq/rabbitmq.config
- 添加配置見rabbitmq.config
- 最後我們需要源服務器和目的服務器都使用相同的配置文件(rabbitmq.config)
- 配置文件如圖:
具體的可以百度。
-
- 鏡像模式
- 概述:
- 集羣模式非常經典的就是Mirror鏡像模式,保證100%數據不丟失,在實際工作中也是用的最多的。並且實現集羣非常的簡單,一般互聯網大廠都會構建這種鏡像集羣模式;
- Mirror鏡像隊列,目的是爲了保證rabbitmq數據的高可靠性解決方案,主要就是實現數據的同步,一般來講是2-3個節點實現數據同步(對於100%數據可靠性解決方案一般是3節點)集羣架構如下:
- 概述:
- 多活模式(比Shovel更好用)
- 概述:
- 這種模式也是實現異地數據複製的主流模式,因爲Shovel模式配置比較複雜,所以一般來說實現異地集羣都是使用這種雙活或者多活模型來去實現的。這種模型需要依賴rabbitmq的federation插件,可以實現持續的可靠的AMQP數據通信,多活模式在實際配置與應用非常的簡單。
- RabbitMQ部署架構採用雙中心(多中心),那麼在兩套(或多套)數據中心各部署一套RabbitMQ集羣,各中心的RabbitMQ服務除了需要爲業務提供正常的消息服務外,中心之間還需要實現部分隊列消息共享。多活集羣架構如下:
-
Federation插件進行互相複製;
- Federation插件是一個不需要構建Cluster,而在Brokers之間傳輸消息的高性能插件,Federation插件可以在Brokers或者Cluster之間傳輸消息,連接的雙方可以使用不同的users和virtual hosts,雙方也可以使用版本不同的RabbitMQ和Erlang. Federation插件使用AMQP協議通訊,可以接受不連續的傳輸。
- Federation Exchanges,可以看成Downstream從Upstream主動拉取消息,但並不是拉取所有消息,必須是在Downstream上已經明確定義Bindings關係的Exchange,也就是有實際的物理Queue來接收消息,纔會從Upstream拉取消息到Downstream。使用AMQP協議實施代理間通信,Dwonstream會將綁定關係組合在一起,綁定/解除綁定命令將發送到Upstream交換機。因此,Federation Exchange只接收具有訂閱的消息,本處貼出官方圖來說明:
- 概述:
集羣構建可參考百度
6.3 Haproxy
-
HAProxy是一款提供高可用性、負載均衡以及基於TCP(第四層)和HTTP(第七層)應用的代理軟件。支持虛擬主機,它是免費、快速並且可靠的一種解決方案。HAProxy特別適用於那些負載特大的web站點,這些站點通常又需要會話保持或七層處理。HAProxy運行在時下的硬件上,完全可以支持數以萬計的併發連接。並且它的運行模式使得它可以很簡單安全的整合進您當前的架構中,同時可以保護你的web服務器不被暴露到網絡上。
-
Haproxy性能最大化(一)
- 如圖所示:
- 如圖所示:
-
實戰安裝:
- 下載依賴包:
yum install gcc vim wget
- 下載haproxy:
wget http://www.haproxy.org/download/1.6/src/haproxy-1.6.5.tar.gz
- 解壓:
- 進入下載的軟件位置:
cd /usr/local/software/
- 解壓:
tar -zxvf haproxy-1.6.5.tar.gz -C /usr/local
- 進行操作:
- 進入目錄
cd haproxy-1.6.5/
- 編譯命令:
make TARGET=linux31 PREFIx=/usr/local/haproxy
- 安裝命令:
make install PREFIX=/usr/local/haproxy
- 創建文件夾:
mkdir /etc/haproxy
- 進入目錄
- 賦權:(給予角色權限)
groupadd -r -g 149 haproxy useradd -g haproxy -r -s /sbin/nologin -u 149 haproxy
- 創建haproxy配置文件
touch /etc/haproxy/haproxy.cfg
我們對代理等的配置,可以在這裏面進行配置; 通過
cd software/
會看到一個haproxy.cfg ,我們可以通過命令:
mv haproxy.cfg /etc/haproxy/
將配置移動 然後cd /etc/haproxy/
- 啓動:
/usr/local/haproxy/sbin/haproxy -f /etc/haproxy/haproxy.cfg
- 查看haproxy進程狀態:
ps -ef | grep haproxy
- 訪問 haproxy
localhost:8100/rabbitmq-stas
- 進入下載的軟件位置:
- 下載依賴包:
6.4 KeepAlived
-
簡介:
-
三個重要功能:
- 管理LVS負載均衡軟件
- 實現LVS集羣節點的健康檢查中
- 作爲系統網絡服務的高可用性
-
KeepAlived高可用原理:
-
什麼是VRRP?
具體的安裝可以百度
- 集羣配置文件:
6.5 集羣恢復與故障轉移
6.7 延遲插件的作用
- 延遲隊列可以做什麼事情?
- 比如消息的延遲推送、定時任務(消息)的執行。包括一些消息重試策略的配合使用,以及用於業務削峯限流、降級的異步延遲消息機制,都是延遲隊列的實際應用場景。
- 安裝:
- 下載插件
- 把下載好的文件放到指定目錄
- 啓動插件
- 訪問地址:http://192.168.1.21:15672/#/exchanges
本章小結:本章我們掌握了RabbitMQ各種集羣構建姿勢,真正從零開始構建一個高可用的RabbitMQ集羣,通過鏡像隊列+Haproxy+KeepAlived的架構進行構建!並且我們介紹了集羣的節點宕機故障問題如何進行解決的5個解決方案!也學習了延遲插件的使用!
七. 互聯網SET化架構
7.1 本章導航
- 瞭解SET化架構的進衍
- 互聯網大長是如何進行SET化的
- SET化架構的設計與解決方案
- RabbitMQ SET化架構的搭建
7.2 BAT/TMD大廠單元化機構設計衍變之路(一)
-
-
容災問題:
- 核心服務(比如訂單服務)會掛掉,會影響全網所有用戶,導致整個業務不可用;
- 數據庫主庫集中在一個IDC,主機房掛掉,會影響全網所有用戶,整個業務無法快速切換和恢復;
-
資源擴展問題:
- 單IDC的資源(機器、網絡帶寬等)已經沒法滿足,擴展IDC時,存在跨機房訪問時延問題(增加異地機房時,時延問題更加嚴重);
- 數據庫主庫單點,連接數有限,不能支持應用程序的持續擴展;
-
大集羣拆分問題:
- 核心問題:分佈式集羣規模擴大後,會相應的帶來資源擴展、大集羣拆分以及容災問題。
- 所以出於對業務擴展性以及容災需求的考慮,我們需要一套從底層架構徹底解決問題的方案;
-
業界主流方案: 單元化架構方案(阿里,支付寶,餓了麼,微信等)
7.3 同城“雙活”架構介紹
- 目前很多大型互聯網公司的業務架構可以理解爲同城“雙活”架構,這裏的“雙活”是加引號的,具體可以這樣理解:
- 業務層面上已經做到真正的雙活(或者多活),分別承擔部分流量;
- 存儲層面比如定時任務、緩存、持久層、數據分析等都是主從架構,會有跨機房寫;
- 一個數據中心故障,可以手動切換流量,部分組件可以自動切換;
7.4 兩地三中心架構介紹:
- 使用災備的思想,在同城“雙活”的基礎上,在異地部署一套災備數據中心,每個中心都具有完備的數據處理能力,只有當主節點故障需要容災的時候纔會緊急啓動備用數據中心;
- 優缺點圖示:
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20191023214325480.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MTI4MDQ5,size_16,color_FFFFFF,t_70)
7.5 SET化架構策略路
-
SET化架構設計:
-
流量路由:
- 按照特殊的key(通常爲userid)進行路由,判斷某次請求該路由到中心集羣還是單元化集羣
-
中心集羣:
- 未進行單元化改造的服務(通常不在覈心交易鏈路,比如供應鏈系統)被稱爲中心集羣,跟當前架構保持一致;
-
單元化集羣:
- 每個單元化集羣只負責本單元內的流量處理,以實現流量拆分以及故障隔離;
- 每個單元化集羣前期只存儲本單元產生的交易數據,後續會做雙向數據同步,實現容災切換需求;
-
中間件(RPC、KV、MQ等):
- RPC:對於SET服務,調用封閉在SET內;對於非SET服務,沿用現有路由邏輯;
- KV:支持分SET的數據生產和查詢
- MQ:支持分SET的消息生產和消費;
-
數據同步:
- 全局數據(數據量小且變化不大,比如商家的菜品數據)部署在中心集羣,其他單元化集羣同步全局數據到本單元化內;
- 未來演變爲異地多活架構時,各單元化集羣數據需要進行雙向同步來實現容災需要;
-
SET化路由策略及其能力:
- 異地容災:
- 通過SET化架構的流量調度能力,將SET分別部署在不同地區的數據中心,實現跨地區容災支持;
- 異地容災:
-
高效的本地化服務:
- 利用前端位置信息採集和域名解析策略,將流量路由到最近的SET,提供最高效的本地化服務;
- 比如O2O場景天然具有本地生產,本地消費的特點,更加需要SET化支持;
-
SET化架構圖:
-
SET化架構流轉圖:
7.6 SET化架構原則:
-
SET化重要的原則:
- 對業務透明原則:
- SET化架構的實現對業務代碼透明,業務代碼層面不需要關心SET化規則,SET部署等問題;
- SET切分規則:
- 理論上,切分規則由業務層面按需定製
- 實現上,建議優先選最大的業務維度進行切分
- 比如海量用戶的O2O業務,按用戶位置信息進行切分。此外,接入層、邏輯層和數據層可以有獨立的SET切分規則,有利於實現部署和運維成本的最優化;
- 部署規範原則:
- 一個SET並不一定只限制在一個機房,也可以跨機房或者跨地區部署;爲保證靈活性,單個SET內機器數不宜過多(如不超過1000臺物理機)
- 對業務透明原則:
-
SET消息中間件架構實現(RabbitMQ雙活):
- 集羣同步插件:
- 集羣同步插件:
使用此集羣插件,發送一個消息到集羣中,它可以進行轉發複製到另外一個集羣中;具體部署方式請百度;
- SET配置規則:
- 第一:
- 第二:
- 第三:
本章小結:本章主要講了互聯網大長的SET化架構進衍,以及使用SET化架構能解決哪些問題,SET化架構的核心設計目標和重要原則,通過對RabbitMQ的SET化設計,也就是使用federation插件構建多活集羣,實現多中心的數據同步!我們可以對大規模集羣的部署有一個更可靠的解決方案!
七. 一線大廠的MQ組件實現思路和架構設計方案
7.1 本章導航
- 基礎組件封裝設計- 迅速消息發送
- 基礎組件封裝設計- 確認消息發送
- 基礎組件封裝設計- 批量消息發送
- 基礎組件封裝設計- 延遲消息發送
- 基礎組件封裝設計- 順序消息發送
- 基礎組件封裝設計- 事務消息發送
- 消息冪等性保障- 消息路由規則架構設計
7.2
-
一線大廠的MQ組件實現思路和架構設計方案
-
MQ組件實現功能點(一)
- 支持消息高性能的序列化轉換、異步化發送消息
- 支持消息生產實例與消費實例的連接池緩存化,提升性能
- 支持可靠性投遞消息,保障消息的100%不丟失
- 支持消費端的冪等操作,避免消費端重複消息的問題;
-
MQ組件實現的功能點(二)
- 支持迅速消息發送模式,在一些系統日誌收集/統計分析等需求下可以保證高性能,超高吞吐量。
- 支持延遲消息模式,消息可以延遲發送,指定延遲時間,用於某些延遲檢查、服務限流場景
- 支持事務消息,且100%保障可靠性投遞,在金融行業單筆大金額操作時會有此類需求;
-
MQ組件實現功能點(三)
- 支持順序消息,保證消息送達消費端的前後順序,例如下訂單等複合性操作;
- 支持消息補償,重試,以及快速定位異常/失敗消息
- 支持集羣消息負載均衡,保證消息落到具體SET集羣的負載均衡
- 支持消息路由策略,指定某些消息路由到指定的SET集羣
7.3 消息發送模式- 迅速消息發送
- 迅速消息:
- 迅速消息是指消息不進行落庫存儲,不做可靠性的保障
- 在一些非核心消息、日誌數據、或者統計分析等場景下比較合適
- 迅速消息的you的優點就是性能最高,吞吐量很大
- 圖示:
不落庫存儲,不做消息的其他處理,直接拿到就消費;損失了可靠性,提高了性能
7.4 消息發送模式- 確認消息發送
- 圖示:
7.5 消息發送模式- 批量消息發送
-
概述:
- 批量消息就是指我們把消息放到一個集合裏進行統一提交
- 這種方案設計思路是期望消息在一個會話裏,比如放到threadlocal裏的集合,然後擁有相同的會話ID,並且帶有這次提交消息的SIZE等相關屬性,最重要的一點是要把這一批消息進行合併。
- 對於Channel而言,就是發送一次消息。這種方式也是希望消費端在消費的時候,可以進行批量化的消費,針對於某一個原子業務的操作去處理,但是不保障可靠性,需要進行補償機制;
-
圖示:
7.6 消息發送模式- 延遲消息發送
- 概述: 延遲消息相對比較簡單,就是我們在Message封裝的時候加delayTime屬性即可,使得我們的消息可以進行延遲發送,根據具體的業務場景也可以很好的使用到!
- 場景舉例:
- 比如你在電商平臺買到的商品簽收後,不點擊確認支付,那麼系統自動會在7天(一定時間)去進行支付操作。
- 還有一些自動超時作廢的場景,你的優惠券/紅包有使用時間限制,也可以用延遲消息機制;
7.7 消息發送模式- 順序消息(一)
- 順序消息,比較類似於批量消息的實現機制,但是也有些不同。
- 我們要保障以下幾點:
- 發送的順序消息,必須保障消息投遞到同一個隊列,且這個消費者只能有一個(獨佔模式)
- 然後需要統一提交(可能是合併成一個大消息,也可能是拆分爲多個消息),並且所有消息的會話ID一致
- 添加消息屬性:順序標記的序號、和本次順序消息的SIZE屬性,進行落庫操作
- 並行進行發送給自身的延遲消息(注意帶上關鍵屬性:會話ID、SIZE)進行後續處理消費
- 當收到延遲消息後,根據會話ID、SIZE抽取數據庫數據進行處理即可。
- 定時輪詢補償機制,對於異常情況:
備註:比如生產端消息沒有完全投遞成功、或者消費端落庫異常,導致消費端落庫後缺少消息條目的情況
建議還是拆分成小消息,這樣不用考慮順序問題,多線程處理,然後接收到後再進行手動排序;
可靠性投遞,就需要數據庫;
7.8 消息發送模式- 事務消息發送(一)
- 事務消息,相對使用比較少見,但是也可能會有此方面的需求;如:單筆轉賬超過一個上限的時候,我們就希望這個消息優先級最高,並且可靠性要求達到100%,當然我們的系統和銀行端系統都要兼顧纔行,所以也會有一些補償機制,主動發起銀行端查詢指令機制等。
7.9 消息發送模式- 事務消息發送(二)
- 爲了保障性能的同時,也支持事務。我們並沒有選擇傳統的RabbitMQ事務和Spring集成的機制,因爲在性能測試的過程中,效果並不理想,非常消耗系統資源且會出現阻塞等情況,在高峯期也是一定程度上影響MQ集羣的性能;
- 解決方案:
- 我們採用類似可靠性投遞的機制,也就是補償機制
- 但是我們的數據源必須是同一個,也就是業務操作DB1數據庫和消息記錄DB2數據庫使用同一個數據源。
- 然後利用重寫Spring DataSourceTransactionManager,在本地事務提交的時候進行發送消息,但是也有可能事務提交陳宮但是消息發送失敗,這個時候就需要進行補償了。
7.10 消息發送模式- 事務消息發送(五)
- 圖示:
7.11 消息的冪等性的必要性
- 保障消息的冪等性,這也是我們在使用MQ中至關重要的環節。
- 可能導致消息出現非冪等的原因:
-
- 可靠性消息投遞機制
-
- MQ Broker服務與消費端傳輸消息的過程中的網絡抖動
-
- 消費端故障或異常
-
7.12 消息的冪等性的設計
- 圖示:
7.13 本章小結
- 本章主要和大家一起來探討和交流互聯網大廠的MQ基礎組件服務的核心解決方案和架構設計思路,包含我們要實現哪些功能,比如各種消息的投遞方式,以及消費的冪等性保障,通過本章的學習希望能夠幫助大家開闊思路,更深入的理解MQ中間件在實際工作中的使用!
八. RabbitMQ課程總結
- 掌握MQ在各種場景下的應用
- 熟悉瞭解/深入探究主流消息中間件特點、架構、原理、底層機制
- 學習Rabbit基礎原理和API級別使用
- 巧妙運用Rabbit高級特性服務於你的應用服務、架構設計
- 學會MQ與Spring體系整合,更高效的實現與應用服務的集成
- 構建高可靠性的MQ服務、搭建完備的平臺化體系
- 緊密結合互聯網大廠的架構設計思路,構建自己的架構設計思想
- 解決各種場景、需求下的MQ中間件設計與落地
- 封裝基礎組件,讓別人站在你的肩膀上編碼;