MQ 入門實踐

曹建濤發表於2021-04-19

MQ

Message Queue,訊息佇列,FIFO 結構。

例如電商平臺,在使用者支付訂單後執行對應的操作;

優點:

  • 非同步
  • 削峰
  • 解耦

缺點

  • 增加系統複雜性
  • 資料一致性
  • 可用性

JMS

Java Message Service,Java訊息服務,類似 JDBC 提供了訪問資料庫的標準,JMS 也制定了一套系統間訊息通訊的規範;

區別於 JDBC,JDK 原生包中並未定義 JMS 相關介面。

  1. ConnectionFactory

  2. Connection

  3. Destination

  4. Session

  5. MessageConsumer

  6. MessageProducer

  7. 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

是一個佇列模型的訊息中介軟體,具有高效能、高可靠、高實時、分散式特點。

架構圖示

  1. Name Server

    名稱伺服器,類似於 Zookeeper 註冊中心,提供 Broker 發現;

  2. Broker

    RocketMQ 的核心元件,絕大部分工作都在 Broker 中完成,接收請求,處理消費,訊息持久化等;

  3. Producer

    訊息生產方;

  4. Consumer

    訊息消費方;

快速開始

安裝後,依次啟動 nameserver 和 broker,可以用 mqadmin 管理主題、叢集和 broker 等資訊;

https://segmentfault.com/a/1190000017841402

新增依賴;

<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

重複消費

產生原因:

  1. 生產者重複投遞;
  2. 訊息佇列異常;
  3. 消費者異常消費;

怎麼解決重複消費的問題,換句話怎麼保證訊息消費的冪等性

通常基於本地訊息表的方案實現,訊息處理過便不再處理。

順序訊息

訊息錯亂的原因:

  1. 一個訊息佇列 queue,多個 consumer 消費;
  2. 一個 queue 對應一個 consumer,但是 consumer 多執行緒消費;

要保證訊息的順序消費,有三個關鍵點:

  1. 訊息順序傳送
  2. 訊息順序儲存
  3. 訊息順序消費

參考 RocketMq 中的 MessageQueueSelector 和 MessageListenerOrderly。

分散式事務

在分散式系統中,一個事務由多個本地事務組成。這裡介紹一個基於 MQ 的分散式事務解決方案。

通過 broker 的 HA 高可用,和定時回查 prepare 訊息的狀態,來保證最終一致性。

相關文章