第七章 Rocketmq--消息驅動

今天咱們接着 上一篇 第六章 Sleuth–鏈路追蹤 繼續寫 SpringCloud Alibaba全家桶 , 第七章 Rocketmq--消息驅動,廢話不多說,開始了

7.1 MQ簡介

7.1.1 什麼是MQ

MQ(Message Queue) 是一種跨進程的通信機制,用於傳遞消息。通俗點說,就是一個先進先出的數據結構。

在這裏插入圖片描述

7.1.2 MQ的應用場景

7.1.2.1 異步解耦

最常見的一個場景是用戶註冊後,需要發送註冊郵件和短信通知,以告知用戶註冊成功。傳統的做法如下:

在這裏插入圖片描述

此架構下注冊、郵件、短信三個任務全部完成後,才返回註冊結果到客戶端,用戶才能使用賬號登錄。但是對於用戶來說,註冊功能實際只需要註冊系統存儲用戶的賬戶信息後,該用戶便可以登錄,而後續的註冊短信和郵件不是即時需要關注的步驟。

所以實際當數據寫入註冊系統後,註冊系統就可以把其他的操作放入對應的消息隊列 MQ 中然後馬上返回用戶結果,由消息隊列 MQ 異步地進行這些操作。架構圖如下:

在這裏插入圖片描述

異步解耦是消息隊列 MQ 的主要特點,主要目的是減少請求響應時間和解耦。主要的使用場景就是將比較耗時而且不需要即時(同步)返回結果的操作作爲消息放入消息隊列。同時,由於使用了消息隊列MQ,只要保證消息格式不變,消息的發送方和接收方並不需要彼此聯繫,也不需要受對方的影響,即解耦合。

7.1.2.2 流量削峯

流量削峯也是消息隊列 MQ 的常用場景,一般在秒殺或團隊搶購(高併發)活動中使用廣泛。

在秒殺或團隊搶購活動中,由於用戶請求量較大,導致流量暴增,秒殺的應用在處理如此大量的訪問流量後,下游的通知系統無法承載海量的調用量,甚至會導致系統崩潰等問題而發生漏通知的情況。爲解決這些問題,可在應用和下游通知系統之間加入消息隊列 MQ。

在這裏插入圖片描述

秒殺處理流程如下所述:

  1. 用戶發起海量秒殺請求到秒殺業務處理系統。
  2. 秒殺處理系統按照秒殺處理邏輯將滿足秒殺條件的請求發送至消息隊列 MQ。
  3. 下游的通知系統訂閱消息隊列 MQ 的秒殺相關消息,再將秒殺成功的消息發送到相應用戶。
  4. 用戶收到秒殺成功的通知。

7.1.3 常見的MQ產品*

目前業界有很多MQ產品,比較出名的有下面這些:

  • ZeroMQ
    號稱最快的消息隊列系統,尤其針對大吞吐量的需求場景。擴展性好,開發比較靈活,採用C語言
    實現,實際上只是一個socket庫的重新封裝,如果做爲消息隊列使用,需要開發大量的代碼。
    ZeroMQ僅提供非持久性的隊列,也就是說如果down機,數據將會丟失。

  • RabbitMQ
    使用erlang語言開發,性能較好,適合於企業級的開發。但是不利於做二次開發和維護。

  • ActiveMQ
    歷史悠久的Apache開源項目。已經在很多產品中得到應用,實現了JMS1.1規範,可以和springjms
    輕鬆融合,實現了多種協議,支持持久化到數據庫,對隊列數較多的情況支持不好。

  • RocketMQ
    阿里巴巴的MQ中間件,由java語言開發,性能非常好,能夠撐住雙十一的大流量,而且使用起來
    很簡單。

  • Kafka
    Kafka是Apache下的一個子項目,是一個高性能跨語言分佈式Publish/Subscribe消息隊列系統,
    相對於ActiveMQ是一個非常輕量級的消息系統,除了性能非常好之外,還是一個工作良好的分佈
    式系統。

7.2 RocketMQ入門

RocketMQ是阿里巴巴開源的分佈式消息中間件,現在是Apache的一個頂級項目。在阿里內部使用
非常廣泛,已經經過了"雙11"這種萬億級的消息流轉。

7.2.1 RocketMQ環境搭建

