MQ
Message Queue,訊息佇列,FIFO 結構。
例如電商平臺,在使用者支付訂單後執行對應的操作;
優點:
- 非同步
- 削峰
- 解耦
缺點
- 增加系統複雜性
- 資料一致性
- 可用性
JMS
Java Message Service,Java訊息服務,類似 JDBC 提供了訪問資料庫的標準,JMS 也制定了一套系統間訊息通訊的規範;
區別於 JDBC,JDK 原生包中並未定義 JMS 相關介面。
-
ConnectionFactory
-
Connection
-
Destination
-
Session
-
MessageConsumer
-
MessageProducer
-
Message
協作方式圖示為;
業界產品
ActiveMQ | RabbitMQ | RocketMQ | kafka | |
---|---|---|---|---|
單機吞吐量 | 萬級 | 萬級 | 10 萬級 | 10 萬級 |
可用性 | 高 | 高 | 非常高 | 非常高 |
可靠性 | 較低概率丟失訊息 | 基本不丟 | 可以做到 0 丟失 | 可以做到 0 丟失 |
功能支援 | 較為完善 | 基於 erlang,併發強,效能好,延時低 | 分散式,擴充性好,支援分散式事務 | 較為簡單,主要應用與大資料實時計算,日誌採集等 |
社群活躍度 | 低 | 中 | 高 | 高 |
ActiveMQ
作為 Apache 下的開源專案,完全支援 JMS 規範。並且 Spring Boot 內建了 ActiveMQ 的自動化配置,作為入門再適合不過。
快速開始
新增依賴;
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
訊息傳送;
// 1. 建立連線工廠
ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
// 2. 工廠建立連線
Connection connection = factory.createConnection();
// 3. 啟動連線
connection.start();
// 4. 建立連線會話session,第一個引數為是否在事務中處理,第二個引數為應答模式
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 5. 根據session建立訊息佇列目的地
Destination queue = session.createQueue("test-queue");
// 6. 根據session和目的地queue建立生產者
MessageProducer producer = session.createProducer(queue);
// 7. 根據session建立訊息實體
Message message = session.createTextMessage("hello world!");
// 8. 通過生產者producer傳送訊息實體
producer.send(message);
// 9. 關閉連線
connection.close();
Spring Boot 整合
自動注入參考:org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionFactoryConfiguration.SimpleConnectionFactoryConfiguration
新增依賴;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
新增 yaml 配置;
spring:
activemq:
broker-url: tcp://localhost:61616
jms:
#訊息模式 true:廣播(Topic),false:佇列(Queue),預設時false
pub-sub-domain: true
收發訊息;
@Autowired
private JmsTemplate jmsTemplate;
// 接收訊息
@JmsListener(destination = "test")
public void receiveMsg(String msg) {
System.out.println(msg);
}
// 傳送訊息
public void sendMsg(String destination, String msg) {
jmsTemplate.convertAndSend(destination, msg);
}
高可用
基於 zookeeper 實現主從架構,修改 activemq.xml 節點 persistenceAdapter 配置;
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/levelDB"
replicas="3"
bind="tcp://0.0.0.0:0"
zkAddress="172.17.0.4:2181,172.17.0.4:2182,172.17.0.4:2183"
zkPath="/activemq/leveldb-stores"
hostname="localhost"
/>
</persistenceAdapter>
broker 地址為:failover:(tcp://192.168.4.19:61616,tcp://192.168.4.19:61617,tcp://192.168.4.19:61618)?randomize=false
負載均衡
在高可用叢集節點 activemq.xml 新增節點 networkConnectors;
<networkConnectors>
<networkConnector uri="static:(tcp://192.168.0.103:61616,tcp://192.168.0.103:61617,tcp://192.168.0.103:61618)" duplex="false"/>
</networkConnectors>
更多詳細資訊可參考:https://blog.csdn.net/haoyuyang/article/details/53931710
叢集消費
由於釋出訂閱模式,所有訂閱者都會接收到訊息,在生產環境,消費者叢集會產生訊息重複消費問題。
ActiveMQ 提供 VirtualTopic 功能,解決多消費端接收同一條訊息的問題。於生產者而言,VirtualTopic 就是一個 topic,對消費而言則是 queue。
在 activemq.xml 新增節點 destinationInterceptors;
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<virtualTopic name="testTopic" prefix="consumer.*." selectorAware="false"/>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
生產者正常往 testTopic 中傳送訊息,訂閱者可修改訂閱主題為類似 consumer.A.testTopic 這樣來消費。
更多詳細資訊可參考:https://blog.csdn.net/java_collect/article/details/82154829
RocketMQ
是一個佇列模型的訊息中介軟體,具有高效能、高可靠、高實時、分散式特點。
架構圖示
-
Name Server
名稱伺服器,類似於 Zookeeper 註冊中心,提供 Broker 發現;
-
Broker
RocketMQ 的核心元件,絕大部分工作都在 Broker 中完成,接收請求,處理消費,訊息持久化等;
-
Producer
訊息生產方;
-
Consumer
訊息消費方;
快速開始
安裝後,依次啟動 nameserver 和 broker,可以用 mqadmin 管理主題、叢集和 broker 等資訊;
新增依賴;
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.5.2</version>
</dependency>
訊息傳送;
DefaultMQProducer producer = new DefaultMQProducer("producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.setInstanceName("producer");
producer.start();
Message msg = new Message(
"producer-topic",
"msg",
"hello world".getBytes()
);
//msg.setDelayTimeLevel(1);
SendResult sendResult = producer.send(msg);
System.out.println(sendResult.toString());
producer.shutdown();
delayLevel 從 1 開始預設依次是:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h。
參考 org.apache.rocketmq.store.schedule.ScheduleMessageService#parseDelayLevel。
訊息接收;
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.setInstanceName("consumer");
consumer.subscribe("producer-topic", "msg");
consumer.registerMessageListener((MessageListenerConcurrently) (list, consumeConcurrentlyContext) -> {
for (MessageExt msg : list) {
System.out.println(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
.\mqadmin.cmd sendMessage -t producer-topic -c msg -p "hello rocketmq" -n localhost:9876
Spring Boot 整合
新增依賴;
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
新增 yaml 配置;
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: producer
傳送訊息;
@Autowired
private RocketMQTemplate mqTemplate;
public void sendMessage(String topic, String tag, String message) {
SendResult result = mqTemplate.syncSend(topic + ":" + tag, message);
System.out.println(JSON.toJSONString(result));
}
接收訊息;
@Component
@RocketMQMessageListener(consumerGroup = "consumer", topic = "topic-test", selectorExpression = "tag-test")
public class MsgListener implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println(message);
}
}
Console 控制檯
RocketMQ 擴充包提供了管理控制檯;
https://github.com/apache/rocketmq-externals/tree/master/rocketmq-console
重複消費
產生原因:
- 生產者重複投遞;
- 訊息佇列異常;
- 消費者異常消費;
怎麼解決重複消費的問題,換句話怎麼保證訊息消費的冪等性。
通常基於本地訊息表的方案實現,訊息處理過便不再處理。
順序訊息
訊息錯亂的原因:
- 一個訊息佇列 queue,多個 consumer 消費;
- 一個 queue 對應一個 consumer,但是 consumer 多執行緒消費;
要保證訊息的順序消費,有三個關鍵點:
- 訊息順序傳送
- 訊息順序儲存
- 訊息順序消費
參考 RocketMq 中的 MessageQueueSelector 和 MessageListenerOrderly。
分散式事務
在分散式系統中,一個事務由多個本地事務組成。這裡介紹一個基於 MQ 的分散式事務解決方案。
通過 broker 的 HA 高可用,和定時回查 prepare 訊息的狀態,來保證最終一致性。