Tüm Ürünler
Search
Belge Merkezi

ApsaraMQ for RocketMQ:Send and receive transactional messages

Son Güncelleme:Oct 04, 2026

Dağıtık sistemlerde, yerel bir veritabanı işlemi ile aşağı akıştaki mesaj teslimatının ya ikisinin de başarılı olması ya da ikisinin de başarısız olması gerekir. Standart mesajlar bu atomikliği garanti edemez; yerel işlem geri alınsa bile mesaj teslim edilebilir. ApsaraMQ for RocketMQ, bu sorunu çözmek için eXtended Architecture (X/Open XA) benzeri bir dağıtık işlem işleme özelliği sunar. Broker, her mesajı yarı işlenmiş (half-committed) durumda tutar ve yalnızca producer açıkça commit ettiğinde mesajı teslim eder. Bu sayede nihai tutarlılık (eventual consistency) sağlanır.

Bu konuda, HTTP istemci SDK'sını kullanarak transactional mesajların nasıl gönderileceğine ve alınacağına ilişkin Go kod örnekleri sunulmaktadır.

Çalışma mantığı

Bir transactional mesaj aşağıdaki yaşam döngüsünden geçer:

图片1.png

  1. Producer, broker'a bir yarı mesaj (half message) gönderir.

  2. Broker, yarı mesajı kalıcı hale getirir ve alındığını onaylar.

  3. Producer, yerel işlemi yürütür.

  4. Yerel işlem sonucuna göre producer, yarı mesajı commit eder veya geri alır (rollback).

  5. Broker, TransCheckImmunityTime aralığında ne bir commit ne de bir rollback alırsa geri kontrol (back-check) başlatır. Broker, bu kontrolü 24 saat boyunca her 10 saniyede bir tekrarlar.

  6. Mesaj commit edildiğinde, broker mesajı consumer'lara teslim eder.

Transactional mesaj modeli hakkında daha fazla bilgi için bkz. Transactional messages.

Ön koşullar

  • Go HTTP istemci SDK'sının yüklenmiş olması. Ayrıntılar için bkz. Prepare the environment.

  • ApsaraMQ for RocketMQ konsolunda bir örnek, bir konu ve bir consumer grubu oluşturulmuş olması. Ayrıntılar için bkz. Create resources.

  • Alibaba Cloud kimlik bilgilerinizle aşağıdaki ortam değişkenlerinin yapılandırılmış olması. Bir AccessKey çifti edinme hakkında ayrıntılı bilgi için bkz. Create an AccessKey pair.

    • ALIBABA_CLOUD_ACCESS_KEY_ID -- AccessKey ID'niz

    • ALIBABA_CLOUD_ACCESS_KEY_SECRET -- AccessKey secret'iniz

Kullanım notları

Kısıtlama Ayrıntılar
Konu türü özel durumu Her konu yalnızca bir mesaj türünü destekler. Normal mesajlar için oluşturulan bir konu, transactional mesajları göndermek veya almak için kullanılamaz.
TransCheckImmunityTime İlk geri kontrolden önceki gecikmeyi kontrol eder. Geçerli değerler: 10 ila 300 saniye. İlk geri kontrolden sonra broker, 24 saate kadar her 10 saniyede bir yeniden kontrol yapar.
Devam eden işlemler Geri kontrol sırasında yerel bir işlem hala çalışıyorsa, işlemi erken commit etmek veya geri almak yerine bilinmeyen bir durum döndürün. Gereksiz kontrolleri azaltmak için TransCheckImmunityTime değerini, beklenen yerel işlem süresini kapsayacak şekilde ayarlayın.

Transactional mesajları gönderme

Bir transactional mesaj göndermek üç adımdan oluşur:

  1. Yarı mesaj yayınlama -- Mesajı yarı işlenmiş (half-committed) durumda broker'a gönderin.

  2. Commit veya rollback -- Yerel iş mantığına dayanarak işlemi sonuçlandırın.

  3. Geri kontrolleri yönetme -- Commit edilmemiş yarı mesajlar için broker tarafından başlatılan durum sorgularına yanıt verin.

Aşağıdaki örnekte dört transactional mesaj gönderilmektedir. İlk mesaj, yayınlandıktan hemen sonra commit edilir. Kalan üç mesaj ise mesaj özelliği a değerine göre geri kontroller sırasında çözülür:

  • a=1 : İlk geri kontrolde commit edilir.

  • a=2 : İkinci geri kontrolde commit edilir (ConsumedTimes > 1).

  • a=3 : Geri alınır (rollback).

Aşağıdaki yer tutucuları gerçek değerlerinizle değiştirin:

Yer Tutucu Açıklama
${HTTP_ENDPOINT} ApsaraMQ for RocketMQ konsolundaki Instance Details sayfasından alınan HTTP uç noktası
${TOPIC} Konu adı
${INSTANCE_ID} Örnek Kimliği. Örneğin ad alanı yoksa boş bir dize olarak ayarlayın.
${GROUP_ID} Consumer grup kimliği
package main