接下來我們先在linux平臺下安裝一個RocketMQ的服務

7.2.1.1 環境準備

下載RocketMQ
http://rocketmq.apache.org/release_notes/release-notes-4.4.0/

環境要求

  • Linux 64位操作系統
  • 64bit JDK 1.8+
7.2.1.2 安裝RocketMQ

1 上傳文件到Linux系統

[root@spiritmark rocketmq]# ls /usr/local/src/
rocketmq-all-4.4.0-bin-release.zip

2 解壓到安裝目錄

[root@spiritmark src]# unzip rocketmq-all-4.4.0-bin-release.zip
[root@spiritmark src]# mv rocketmq-all-4.4.0-bin-release ../rocketmq
7.2.1.3 啓動RocketMQ

1. 切換到安裝目錄

[root@spiritmark rocketmq]# ls
benchmark bin conf lib LICENSE NOTICE README.md

2 啓動NameServer

[root@spiritmark rocketmq]# nohup ./bin/mqnamesrv &
[1] 1467
# 只要進程不報錯,就應該是啓動成功了,可以查看一下日誌
[root@spiritmark rocketmq]# tail -f /root/logs/rocketmqlogs/namesrv.log

3 啓動Broker

# 編輯bin/runbroker.sh 和 bin/runserver.sh文件,修改裏面的
# JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
# 爲JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
[root@spiritmark rocketmq]# nohup bin/mqbroker -n localhost:9876 &
[root@spiritmark rocketmq]# tail -f /root/logs/rocketmqlogs/broker.log
7.2.1.4 測試RocketMQ

1 測試消息發送

[root@spiritmark rocketmq]# export NAMESRV_ADDR=localhost:9876
[root@spiritmark rocketmq]# bin/tools.sh
org.apache.rocketmq.example.quickstart.Producer

2 測試消息接收

[root@spiritmark rocketmq]# export NAMESRV_ADDR=localhost:9876
[root@spiritmark rocketmq]# bin/tools.sh
org.apache.rocketmq.example.quickstart.Consumer
7.2.1.5 關閉RocketMQ
[root@heima rocketmq]# bin/mqshutdown broker
[root@heima rocketmq]# bin/mqshutdown namesrv

7.2.2 RocketMQ的架構及概念

在這裏插入圖片描述
如上圖所示,整體可以分成4個角色,分別是:NameServerBrokerProducerConsumer

  • Broker(郵遞員)
    Broker是RocketMQ的核心,負責消息的接收,存儲,投遞等功能

  • NameServer(郵局)
    消息隊列的協調者,Broker向它註冊路由信息,同時Producer和Consumer向其獲取路由信息

  • Producer(寄件人)
    消息的生產者,需要從NameServer獲取Broker信息,然後與Broker建立連接,向Broker發送消

  • Consumer(收件人)
    消息的消費者,需要從NameServer獲取Broker信息,然後與Broker建立連接,從Broker獲取消

  • Topic(地區)
    用來區分不同類型的消息,發送和接收消息前都需要先創建Topic,針對Topic來發送和接收消息

  • Message Queue(郵件)

爲了提高性能和吞吐量,引入了Message Queue,一個Topic可以設置一個或多個Message
Queue,這樣消息就可以並行往各個Message Queue發送消息,消費者也可以並行的從多個
Message Queue讀取消息

  • Message
    Message 是消息的載體。
  • Producer Group
    生產者組,簡單來說就是多個發送同一類消息的生產者稱之爲一個生產者組。
  • Consumer Group
    消費者組,消費同一類消息的多個 consumer 實例組成一個消費者組。

7.2.3 RocketMQ控制檯安裝

1 下載

# 在git上下載下面的工程 rocketmq-console-1.0.0
https://github.com/apache/rocketmq-externals/releases

2 修改配置文件

# 修改配置文件 rocketmq-console\src\main\resources\application.properties
#項目啓動後的端口號
server.port=7777 
 #nameserv的地址,注意防火牆要開啓 9876端口
rocketmq.config.namesrvAddr=192.168.109.131:9876

3 打成jar包,並啓動

# 進入控制檯項目,將工程打成jar包
mvn clean package -Dmaven.test.skip=true
# 啓動控制檯
java -jar target/rocketmq-console-ng-1.0.0.jar

