【MQ】RocketMQ 學習

一、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("消費者啓動");
    }
}

 

 

 

 

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