import (
    "fmt"
    "os"
    "strconv"
    "strings"
    "time"

    "github.com/aliyunmq/mq-http-go-sdk"
    "github.com/gogap/errors"
)

var loopCount = 0

// ProcessError logs acknowledgment errors returned by commit or rollback operations.
func ProcessError(err error) {
    // If a commit or rollback is attempted after the receipt handle expires
    // (either TransCheckImmunityTime or the consumeHalfMessage timeout),
    // the operation fails. This example uses a 10-second timeout for consumeHalfMessage.
    if err == nil {
        return
    }
    fmt.Println(err)
    for _, errAckItem := range err.(errors.ErrCode).Context()["Detail"].([]mq_http_sdk.ErrAckItem) {
        fmt.Printf("\tErrorHandle:%s, ErrorCode:%s, ErrorMsg:%s\n",
            errAckItem.ErrorHandle, errAckItem.ErrorCode, errAckItem.ErrorMsg)
    }
}

// ConsumeHalfMsg processes uncommitted half messages through back-checks.
// Based on the message property "a", it commits, rolls back, or defers the decision.
func ConsumeHalfMsg(mqTransProducer *mq_http_sdk.MQTransProducer) {
    for {
        if loopCount >= 10 {
            return
        }
        loopCount++
        endChan := make(chan int)
        respChan := make(chan mq_http_sdk.ConsumeMessageResponse)
        errChan := make(chan error)
        go func() {
            select {
            case resp := <-respChan:
                {
                    var handles []string
                    fmt.Printf("Consume %d messages---->\n", len(resp.Messages))
                    for _, v := range resp.Messages {
                        handles = append(handles, v.ReceiptHandle)
                        fmt.Printf("\tMessageID: %s, PublishTime: %d, MessageTag: %s\n"+
                            "\tConsumedTimes: %d, FirstConsumeTime: %d, NextConsumeTime: %d\n\tBody: %s\n"+
                            "\tProperties:%s, Key:%s, Timer:%d, Trans:%d\n",
                            v.MessageId, v.PublishTime, v.MessageTag, v.ConsumedTimes,
                            v.FirstConsumeTime, v.NextConsumeTime, v.MessageBody,
                            v.Properties, v.MessageKey, v.StartDeliverTime, v.TransCheckImmunityTime)

                        a, _ := strconv.Atoi(v.Properties["a"])
                        var comRollErr error
                        if a == 1 {
                            // Property a=1: commit the transaction.
                            comRollErr = (*mqTransProducer).Commit(v.ReceiptHandle)
                            fmt.Println("Commit---------->")
                        } else if a == 2 && v.ConsumedTimes > 1 {
                            // Property a=2: commit only after a second back-check.
                            comRollErr = (*mqTransProducer).Commit(v.ReceiptHandle)
                            fmt.Println("Commit---------->")
                        } else if a == 3 {
                            // Property a=3: roll back the transaction.
                            comRollErr = (*mqTransProducer).Rollback(v.ReceiptHandle)
                            fmt.Println("Rollback---------->")
                        } else {
                            // Unknown state: defer to the next back-check.
                            fmt.Println("Unknown---------->")
                        }
                        ProcessError(comRollErr)
                    }
                    endChan <- 1
                }
            case err := <-errChan:
                {
                    if strings.Contains(err.(errors.ErrCode).Error(), "MessageNotExist") {
                        fmt.Println("\nNo new message, continue!")
                    } else {
                        fmt.Println(err)
                        time.Sleep(time.Duration(3) * time.Second)
                    }
                    endChan <- 1
                }
            case <-time.After(35 * time.Second):
                {
                    fmt.Println("Timeout of consumer message ??")
                    return
                }
            }
        }()

        // Poll for uncommitted half messages using long polling.
        // If no half message is available, the request is held on the broker
        // for up to the specified polling duration before returning.
        (*mqTransProducer).ConsumeHalfMessage(respChan, errChan,
            3, // Max messages per batch (valid range: 1-16)
            3, // Long polling duration in seconds (valid range: 1-30)
        )
        <-endChan
    }
}