4 訪問控制檯

在這裏插入圖片描述

7.3 消息發送和接收演示


接下來我們使用Java代碼來演示消息的發送和接收

   <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>

7.3.1 發送消息

消息發送步驟:

  1. 創建消息生產者, 指定生產者所屬的組名
  2. 指定Nameserver地址
  3. 啓動生產者
  4. 創建消息對象,指定主題、標籤和消息體
  5. 發送消息
  6. 關閉生產者
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.common.message.Message;
import org.apache.rocketmq.spring.core.RocketMQTemplate;

public class RocketMQSendMessageTest {


    //發送消息
    public static void main(String[] args) throws Exception {
        //1.創建消息生產者,並且設置生產組名
        DefaultMQProducer producer = new DefaultMQProducer("myproducer-group");

        //2 爲生產者設置NameServer的地址
        producer.setNamesrvAddr("192.168.109.131:9876");

        //3 啓動生產者
        producer.start();

        //4 構建消息對象,主要是設置消息的主題 標籤 內容
        Message message = new Message("myTopic", "myTag", ("Test RocketMQ Message").getBytes());

        //5 發送消息 第二個參數代表超時時間
        SendResult result = producer.send(message, 10000);
        System.out.println(result);

        //6 關閉生產者
        producer.shutdown();

    }
}

7.3.2 接收消息

消息接收步驟:

  1. 創建消息消費者, 指定消費者所屬的組名
  2. 指定Nameserver地址
  3. 指定消費者訂閱的主題和標籤
  4. 設置回調函數,編寫處理消息的方法
  5. 啓動消息消費者

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 RocketMQReceiveMessageTest {

    //接收消息
    public static void main(String[] args) throws Exception {

        //1 創建消費者,並且爲其指定消費者組名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myconsumer-group");

        //2 爲消費者設置NameServer的地址
        consumer.setNamesrvAddr("192.168.109.131:9876");

        //3 指定消費者訂閱的主題和標籤
        consumer.subscribe("myTopic", "*");

        //4 設置一個回調函數,並在函數中編寫接收到消息之後的處理方法
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            //處理獲取到的消息
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                //消費邏輯
                System.out.println("Message===>" + list);

                //返回消費成功狀態
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //5 啓動消費者
        consumer.start();
        System.out.println("啓動消費者成功了");
    }
}

7.4 案例

接下來我們模擬一種場景: 下單成功之後,向下單用戶發送短信。設計圖如下:

在這裏插入圖片描述

7.4.1 訂單微服務發送消息

1 在shop-order 中添加rocketmq的依賴

    <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
        </dependency>

2 添加配置

#rocketmq
rocketmq:
  name-server: 192.168.109.131:9876   #rocketMQ服務的地址
  producer:
    group: shop-order # 生產者組

3 編寫測試代碼

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class OrderController2 {

    @Autowired
    private OrderService orderService;

    @Autowired
    private ProductService productService;

    //下單
    @RequestMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info("接收到{}號商品的下單請求,接下來調用商品微服務查詢此商品信息", pid);

        //調用商品微服務,查詢商品信息
        Product product = productService.findByPid(pid);

        //模擬調用商品微服務需要2s的時間
        try {
            Thread.sleep(2000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        log.info("查詢到{}號商品的信息,內容是:{}", pid, JSON.toJSONString(product));

        //下單(創建訂單)
        Order order = new Order();
        order.setUid(1);
        order.setUsername("測試用戶");
        order.setPid(pid);
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);

        //爲了不產生大量的額垃圾數據,暫時不保存訂單入庫
        //orderService.createOrder(order);

        log.info("創建訂單成功,訂單信息爲{}", JSON.toJSONString(order));
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return order;
    }

    //測試高併發
    @RequestMapping("/order/message")
    public String message() {
        return "測試高併發";
    }
}

7.4.2 用戶微服務訂閱消息

1 修改shop-user 模塊配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-alibaba</artifactId>
        <groupId>com.spiritmark</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>shop-user</artifactId>

    <dependencies>
        <!--springboot-web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--shop-common-->
        <dependency>
            <groupId>com.spiritmark</groupId>
            <artifactId>shop-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
        </dependency>

    </dependencies>

</project>

