傳送不同型別的訊息

青青子衿wu發表於2020-12-19

傳送不同型別的訊息

普通訊息

RocketMQ提供三種方式來傳送普通訊息:可靠同步傳送、可靠非同步傳送和單向傳送。

可靠同步傳送

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

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

可靠非同步傳送

非同步傳送是指傳送方發出資料後,不等接收方發回響應,接著傳送下個資料包的通訊方式。傳送方通過

回撥介面接收伺服器響應,並對響應結果進行處理。

非同步傳送一般用於鏈路耗時較長,對 RT 響應時間較為敏感的業務場景,例如使用者視訊上傳後通知

啟動轉碼服務,轉碼完成後通知推送轉碼結果等。

單向傳送

單向傳送是指傳送方只負責傳送訊息,不等待伺服器回應且沒有回撥函式觸發,即只傳送請求不

等待應答。

適用於某些耗時非常短,但對可靠性要求並不高的場景,例如日誌收集。

測試程式碼:

匯入依賴

 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
package com.wxit.test;

import com.wxit.OrderApplication;
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;

/**
 * @Author wj
 **/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
public class MessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    //同步訊息
    @Test
    public void testSyncSend(){
        //引數一:topic:tag
        //引數二:訊息體
        //引數三:超時時間
        SendResult result = rocketMQTemplate.syncSend("test-topic-1:tag", "這是一條同步訊息", 10000);
        System.out.println(result);
    }

    //非同步訊息
    @Test
    public void testAsyncSend() throws InterruptedException {
        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(3000000);
    }

    @Test
    //單向訊息
    public void testOneWay(){
        for (int i = 0; i < 10; i++) {
            rocketMQTemplate.sendOneWay("test-topic-1","這是一條單向訊息");
        }
    }

}   

順序訊息

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

 	@Test
    public void testOneWayOrderly(){
        //
        for (int i = 0; i < 10; i++) {
            rocketMQTemplate.sendOneWayOrderly("test-topic-1","這是一條單向訊息","xx");
        }
    }

事務訊息

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

事務訊息互動流程

在這裡插入圖片描述

兩個概念

半事務訊息:暫不能投遞的訊息,傳送方已經成功地將訊息傳送到了RocketMQ服務端,但是服務

端未收到生產者對該訊息的二次確認,此時該訊息被標記成“暫不能投遞”狀態,處於該種狀態下的

訊息即半事務訊息。

訊息回查:由於網路閃斷、生產者應用重啟等原因,導致某條事務訊息的二次確認丟失,

RocketMQ服務端通過掃描發現某條訊息長期處於“半事務訊息”時,需要主動向訊息生產者詢問該

訊息的最終狀態(Commit 或是 Rollback),該詢問過程即訊息回查。

事務訊息傳送步驟

\1. 傳送方將半事務訊息傳送至RocketMQ服務端。

\2. RocketMQ服務端將訊息持久化之後,向傳送方返回Ack確認訊息已經傳送成功,此時訊息為半事

務訊息。

\3. 傳送方開始執行本地事務邏輯。

\4. 傳送方根據本地事務執行結果向服務端提交二次確認(Commit 或是 Rollback),服務端收到

Commit 狀態則將半事務訊息標記為可投遞,訂閱方最終將收到該訊息;服務端收到 Rollback 狀

態則刪除半事務訊息,訂閱方將不會接受該訊息。

事務訊息回查步驟

\1. 在斷網或者是應用重啟的特殊情況下,上述步驟4提交的二次確認最終未到達服務端,經過固定時

間後服務端將對該訊息發起訊息回查。

\2. 傳送方收到訊息回查後,需要檢查對應訊息的本地事務執行的最終結果。

\3. 傳送方根據檢查得到的本地事務的最終狀態再次提交二次確認,服務端仍按照步驟4對半事務訊息

進行操作

程式碼示例

package com.wxit.domain;

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;
}

package com.wxit.service.impl;

import com.wxit.dao.OrderDao;
import com.wxit.dao.TxLogDao;
import com.wxit.domain.Order;
import com.wxit.domain.TxLog;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.UUID;

/**
 * @Author wj
 **/
@Service
public class OrderServiceImpl4 {

    @Autowired
    private OrderDao orderDao;

    @Autowired
    private TxLogDao txLogDao;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    //傳送半事務訊息
    public void createOrderBefore(Order order){

        String txId = UUID.randomUUID().toString();

        rocketMQTemplate.sendMessageInTransaction(
                "tx_producer_group",
                "tx_topic",
                MessageBuilder.withPayload(order).setHeader("txId",txId).build(),
                order
        );
    }

    @Transactional
    public void createOrder(String txId,Order order){
        orderDao.save(order);

        TxLog txLog = new TxLog();
        txLog.setTxId(txId);
        txLog.setDate(new Date());

        txLogDao.save(txLog);
    }
}

package com.wxit.service.impl;

import com.wxit.dao.TxLogDao;
import com.wxit.domain.Order;
import com.wxit.domain.TxLog;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;

/**
 * @Author wj
 **/
@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;
        }
    }
}

訊息消費要注意的細節

@Slf4j
@Service
@RocketMQMessageListener(
        consumerGroup = "shop-user",//消費者分組
        topic = "order-topic",//要消費的主題
        consumeMode = ConsumeMode.CONCURRENTLY, //消費模式:無序和有序
        messageModel = MessageModel.CLUSTERING//訊息模式:廣播和叢集,預設是叢集
)
public class SmsService implements RocketMQListener<Order> {

    @Override
    public void onMessage(Order message) {
        log.info("接收到了一個訂單資訊{},接下來就可以傳送簡訊通知了", message);
    }
}

RocketMQ支援兩種訊息模式:

廣播消費: 每個消費者例項都會收到訊息,也就是一條訊息可以被每個消費者例項處理;

叢集消費: 一條訊息只能被一個消費者例項消費

相關文章