func main() {
    // Step 1: Configure the client.
    // Get the HTTP endpoint from the Instance Details page in the ApsaraMQ for RocketMQ console.
    endpoint := "${HTTP_ENDPOINT}"
    // Read credentials from environment variables.
    accessKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    secretKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    // Specify the topic. The topic must already exist in the console.
    topic := "${TOPIC}"
    // Specify the instance ID. Set to "" if the instance has no namespace.
    instanceId := "${INSTANCE_ID}"
    // Specify the consumer group ID.
    groupId := "${GROUP_ID}"

    client := mq_http_sdk.NewAliyunMQClient(endpoint, accessKey, secretKey, "")

    mqTransProducer := client.GetTransProducer(instanceId, topic, groupId)

    // Step 2: Start a goroutine to handle back-checks for uncommitted half messages.
    go ConsumeHalfMsg(&mqTransProducer)

    // Step 3: Publish four transactional messages.
    // The first message (i=0) is committed immediately.
    // The remaining three are resolved during back-checks.
    for i := 0; i < 4; i++ {
        msg := mq_http_sdk.PublishMessageRequest{
            MessageBody: "I am transaction msg!",
            Properties:  map[string]string{"a": strconv.Itoa(i)},
        }
        // Set the back-check delay: seconds before the first status check.
        // Valid values: 10-300. After the first check, the broker re-checks every 10s.
        msg.TransCheckImmunityTime = 10

        resp, pubErr := mqTransProducer.PublishMessage(msg)
        if pubErr != nil {
            fmt.Println(pubErr)
            return
        }
        fmt.Printf("Publish ---->\n\tMessageId:%s, BodyMD5:%s, Handle:%s\n",
            resp.MessageId, resp.MessageBodyMD5, resp.ReceiptHandle)
        if i == 0 {
            // Commit the first message immediately using its receipt handle.
            ackErr := mqTransProducer.Commit(resp.ReceiptHandle)
            fmt.Println("Commit---------->")
            ProcessError(ackErr)
        }
    }

    // Wait for the back-check goroutine to complete.
    for loopCount < 10 {
        time.Sleep(time.Duration(1) * time.Second)
    }
}

Transactional mesajlara abonelik

Bir transactional mesaj commit edildiğinde, broker onu diğer mesajlar gibi consumer'lara teslim eder. Abonelik için standart consumer API'sini kullanın.

Aşağıdaki örnek, mesajları uzun oylama (long polling) modunda tüketir ve her partiyi onaylar. Broker, NextConsumeTime süresi dolmadan önce bir onay almazsa mesajı yeniden teslim eder.

package main

import (
    "fmt"
    "os"
    "strings"
    "time"

    "github.com/aliyunmq/mq-http-go-sdk"
    "github.com/gogap/errors"
)

func main() {
    // Configure the client with the same parameters used for the producer.
    endpoint := "${HTTP_ENDPOINT}"
    accessKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    secretKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    topic := "${TOPIC}"
    instanceId := "${INSTANCE_ID}"
    groupId := "${GROUP_ID}"

    client := mq_http_sdk.NewAliyunMQClient(endpoint, accessKey, secretKey, "")

    // Pass an empty string as the message tag to subscribe to all tags.
    mqConsumer := client.GetConsumer(instanceId, topic, groupId, "")

    for {
        endChan := make(chan int)
        respChan := make(chan mq_http_sdk.ConsumeMessageResponse)
        errChan := make(chan error)
        go func() {
            select {
            case resp := <-respChan:
                {
                    var handles []string
                    fmt.Printf("Consume %d messages---->\n", len(resp.Messages))
                    for _, v := range resp.Messages {
                        handles = append(handles, v.ReceiptHandle)
                        fmt.Printf("\tMessageID: %s, PublishTime: %d, MessageTag: %s\n"+
                            "\tConsumedTimes: %d, FirstConsumeTime: %d, NextConsumeTime: %d\n"+
                            "\tBody: %s\n"+
                            "\tProps: %s\n",
                            v.MessageId, v.PublishTime, v.MessageTag, v.ConsumedTimes,
                            v.FirstConsumeTime, v.NextConsumeTime, v.MessageBody, v.Properties)
                    }

                    // Acknowledge messages after processing.
                    // Unacknowledged messages are redelivered after NextConsumeTime.
                    ackerr := mqConsumer.AckMessage(handles)
                    if ackerr != nil {
                        fmt.Println(ackerr)
                        if errAckItems, ok := ackerr.(errors.ErrCode).Context()["Detail"].([]mq_http_sdk.ErrAckItem); ok {
                            for _, errAckItem := range errAckItems {
                                fmt.Printf("\tErrorHandle:%s, ErrorCode:%s, ErrorMsg:%s\n",
                                    errAckItem.ErrorHandle, errAckItem.ErrorCode, errAckItem.ErrorMsg)
                            }
                        } else {
                            fmt.Println("ack err =", ackerr)
                        }
                        time.Sleep(time.Duration(3) * time.Second)
                    } else {
                        fmt.Printf("Ack ---->\n\t%s\n", handles)
                    }

                    endChan <- 1
                }
            case err := <-errChan:
                {
                    if strings.Contains(err.(errors.ErrCode).Error(), "MessageNotExist") {
                        fmt.Println("\nNo new message, continue!")
                    } else {
                        fmt.Println(err)
                        time.Sleep(time.Duration(3) * time.Second)
                    }
                    endChan <- 1
                }
            case <-time.After(35 * time.Second):
                {
                    fmt.Println("Timeout of consumer message ??")
                    endChan <- 1
                }
            }
        }()

        // Consume messages in long polling mode.
        // The default network timeout is 35 seconds.
        mqConsumer.ConsumeMessage(respChan, errChan,
            3, // Max messages per batch (valid range: 1-16)
            3, // Long polling duration in seconds (valid range: 1-30)
        )
        <-endChan
    }
}

İlgili konular