2 修改主類

   @SpringBootApplication
    @EnableDiscoveryClient
    public class UserApplication {
        public static void main(String[] args) {
            SpringApplication.run(UserApplication.class, args);
        }
    }

3 修改配置文件

server:
  port: 8071
spring:
  application:
    name: service-user
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///shop?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: root
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
#rocketmq
rocketmq:
  name-server: 192.168.109.131:9876

4 編寫消息接收服務


@Slf4j
@Service("shopSmsService")
//consumerGroup-消費者組名  topic-要消費的主題
@RocketMQMessageListener(
        consumerGroup = "shop-user", //消費者組名
        topic = "order-topic",//消費主題
        consumeMode = ConsumeMode.CONCURRENTLY,//消費模式,指定是否順序消費 CONCURRENTLY(同步,默認) ORDERLY(順序)
        messageModel = MessageModel.CLUSTERING//消息模式 BROADCASTING(廣播)  CLUSTERING(集羣,默認)
)
public class SmsService implements RocketMQListener<Order> {

@Override
public void onMessage(Order order) {
	log.info("收到一個訂單信息{},接下來發送短信", JSON.toJSONString(order));
}

	}

}

5 啓動服務,執行下單操作,觀看後臺輸出

7.5 發送不同類型的消息

7.5.1 普通消息

RocketMQ提供三種方式來發送普通消息:可靠同步發送、可靠異步發送和單向發送。

可靠同步發送

同步發送是指消息發送方發出數據後,會在收到接收方發回響應之後才發下一個數據包的通訊方
式。

此種方式應用場景非常廣泛,例如重要通知郵件、報名短信通知、營銷短信系統等。

可靠異步發送

異步發送是指發送方發出數據後,不等接收方發回響應,接着發送下個數據包的通訊方式。發送
方通過回調接口接收服務器響應,並對響應結果進行處理。
異步發送一般用於鏈路耗時較長,對 RT 響應時間較爲敏感的業務場景,例如用戶視頻上傳後通知
啓動轉碼服務,轉碼完成後通知推送轉碼結果等。

單向發送

單向發送是指發送方只負責發送消息,不等待服務器迴應且沒有回調函數觸發,即只發送請求不
等待應答。
適用於某些耗時非常短,但對可靠性要求並不高的場景,例如日誌收集。

   <!--依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
public class MessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;



   //同步消息
    @Test
    public void testSyncSend() {
//參數一: topic, 如果想添加tag 可以使用"topic:tag"的寫法
//參數二: 消息內容
        SendResult sendResult =
                rocketMQTemplate.syncSend("test-topic-1", "這是一條同步消息");
        System.out.println(sendResult);
    }



    //異步消息
    @Test
    public void testAsyncSend() throws InterruptedException {
        //參數一: topic:tag
        //參數二: 消息體
        //參數三: 回調
        rocketMQTemplate.asyncSend("test-topic-1", "這是一條異步消息", new SendCallback() {
            //成功響應的回調
            @Override
            public void onSuccess(SendResult result) {
                System.out.println(result);
            }

            //異常響應的回調
            @Override
            public void onException(Throwable throwable) {
                System.out.println(throwable);
            }
        });
        System.out.println("==================");
        Thread.sleep(300000000);
    }


//單向消息
@Test
public void testOneWay() {
		rocketMQTemplate.sendOneWay("test-topic-1", "這是一條單向消息");
}

三種發送方式的對比

發送方式 發送 TPS 發送結果反饋 可靠性
異步發送 不丟失
異步發送 不丟失
單向發送 最快 可能丟失

7.5.2 順序消息

順序消息是消息隊列提供的一種嚴格按照順序來發布和消費的消息類型。

在這裏插入圖片描述

  //同步順序消息[異步順序 單向順序寫法類似]
    public void testSyncSendOrderly() {
//第三個參數用於隊列的選擇
        rocketMQTemplate.syncSendOrderly("test-topic-1", "這是一條異步順序消息",
                "xxxx");
    }

7.5.3 事務消息

RocketMQ提供了事務消息,通過事務消息就能達到分佈式事務的最終一致。

事務消息交互流程:

在這裏插入圖片描述

