一、MQ 概述
1.1 什麼是 MQ
MQ(Message Queue) 即消息隊列,是一種先進先出的結構,主要應用於應用解耦、流量削峯、數據分發等場景,能夠極大降低硬件需求,是大併發處理系統中不可缺少的一個環節
1.2 MQ 的常見場景
1.2.1 應用解耦
系統設計時要求高內聚,低耦合。系統耦合性越高,容錯性就越低。
舉個例子:
在訂單系統中,需要調用支付系統、庫存系統、物流系統
當有一天物流系統發生故障或升級,則會對訂單系統造成影響,造成用戶下單異常。即使物流系統修復,也可能造成訂單的丟失。
使用消息隊列後,訂單系統只需要將數據發送到 MQ 中,就可以返回給用戶了。而消息存儲在 MQ 中,即使系統發生異常,等系統重啓後,也會從 MQ 中取出對應數據,並處理。
1.2.2 流量削峯
應用系統若遇到系統請求量猛增時,可能會對數據庫造成極大壓力,甚至導致系統崩潰。同時使用流量削峯,能夠降低硬件成本。
例如:
A系統是一個訂單秒殺系統,訂單秒殺只有每天的晚上6點纔會有大流量壓入。經過預估,A系統每秒能承受的併發量爲3k,超出可能造成延時甚至系統崩潰。這時候若選擇升級硬件,由於訂單秒殺系統只有晚6點纔會有大流量,就會造成成本增加和資源浪費。若放任不管,則要承擔系統崩潰的風險。
若增加了消息隊列,則可以將請求緩存起來,讓系統逐步處理。通過控制每秒獲取的請求數,可以讓A系統安全地處理完這些請求。
1.2.3 數據分發
通過消息隊列,可以讓數據在多個系統之間進行流通。數據產生方不需要關心誰來使用數據,只需要將數據發送到消息隊列中。而數據的使用方只要直接從消息隊列中獲取數據使用即可。
1.3 MQ 的優缺點
優點:解耦、削峯、數據分發,降低成本
缺點:
系統可用性降低
過度依賴 MQ,一旦 MQ 宕機,則會對業務造成巨大影響
系統複雜度提高
MQ 的加入大大提高了系統複雜度,以前系統是同步的遠程調用,現在是通過MQ進行異步調用。如何保證消息沒有被重複消費?如何處理消息丟失的情況?如何保證消息的有序性?
一致性問題
A系統處理完業務,通過MQ給B、C、D三個系統,若B、C系統處理成功,D系統處理失敗,如何保證消息數據處理的一致性?
1.4 MQ 產品比較
常見的產品有 ActiveMQ、RabbitMQ、RocketMQ、Kafka
二、RocketMQ 使用
2.1 RocketMQ 角色介紹
- Producer:消息發送者
- Consumer:消息接受者
- Broker: 暫存和傳輸消息
- NameServer:管理Broker
- Topic:區分消息的種類,一個發送者可以發送消息給一個或多個 Topic;一個消息的接收者可以訂閱一個或多個 Topic 消息
- Message Queue:相當於 Topic的分區,用於並行發送消息和並行接收消息
2.2 基本搭建
2.2.1 下載
官網下載:https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.5.1/rocketmq-all-4.5.1-bin-release.zip
百度網盤下載:
鏈接:https://pan.baidu.com/s/1nUNp4E9c7l_K6X5j4fcyfA
提取碼:ck22
2.2.2 安裝和配置
安裝rocketmq
# 解壓安裝包
unzip rocketmq-all-4.5.1-bin-release.zip
# 重命名
mv rocketmq-all-4.5.1-bin-release rocketmq
目錄解析:
- benchmark:官方自帶的壓測工具
- bin:啓動腳本
- conf:實例配置腳本,包括 broker 配置文件、 logback 配置文件等
- lib:依賴的jar包,包括 Netty、commons-lang、FastJSON等
修改配置文件:
由於 rocketmq 默認佔用的虛擬機內存爲8G,可能與實際需求不符,故需要修改 rocketmq 佔用的內存大小
vim ./bin/runserver.sh
vim runbroker.sh
配置屬性:
- -Xms 堆內存的最小大小,默認爲物理內存的1/64
- -Xmx jvm最大可用堆內存的大小,默認爲物理內存的1/4
- -Xmn 堆內新生代的大小。
- -XX:MetaspaceSize=128m //持久代的初始大小
- -XX:MaxMetaspaceSize=320m //持久代的上限
- 整個JVM內存大小=新生代大小 + 年老代大小 + 持久代大小
2.2.3 啓動和日誌
啓動時最好先啓動 NameServer,後啓動 Broker。因爲 NameServer 是用於記錄 Broker 地址的,每個 Broker 啓動時都會向 NameServer 上報自己的地址
啓動 NameServer
# 後臺啓動NameServer
nohup sh bin/mqnamesrv &
# 查看啓動日誌
tail -f ~/logs/rocketmqlogs/namesrv.log
啓動 Broker
# 啓動Broker
nohup sh bin/mqbroker -n localhost:9876 &
# 查看啓動日誌
tail -f ~/logs/rocketmqlogs/broker.log
驗證
jps
2.2.4 關閉 rocketmq
# 1.關閉NameServer
sh bin/mqshutdown namesrv
# 2.關閉Broker
sh bin/mqshutdown broker
2.2.5 測試 RocketMQ
發送消息
# 設置環境變量
export NAMESRV_ADDR=localhost:9876
# 使用安裝包的Demo發送消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
接收消息
# 設置環境變量
export NAMESRV_ADDR=localhost:9876
# 接收消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
2.3 編寫啓動和關閉腳本
2.3.1 nameserver 腳本
# 創建啓動腳本文件
vim name_start.sh
# 添加腳本內容
nohup sh ./bin/mqnamesrv &
echo "nameserver 啓動成功 .."
tail -f ~/logs/rocketmqlogs/namesrv.log
# 創建關閉腳本文件
vim name_stop.sh
# 添加腳本內容
sh bin/mqshutdown namesrv
echo "nameserver 關閉成功..."
echo "再次搜索結果爲:"
sleep 3
jps
2.3.2 brokerserver 腳本
# 創建啓動腳本文件
vim broker_start.sh
# 添加腳本內容
nohup sh bin/mqbroker -n localhost:9876 &
tail -f ~/logs/rocketmqlogs/broker.log
# 創建關閉腳本文件
vim broker_stop.sh
# 添加腳本內容
sh bin/mqshutdown broker
echo "brokerserver 關閉中..."
echo "再次搜索結果爲:"
sleep 5
jps
三、Rocket 集羣
3.1 集羣架構
描述 | |
NameServer 集羣 | NameServer 是一個無狀態的節點,可集羣部署,節點都是各自獨立的,無任何信息同步 |
Broker 集羣 |
① Broker 分爲 Master 與 Slave,一個 Master 可以對應多個 Slave,但一個 Slave 只能對應一 個Master ② Master 與 Slave 的對應關係通過制定相同的 BrokerName,不同的 BrokerID 來定義,id 爲 0 表示 Master, 非 0 表示 Slave ③ 可以部署多個 Master 實現 Broker 集羣,即多組 Master - Slaves 的 Broker 節點 ④ Master 通常用於寫入數據,Slaves 用於讀取數據 ⑤ 每個 Broker 與 NameServer 集羣中的所有節點建立長連接,定時註冊 Topic 信息到所有 NameServer |
Producer 集羣 |
① Producer 爲消息的生產者,都是各自獨立的無狀態的節點,可以認爲只要向 mq 中推送消息的節點都算作 Producer 節點。 ② Producer 節點與 NameServer 集羣中的隨機一個節點建立長連接,定期從 NameServer 取出 Topic 路由信息,並向提供 Topic 服務的 Master 建立長連接。且定時向 Master 發送心跳。 |
Customer 集羣 |
① Customer 爲消息的消費者,都是各自獨立的無狀態的節點,可以認爲只要向 mq 中獲取消息的節點都算作 Customer 節點。 ② Customer 節點與 NameServer 集羣中的隨機一個節點建立長連接,定期從 NameServer 取出 Topic 路由信息,並向提供 Topic 服務的 Master、Slave 建立長連接。且定時向 Master、Slave 發送心跳。 ③ Customer 節點既可以從 Master 訂閱消息,也可以從 Slave 訂閱消息,訂閱規則由 Broker 配置決定 |
3.2 四種集羣模式
3.2.1 單 Master 模式
部署簡單,但風險較大,一旦 Broker 重啓或者宕機時,會導致服務不可用,不建議在線上環境使用,可以在本地測試
3.2.2 多 Master 模式
Broker 集羣中無 Slave,全是 Master
優點:
配置簡單,且單個 Master 宕機或重啓維護對應用無影響,在磁盤配置爲 RAID10 時,即使機器宕機不可恢復的情況下,由於 RAID10 磁盤非常可靠,消息也不會丟(異步刷盤丟失少量消息,同步刷盤一條不丟),性能最高
缺點:
單臺機器宕機期間,這臺機器上未被消費的消息在機器恢復之前不可訂閱和消費,消費實時性會受到影響
3.2.3 多 Master 多 Slave 模式(異步)
每個 Master 配置一個 Slave,有多對 Master-Slave,HA 採取異步複製方式,主備有短暫消息延遲(毫秒級)。即提供者將消息發送到 Master 後,Master 即刻返回結果給提供者。同時異步複製到 Slave 中
優點:
即使磁盤損壞,丟失的消息非常少,且消息的實時性不會受到影響。且 Master 宕機後,消費者仍然可以從 Slave 消費,而且此過程對應用透明,不需要人工干預,性能同多 Master 模式幾乎一樣
缺點:
Master 宕機,磁盤損壞的情況下會丟失少量消息
3.2.4 多 Master 多 Slave 模式(同步)
每個Master配置一個Slave,有多對Master-Slave,HA採用同步雙寫方式,即只有主備都寫成功,才嚮應用返回成功。即提供者將消息發送到 Master後,Master 要將這條消息同步到所有的 Slave 上,同步全部完成後,纔會返回結果給提供者。
優點:
數據與服務都無單點故障,在 Master 宕機的情況下消息無延遲,服務可用性與數據可用性都非常高
消息安全高,高可用性
缺點:
性能比異步複製模式略低(約低10%左右),發送單個消息的RT會略高,且在主節點宕機後,備機不能自動切換爲主機,需要人爲干涉
3.3 雙主雙從集羣搭建
3.3.1 總體架構圖
雙主雙從集羣(2m-2s)同步雙寫方式,即使用兩組 Master-Slave 形成集羣
3.3.2 工作流程
1)啓動NameServer,NameServer起來後監聽端口,等待Broker、Producer、Consumer連上來,相當於一個路由控制中心。
2)Broker啓動,跟所有的NameServer保持長連接,定時發送心跳包。心跳包中包含當前Broker信息(IP+端口等)以及存儲所有
Topic信息。註冊成功後,NameServer集羣中就有Topic跟Broker的映射關係。
3)收發消息前,先創建Topic,創建Topic時需要指定該Topic要存儲在哪些Broker上,也可以在發送消息時自動創建Topic。
4)Producer發送消息,啓動時先跟NameServer集羣中的其中一臺建立長連接,並從NameServer中獲取當前發送的Topic存在哪
些Broker上,輪詢從隊列列表中選擇一個隊列,然後與隊列所在的Broker建立長連接從而向Broker發消息。
5)Consumer跟Producer類似,跟其中一臺NameServer建立長連接,獲取當前訂閱Topic存在哪些Broker上,然後直接跟Broker
建立連接通道,開始消費消息。
3.4 雙主雙從集羣搭建流程
3.4.1 防火牆開放端口
在實際工作中,應只對外暴露需要被外部訪問的端口。
- nameserver: 默認使用 9876 端口
- master: 默認使用 10911 端口
- slave: 默認使用 11011 端口
開放命令:
# 開放name server默認端口
firewall-cmd --zone=public --add-port=9876/tcp --permanent
# 開放master默認端口
firewall-cmd --zone=public --add-port=10911/tcp --permanent
# 開放slave默認端口 (當前集羣模式可不開啓)
firewall-cmd --zone=public --add-port=11011/tcp --permanent
# 重啓防火牆
firewall-cmd --reload
# 查看已開放的端口
firewall-cmd --list-ports
3.4.2 配置環境變量
進入配置文件
vim /etc/profile
在 profile 文件末尾增加
#set rocketmq
ROCKETMQ_HOME=/usr/local/rocketmq
PATH=$PATH:$ROCKETMQ_HOME/bin
export ROCKETMQ_HOME PAT
讓配置立刻生效
source /etc/profile
3.4.3 創建消息存儲路徑
mq 獲取到消息後,broker 會默認將消息持久化,持久化目錄默認爲 /home,故可以修改消息存儲路徑
# 創建消息存儲文件夾
cd /usr/local/rocketmq
mkdir store
mkdir ./store/commitlog
mkdir ./store/consumequeue
mkdir ./store/index
創建slave文件夾,避免和master共用一個存儲文件夾,否則會報 Lock failed,MQ already started
# 創建 slave 存儲的文件夾
cd /usr/local/rocketmq
mkdir s_store
mkdir ./s_store/commitlog
mkdir ./s_store/consumequeue
mkdir ./s_store/index
3.4.4 配置 broker 配置文件
rocketmq 已經提供了一些集羣模式的配置文件模板
cd /usr/local/rocketmq/conf
修改集羣配置:
序號 | IP | 角色 | 架構模式 |
1 | 192.168.234.135 | nameserver、brokerserver | Master1、Slave2 |
2 | 192.168.234.139 | nameserver、brokerserver | Master2、Slave1 |
1)Master1 配置(192.168.234.135)
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-a.properties
修改配置文件內容:
#所屬集羣名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此處不同的配置文件填寫的不一樣
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
brokerIP1=192.168.234.135
#nameServer地址,分號分割
namesrvAddr=192.168.234.135:9876;192.168.234.139:9876
#在發送消息時,自動創建服務器不存在的topic,默認創建的隊列數
defaultTopicQueueNums=4
#是否允許 Broker 自動創建Topic,建議線下開啓,線上關閉
autoCreateTopicEnable=true
#是否允許 Broker 自動創建訂閱組,建議線下開啓,線上關閉
autoCreateSubscriptionGroup=true
#Broker 對外服務的監聽端口
listenPort=10911
#刪除文件時間點,默認凌晨 4點
deleteWhen=04
#文件保留時間,默認 48 小時
fileReservedTime=120
#commitLog每個文件的大小默認1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每個文件默認存30W條,根據業務情況調整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#檢測物理文件磁盤空間
diskMaxUsedSpaceRatio=88
#存儲路徑
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存儲路徑
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消費隊列存儲路徑存儲路徑
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存儲路徑
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存儲路徑
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存儲路徑
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 異步複製Master
#- SYNC_MASTER 同步雙寫Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盤方式
#- ASYNC_FLUSH 異步刷盤
#- SYNC_FLUSH 同步刷盤
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#發消息線程池數量
#sendMessageThreadPoolNums=128
#拉消息線程池數量
#pullMessageThreadPoolNums=128
2)Slave2 配置(192.168.234.135)
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-b-s.properties
修改配置文件內容:
#所屬集羣名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此處不同的配置文件填寫的不一樣
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
brokerIP1=192.168.234.135
#nameServer地址,分號分割
namesrvAddr=192.168.234.135:9876;192.168.234.139:9876
#在發送消息時,自動創建服務器不存在的topic,默認創建的隊列數
defaultTopicQueueNums=4
#是否允許 Broker 自動創建Topic,建議線下開啓,線上關閉
autoCreateTopicEnable=true
#是否允許 Broker 自動創建訂閱組,建議線下開啓,線上關閉
autoCreateSubscriptionGroup=true
#Broker 對外服務的監聽端口
listenPort=11011
#刪除文件時間點,默認凌晨 4點
deleteWhen=04
#文件保留時間,默認 48 小時
fileReservedTime=120
#commitLog每個文件的大小默認1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每個文件默認存30W條,根據業務情況調整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#檢測物理文件磁盤空間
diskMaxUsedSpaceRatio=88
#存儲路徑
storePathRootDir=/usr/local/rocketmq/s_store
#commitLog 存儲路徑
storePathCommitLog=/usr/local/rocketmq/s_store/commitlog
#消費隊列存儲路徑存儲路徑
storePathConsumeQueue=/usr/local/rocketmq/s_store/consumequeue
#消息索引存儲路徑
storePathIndex=/usr/local/rocketmq/s_store/index
#checkpoint 文件存儲路徑
storeCheckpoint=/usr/local/rocketmq/s_store/checkpoint
#abort 文件存儲路徑
abortFile=/usr/local/rocketmq/s_store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 異步複製Master
#- SYNC_MASTER 同步雙寫Master
#- SLAVE
brokerRole=SLAVE
#刷盤方式
#- ASYNC_FLUSH 異步刷盤
#- SYNC_FLUSH 同步刷盤
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#發消息線程池數量
#sendMessageThreadPoolNums=128
#拉消息線程池數量
#pullMessageThreadPoolNums=128
3)Master2 配置(192.168.234.139)
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-b.properties
修改配置文件內容:
#所屬集羣名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此處不同的配置文件填寫的不一樣
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
brokerIP1=192.168.234.139
#nameServer地址,分號分割
namesrvAddr=192.168.234.139:9876;192.168.234.135:9876
#在發送消息時,自動創建服務器不存在的topic,默認創建的隊列數
defaultTopicQueueNums=4
#是否允許 Broker 自動創建Topic,建議線下開啓,線上關閉
autoCreateTopicEnable=true
#是否允許 Broker 自動創建訂閱組,建議線下開啓,線上關閉
autoCreateSubscriptionGroup=true
#Broker 對外服務的監聽端口
listenPort=10911
#刪除文件時間點,默認凌晨 4點
deleteWhen=04
#文件保留時間,默認 48 小時
fileReservedTime=120
#commitLog每個文件的大小默認1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每個文件默認存30W條,根據業務情況調整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#檢測物理文件磁盤空間
diskMaxUsedSpaceRatio=88
#存儲路徑
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存儲路徑
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消費隊列存儲路徑存儲路徑
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存儲路徑
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存儲路徑
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存儲路徑
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 異步複製Master
#- SYNC_MASTER 同步雙寫Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盤方式
#- ASYNC_FLUSH 異步刷盤
#- SYNC_FLUSH 同步刷盤
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#發消息線程池數量
#sendMessageThreadPoolNums=128
#拉消息線程池數量
#pullMessageThreadPoolNums=128
4)Slave1 配置(192.168.234.139)
vi /usr/soft/rocketmq/conf/2m-2s-sync/broker-a-s.properties
修改配置文件內容:
#所屬集羣名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此處不同的配置文件填寫的不一樣
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=1
brokerIP1=192.168.234.139
#nameServer地址,分號分割
namesrvAddr=192.168.234.135:9876;192.168.234.139:9876
#在發送消息時,自動創建服務器不存在的topic,默認創建的隊列數
defaultTopicQueueNums=4
#是否允許 Broker 自動創建Topic,建議線下開啓,線上關閉
autoCreateTopicEnable=true
#是否允許 Broker 自動創建訂閱組,建議線下開啓,線上關閉
autoCreateSubscriptionGroup=true
#Broker 對外服務的監聽端口
listenPort=11011
#刪除文件時間點,默認凌晨 4點
deleteWhen=04
#文件保留時間,默認 48 小時
fileReservedTime=120
#commitLog每個文件的大小默認1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每個文件默認存30W條,根據業務情況調整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#檢測物理文件磁盤空間
diskMaxUsedSpaceRatio=88
#存儲路徑
storePathRootDir=/usr/local/rocketmq/s_store
#commitLog 存儲路徑
storePathCommitLog=/usr/local/rocketmq/s_store/commitlog
#消費隊列存儲路徑存儲路徑
storePathConsumeQueue=/usr/local/rocketmq/s_store/consumequeue
#消息索引存儲路徑
storePathIndex=/usr/local/rocketmq/s_store/index
#checkpoint 文件存儲路徑
storeCheckpoint=/usr/local/rocketmq/s_store/checkpoint
#abort 文件存儲路徑
abortFile=/usr/local/rocketmq/s_store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 異步複製Master
#- SYNC_MASTER 同步雙寫Master
#- SLAVE
brokerRole=SLAVE
#刷盤方式
#- ASYNC_FLUSH 異步刷盤
#- SYNC_FLUSH 同步刷盤
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#發消息線程池數量
#sendMessageThreadPoolNums=128
#拉消息線程池數量
#pullMessageThreadPoolNums=128
3.4.5 開放對應端口
根據自己在 Master 文件中設定的 ListenPort 值,計算需要開放的端口:
1) Master1
在 Master1 中定義的 listenPort 爲 10911,故需要暴露:
vip通道端口:10911 - 2 = 10909
HA 高可用端口:10911 + 1 = 10912
firewall-cmd --zone=public --add-port=10909/tcp --permanent
firewall-cmd --zone=public --add-port=10912/tcp --permanent
firewall-cmd --reload
2) Master2
在 Master2 中定義的 listenPort 爲 11011,故需要暴露:
vip通道端口:11011 - 2 = 11009
HA 高可用端口:11011 + 1 = 11012
firewall-cmd --zone=public --add-port=11009/tcp --permanent
firewall-cmd --zone=public --add-port=11012/tcp --permanent
firewall-cmd --reload
3.4.6 創建腳本文件
nameserver 腳本:
# 創建啓動文件
vim name_start.sh
# 添加內容
nohup sh ./bin/mqnamesrv &
echo "nameserver 啓動成功 .."
tail -f ~/logs/rocketmqlogs/namesrv.log
# 創建關閉文件
vim name_stop.sh
# 添加內容
sh bin/mqshutdown namesrv
echo "nameserver 關閉成功..."
echo "再次搜索結果爲:"
sleep 3
jps
在135服務器上:
# 編寫master1啓動腳本
vim b_master1_start.sh
# 添加內容
nohup sh ./bin/mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a.properties &
tail -f ~/logs/rocketmqlogs/broker.log
# 編寫slave2啓動腳本
vim b_slave2_start.sh
# 添加內容
nohup sh ./bin/mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b-s.properties &
tail -f ~/logs/rocketmqlogs/broker.log
在139服務器上:
# 編寫master2啓動腳本
vim b_master2_start.sh
# 添加內容
nohup sh ./bin/mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b.properties &
tail -f ~/logs/rocketmqlogs/broker.log
# 編寫slave1啓動腳本
vim b_slave1_start.sh
# 添加內容
nohup sh ./bin/mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a-s.properties &
tail -f ~/logs/rocketmqlogs/broker.log
3.5 集羣監控平臺
3.5.1 概述
RocketMQ
有一個對其擴展的開源項目incubator-rocketmq-externals,這個項目中有一個子模塊叫rocketmq-console
,這個便是管理控制檯項目了,先將incubator-rocketmq-externals拉到本地,因爲我們需要自己對rocketmq-console
進行編譯打包運行。
3.5.2 編譯和打包
下載項目:
git clone https://github.com/apache/rocketmq-externals
修改配置文件:
打包:
用git打包界面,輸入下面的命令
mvn clean package -Dmaven.test.skip=true
將打包後的jar包放到服務器上,啓動控制檯:
java -jar rocketmq-console-ng-1.0.0.jar
如果服務器的防火牆沒有開放 8080、11099 端口,則需要打開:
firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --zone=public --add-port=11009/tcp --permanent
firewall-cmd --reload
啓動成功後,在瀏覽器訪問 localhost:8080 進入控制檯界面即可
四、RocketMQ 示例代碼
4.1 提供者示例
4.1.1 引入依賴
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
4.1.2 發送同步消息
同步消息即消息生產者給 mq 發送消息,發送後發送線程會阻塞,直到 mq 返回結果。這種發送方式可靠性較高,通常用於重要的消息中,如發送短信等。
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
import java.util.concurrent.TimeUnit;
/**
* 發送同步消息
*/
public class SyncProducer {
public static void main(String[] args) throws Exception {
// 創建消息生產者producer,並指定生產者組名
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 指定 Nameserver 地址
producer.setSendMsgTimeout(6000);
producer.setVipChannelEnabled(false);
producer.setNamesrvAddr("192.168.234.134:9876;192.168.234.139:9876");
// 啓動 producer
producer.start();
for (int i = 0; i < 1; i++) {
// 創建消息對象,指定主體 Topic、Tag 和消息體
Message message = new Message("base", "Tag1", ("Hello World" + i).getBytes());
// 發送消息
SendResult result = producer.send(message);
// 發送狀態、ID以及接受消息的隊列的ID
SendStatus status = result.getSendStatus();
String msgId = result.getMsgId();
int queueId = result.getMessageQueue().getQueueId();
System.out.println("發送狀態: " + status + " 消息ID: " + msgId + " 隊列: " + queueId);
// TimeUnit.SECONDS.sleep(1);
}
// 關閉 producer
producer.shutdown();
}
}
4.1.3 發送異步消息
異步消息通常用在對響應時間敏感的業務場景,即發送端不能容忍長時間地等待 Broker 的響應
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import java.util.concurrent.TimeUnit;
public class AsyncProducer {
public static void main(String[] args) throws Exception {
// 實例化消息生產者Producer
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 指定 Nameserver 地址
producer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
// 啓動Producer實例
producer.start();
producer.setRetryTimesWhenSendAsyncFailed(0);
for (int i = 0; i < 300; i++) {
final int index = i;
// 創建消息,並指定Topic,Tag和消息體
Message msg = new Message("base2", "Tag1", ("Hello World" + i).getBytes());
producer.send(msg, new SendCallback() {
public void onSuccess(SendResult sendResult) {
System.out.println("發送結果:" + sendResult);
}
public void onException(Throwable e) {
System.out.println("發送異常:" + e);
// e.printStackTrace();
}
});
}
Thread.sleep(100000);
// 如果不再發送消息,關閉Producer實例。
producer.shutdown();
}
}
4.1.4 發送單向消息
這種方式主要用在不特別關心發送結果的場景,例如日誌發送
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class OnewayProvider {
public static void main(String[] args) throws Exception {
// 創建消息生產者producer,並指定生產者組名
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 指定 Nameserver 地址
producer.setSendMsgTimeout(6000);
producer.setVipChannelEnabled(false);
producer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
// 啓動 producer
producer.start();
for (int i = 0; i < 100; i++) {
// 創建消息對象,指定主體 Topic、Tag 和消息體
Message message = new Message("base2", "Tag1", ("Hello World" + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 發送單向消息,不返回任何結果
producer.sendOneway(message);
System.out.println("發送單向消息");
// TimeUnit.SECONDS.sleep(1);
}
// 關閉 producer
producer.shutdown();
}
}
4.2 消費者示例
消費者消費分爲廣播模式和負載均衡模式
4.2.1 負載均衡模式(默認)
負載均衡模式即有多個消費者共同消費隊列信息,每個消費者獲得的消息都不同。即隊列中有a,b,c三條消息,消費者1拿到的是a,b,這兩條消息,並進行消費,而消費者2拿到c這條消息,並消費。他們共同協作消費掉隊列中的消息
示例代碼:
通過設置消費方式:
// 設置爲負載均衡模式消費
consumer.setMessageModel(MessageModel.CLUSTERING);
完整代碼:
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import java.util.List;
/**
* @File: Consumer
* @Description:
* @Author: tom
* @Create: 2020-07-01 09:17
**/
public class Consumer {
public static void main(String[] args) throws Exception {
// 創建消費者 consumer,指定消費者組名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
// 指定 Nameserver 地址
consumer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
// 訂閱主題 Topic 和 Tag
consumer.subscribe("base2", "Tag1");
// 設置爲負載均衡模式消費
consumer.setMessageModel(MessageModel.CLUSTERING);
// 設置回調函數,處理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
// 接收消息內容
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : msgs) {
System.out.println(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 啓動消費者consumer
consumer.start();
}
}
4.2.2 廣播模式
廣播模式即只要每個消費者訂閱的topic和tag相同,則每個消費者獲取到的消息都是相同的。如隊列中有 a,b,c 這三條消息,則只要訂閱了的消費者均能拿到 a,b,c 三條消息
示例代碼:
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class Consumer {
public static void main(String[] args) throws Exception {
// 創建消費者 consumer,指定消費者組名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
// 指定 Nameserver 地址
consumer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
// 訂閱主題 Topic 和 Tag
consumer.subscribe("base2", "Tag1");
// 設置回調函數,處理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
// 接收消息內容
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : msgs) {
System.out.println(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 啓動消費者consumer
consumer.start();
}
}
五、消息類型
5.1 順序消息
消息有序是指可以按照消息的發送順序來消費(FIFO),RoecketMQ 可以保證消息有序,分爲分區有序和全局有序。
5.1.1 原理解析
由於消息發送是採用輪詢的方式將消息發送到不同的 queue 中,而消費者消費消息是通過多線程,同時從多個 queue 上拉取消息,這種情況下很難保證消息的發送和消費有序。
如圖,可以看到最後消費者得到的消息是亂序的
Rocketmq又如何保證有序呢?
如上圖,我們只需要保證發送者中需要保證消息有序的消息,都發送到同一個 queue 中去就行了。即小王的所有消息都發送到同一個隊列中,小李的所有消息都發送到同一個隊列中。在代碼實現中,可以通過一個唯一標識選擇一個隊列,之後的消息都發到對應隊列中。
全局有序:只有一個隊列,所有的消息都被推送到這個唯一的隊列中
分區有序:存在多個隊列,但要保證的有序的消息都會被扔到同一個隊列中
當然,這只是簡單的實現了順序消息,實際情況總是比這複雜,如多消費者的情況下,如何保證消息順序
5.1.2 示例代碼
producer:
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import java.util.List;
/**
* @File: Producer
* @Description:
* @Author: tom
* @Create: 2020-07-01 10:37
**/
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
producer.start();
List<OrderStep> orderSteps = OrderStep.buildOrders();
int i = 0;
for (OrderStep orderStep : orderSteps) {
String body = orderStep.toString();
Message msg = new Message("OrderTopic", "Order", "i = " + i, body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
/**
*
* @param mqs 隊列集合
* @param message 消息對象
* @param arg 業務標識的參數
* @return
*/
public MessageQueue select(List<MessageQueue> mqs, Message message, Object arg) {
long orderId = Long.valueOf(String.valueOf(arg));
long index = orderId % mqs.size();
return mqs.get((int) index);
}
}, orderStep.getOrderId());
System.out.println("發送結果:" + sendResult);
i++;
}
producer.shutdown();
}
}
Consumer:
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* @File: Consumer
* @Description:
* @Author: tom
* @Create: 2020-07-01 10:54
**/
public class Consumer {
public static void main(String[] args) throws Exception {
// 創建消費者 consumer,指定消費者組名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
// 指定 Nameserver 地址
consumer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
// 訂閱主題 Topic 和 Tag
consumer.subscribe("OrderTopic", "Order");
// 設置爲負載均衡模式消費
// consumer.setMessageModel(MessageModel.BROADCASTING);
// 處理消息
consumer.registerMessageListener(new MessageListenerOrderly() {
// 對於一個隊列的消息,只採用一個線程去處理
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt msg : list) {
System.out.println("線程名稱:【" + Thread.currentThread().getName() + "】:" + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
// 啓動消費者consumer
consumer.start();
System.out.println("消費者啓動");
}
}
5.2 延時消息
延時消息即消息被提供者發送到隊列中後,不會被立即消費,而是在過了設定的時間後,纔會被消費者消費
5.2.1 常用場景
延時消費大量用於電商項目,例如:用戶要對商品進行付款,若用戶一直沒有付款,則這個訂單就會一直存在於數據庫中變成髒數據。這時候可以發送一條延時消息到隊列,1h後消費者獲取到這條消息,去檢查訂單狀態,若訂單還是在未付款狀態則取消訂單,釋放內存。
5.2.2 實現原理
https://blog.csdn.net/hosaos/article/details/90577732
5.2.3 示例代碼
ScheduledMessageProducer
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
public class ScheduledMessageProducer {
public static void main(String[] args) throws Exception {
// 實例化一個生產者來產生延時消息
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
// 啓動生產者
producer.start();
for (int i = 0; i < 10; i++) {
Message message = new Message("DelayTopic", ("Hello scheduled message " + i).getBytes());
// 設置延時等級3,這個消息將在10s之後發送(現在只支持固定的幾個時間,詳看delayTimeLevel)
message.setDelayTimeLevel(3);
// 發送消息
SendResult sendResult = producer.send(message);
SendStatus status = sendResult.getSendStatus();
System.out.println("發送結果: " + status);
}
// 關閉生產者
producer.shutdown();
}
}
ScheduledMessageConsumer
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* @File: ScheduledMessageConsumer
* @Description:
* @Author: tom
* @Create: 2020-07-01 11:34
**/
public class ScheduledMessageConsumer {
public static void main(String[] args) throws Exception {
// 實例化消費者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer");
consumer.setNamesrvAddr("gtom.top:9876;gtom2.top:9876");
// 訂閱Topics
consumer.subscribe("DelayTopic", "*");
// 註冊消息監聽者
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
for (MessageExt message : messages) {
// Print approximate delay time period
System.out.println("Receive message[msgId=" + message.getMsgId() + "]:延遲時間 = " + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later");
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 啓動消費者
consumer.start();
System.out.println("消費者啓動");
}
}