兩個概念:

  • 半事務消息:暫不能投遞的消息,發送方已經成功地將消息發送到了RocketMQ服務端,但是服務
    端未收到生產者對該消息的二次確認,此時該消息被標記成“暫不能投遞”狀態,處於該種狀態下的
    消息即半事務消息。

  • 消息回查:由於網絡閃斷、生產者應用重啓等原因,導致某條事務消息的二次確認丟失,
    RocketMQ服務端通過掃描發現某條消息長期處於“半事務消息”時,需要主動向消息生產者詢問該
    消息的最終狀態(Commit 或是 Rollback),該詢問過程即消息回查。

事務消息發送步驟:

  1. 發送方將半事務消息發送至RocketMQ服務端。
  2. RocketMQ服務端將消息持久化之後,向發送方返回Ack確認消息已經發送成功,此時消息爲半事
    務消息。
  3. 發送方開始執行本地事務邏輯。
  4. 發送方根據本地事務執行結果向服務端提交二次確認(Commit 或是 Rollback),服務端收到
    Commit 狀態則將半事務消息標記爲可投遞,訂閱方最終將收到該消息;服務端收到 Rollback 狀
    態則刪除半事務消息,訂閱方將不會接受該消息。

事務消息回查步驟:

  1. 在斷網或者是應用重啓的特殊情況下,上述步驟4提交的二次確認最終未到達服務端,經過固定時
    間後服務端將對該消息發起消息回查。
  2. 發送方收到消息回查後,需要檢查對應消息的本地事務執行的最終結果。
  3. 發送方根據檢查得到的本地事務的最終狀態再次提交二次確認,服務端仍按照步驟4對半事務消息
    進行操作。
import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;

//消息事物狀態記錄
@Entity(name = "shop_txlog")
@Data
public class TxLog {
    @Id
    private String txId;
    private Date date;
}


//@RestController
@Slf4j
public class OrderController4 {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private OrderServiceImpl4 orderService;

    @Autowired
    private ProductService productService;

    //下單--fegin
    @RequestMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info("接收到{}號商品的下單請求,接下來調用商品微服務查詢此商品信息", pid);

        //調用商品微服務,查詢商品信息
        Product product = productService.findByPid(pid);

        if (product.getPid() == -100) {
            Order order = new Order();
            order.setOid(-100L);
            order.setPname("下單失敗");
            return order;
        }

        log.info("查詢到{}號商品的信息,內容是:{}", pid, JSON.toJSONString(product));

        //下單(創建訂單)
        Order order = new Order();
        order.setUid(1);
        order.setUsername("測試用戶");
        order.setPid(pid);
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);

        orderService.createOrderBefore(order);

        log.info("創建訂單成功,訂單信息爲{}", JSON.toJSONString(order));

        return order;
    }


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
@RocketMQTransactionListener(txProducerGroup = "tx_producer_group")
public class OrderServiceImpl4Listener implements RocketMQLocalTransactionListener {

    @Autowired
    private OrderServiceImpl4 orderServiceImpl4;


    @Autowired
    private TxLogDao txLogDao;

    //執行本地事物
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {

        String txId = (String) msg.getHeaders().get("txId");

        try {
            //本地事物
            Order order = (Order) arg;
            orderServiceImpl4.createOrder(txId,order);

            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    //消息回查
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        String txId = (String) msg.getHeaders().get("txId");
        TxLog txLog = txLogDao.findById(txId).get();

        if (txLog != null){
            //本地事物(訂單)成功了
            return RocketMQLocalTransactionState.COMMIT;
        }else {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

7.6 消息消費要注意的細節

@Slf4j
@Service("shopSmsService")
//consumerGroup-消費者組名  topic-要消費的主題
@RocketMQMessageListener(
        consumerGroup = "shop-user", //消費者組名
        topic = "order-topic",//消費主題
        consumeMode = ConsumeMode.CONCURRENTLY,//消費模式,指定是否順序消費 CONCURRENTLY(同步,默認) ORDERLY(順序)
        messageModel = MessageModel.CLUSTERING//消息模式 BROADCASTING(廣播)  CLUSTERING(集羣,默認)
)
public class SmsService implements RocketMQListener<Order> {}

RocketMQ支持兩種消息模式:

  • 廣播消費: 每個消費者實例都會收到消息,也就是一條消息可以被每個消費者實例處理;
  • 集羣消費: 一條消息只能被一個消費者實例消費
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章