ApsaraMQ for RocketMQ, dağıtılmış işlemler arasında nihai tutarlılığı sağlamak için eXtended Architecture (X/Open XA) protokolüne benzer şekilde işlemsel mesajlar kullanır. Aşağıdaki Node.js örnek kod, HTTP istemci SDK'sı aracılığıyla işlemsel mesajların nasıl gönderilip alınacağını gösterir.
İşlemsel mesajların çalışma şekli
İşlemsel bir mesaj, yerel bir işlemi mesaj teslimatına bağlayan iki aşamalı commit sürecinden geçer. Aşağıdaki adımlar, üretici, broker ve tüketiciler arasındaki tam etkileşimi açıklar:
Üretici yarım mesajı broker'a gönderir. Yarım mesaj depolanır ancak tüketiciler tarafından görünmez kalır.
Broker yarım mesajı kalıcı hale getirir ve bir onay numarası içeren bir onay (ACK) döndürür.
Üretici yerel işlemi yürütür (örneğin, bir veritabanını günceller).
Yerel işlemin sonucuna bağlı olarak üretici, onay numarasını kullanarak yarım mesajı commit eder veya geri alır.
Üretici yarım mesajı commit ederse broker bunu teslim edilebilir olarak işaretler. Tüketiciler artık bu mesajı alabilir.
Üretici yarım mesajı geri alırsa broker mesajı atar. Tüketiciler mesajı asla görmez.
Broker bir commit veya geri alma kararı almazsa (ağ arızası veya uygulama yeniden başlatma nedeniyle),
TransCheckImmunityTimearalığı dolduktan sonra bir işlem kontrolü başlatır.Üretici kontrol isteğini alır ve yerel işlem durumunu yeniden değerlendirir.
Üretici commit veya geri alma kararını broker'a gönderir. Karar hala bilinmiyorsa broker 24 saat boyunca her 10 saniyede bir yeniden dener.

Daha fazla bilgi için bkz. Transactional messages.
HTTP SDK ile ilgili hususlar
HTTP istemci SDK'sı işlem kontrolleri için çekme tabanlı bir model kullanır. Broker'ın kontrol isteklerini üreticiye ittiği yerel RocketMQ SDK'larının aksine HTTP SDK'sı, üreticinin consumeHalfMessage ile commit edilmemiş yarım mesajları yoklamasını gerektirir. Güvenilir işlem çözümlemesi sağlamak için bu yoklama döngüsünü ayrı bir işlemde veya iş parçacığında çalıştırın.
Ön koşullar
Node.js SDK'yı yükleyin. Daha fazla bilgi için bkz. Prepare the environment.
ApsaraMQ for RocketMQ konsolunda gerekli kaynakları oluşturun: bir örnek, bir konu ve bir tüketici grubu. Daha fazla bilgi için bkz. Create resources.
Alibaba Cloud hesabınız için bir AccessKey çifti edinin. Daha fazla bilgi için bkz. Create an AccessKey pair.
Temel parametreler
Örnek kodu çalıştırmadan önce aşağıdaki yer tutucuları gerçek değerlerinizle değiştirin:
| Yer Tutucu | Açıklama | Nerede Bulunur |
|---|---|---|
${HTTP_ENDPOINT} |
Örneğinizin HTTP uç noktası | ApsaraMQ for RocketMQ konsolundaki Instance Details sayfası > HTTP Endpoint bölümü |
${TOPIC} |
Konu adı | ApsaraMQ for RocketMQ konsolunda oluşturuldu |
${GROUP_ID} |
Tüketici grubu kimliği | ApsaraMQ for RocketMQ konsolunda oluşturuldu |
${INSTANCE_ID} |
Örnek kimliği (örneğin ad alanı yoksa null veya "" olarak ayarlayın) |
ApsaraMQ for RocketMQ konsolundaki Instance Details sayfası |
AccessKey kimlik bilgileri ortam değişkenlerinden okunur:
ALIBABA_CLOUD_ACCESS_KEY_IDALIBABA_CLOUD_ACCESS_KEY_SECRET
İşlemsel mesajları gönderme
Üretici kodu iki bölümden oluşur: işlemsel mesajları gönderme ve commit edilmemiş yarım mesajları yoklama.
İşlem üreticisi API'si
| Yöntem | Amaç |
|---|---|
client.getTransProducer(instanceId, topic, groupId) |
Bir işlem üreticisi oluşturma |
mqTransProducer.publishMessage(body, tag, msgProps) |
Yarım mesaj gönderme |
mqTransProducer.commit(receiptHandle) |
Yarım mesajı commit etme |
mqTransProducer.rollback(receiptHandle) |
Yarım mesajı geri alma |
mqTransProducer.consumeHalfMessage(numOfMessages, waitSeconds) |
Commit edilmemiş yarım mesajları yoklama |
TransCheckImmunityTime
TransCheckImmunityTime parametresi, broker'ın yarım mesaj gönderildikten sonra ilk işlem kontrolünden önce ne kadar bekleyeceğini belirler.
Birim: saniye
Geçerli değerler: 10 ila 300
İlk kontrolden sonraki davranış: Yarım mesaj hala commit edilmemişse broker 24 saat boyunca her 10 saniyede bir kontrol eder.
Örnek kod
Aşağıdaki örnek dört işlemsel mesaj gönderir. İlk mesaj yayımlandıktan hemen sonra commit edilir. Kalan üç mesaj, özel özellikleri olan a değerine göre yarım mesaj yoklama döngüsü sırasında çözülür:
a=1: ilk kontrolde commit edilira=2: ikinci kontrolden sonra commit edilir (ConsumedTimes > 1)a=3: geri alınır
const {
MQClient,
MessageProperties
} = require('@aliyunmq/mq-http-sdk');
// HTTP endpoint from the Instance Details page in the ApsaraMQ for RocketMQ console.
const endpoint = "${HTTP_ENDPOINT}";
// AccessKey credentials from environment variables.
const accessKeyId = process.env['ALIBABA_CLOUD_ACCESS_KEY_ID'];
const accessKeySecret = process.env['ALIBABA_CLOUD_ACCESS_KEY_SECRET'];
var client = new MQClient(endpoint, accessKeyId, accessKeySecret);
// Topic created in the ApsaraMQ for RocketMQ console.
const topic = "${TOPIC}";
// Consumer group ID created in the ApsaraMQ for RocketMQ console.
const groupId = "${GROUP_ID}";
// Instance ID. Set to null or "" if the instance has no namespace.
const instanceId = "${INSTANCE_ID}";
const mqTransProducer = client.getTransProducer(instanceId, topic, groupId);
async function processTransResult(res, msgId) {
if (!res) {
return;
}
if (res.code != 204) {
// Commit or rollback failed. The receipt handle may have expired
// if TransCheckImmunityTime or the consumeHalfMessage timeout elapsed.
console.log("Commit/Rollback Message Fail:");
const failHandles = res.body.map((error) => {
console.log("\tErrorHandle:%s, Code:%s, Reason:%s\n", error.ReceiptHandle, error.ErrorCode, error.ErrorMessage);
return error.ReceiptHandle;
});
} else {
console.log("Commit/Rollback Message suc!!! %s", msgId);
}
}
var halfMessageCount = 0;
var halfMessageConsumeCount = 0;
(async function(){
try {
// Send four transactional messages.
for(var i = 0; i < 4; i++) {
let res;
msgProps = new MessageProperties();
// Custom property used by the half-message polling loop
// to decide whether to commit or roll back.
msgProps.putProperty("a", i);
msgProps.messageKey("MessageKey");
// First transaction check delay: 10 seconds. Valid values: 10-300.
// After the first check, the broker rechecks every 10 seconds for up to 24 hours.
msgProps.transCheckImmunityTime(10);
res = await mqTransProducer.publishMessage("hello mq.", "tagA", msgProps);
console.log("Publish message: MessageID:%s,BodyMD5:%s,Handle:%s", res.body.MessageId, res.body.MessageBodyMD5, res.body.ReceiptHandle);
if (res && i == 0) {
// Commit the first message immediately using its receipt handle.
const msgId = res.body.MessageId;
res = await mqTransProducer.commit(res.body.ReceiptHandle);
console.log("Commit msg when publish, %s", msgId);
processTransResult(res, msgId);
}
}
} catch(e) {
// Add retry or persistence logic for failed messages.
console.log(e)
}
})();
// Run a separate loop to poll and resolve uncommitted half messages.
(async function() {
// Poll for half messages, similar to consuming normal messages.
while(halfMessageCount < 3 && halfMessageConsumeCount < 15) {
try {
halfMessageConsumeCount++;
res = await mqTransProducer.consumeHalfMessage(3, 3);
if (res.code == 200) {
console.log("Consume Messages, requestId:%s", res.requestId);
res.body.forEach(async (message) => {
console.log("\tMessageId:%s,Tag:%s,PublishTime:%d,NextConsumeTime:%d,FirstConsumeTime:%d,ConsumedTimes:%d,Body:%s" +
",Props:%j,MessageKey:%s,Prop-A:%s",
message.MessageId, message.MessageTag, message.PublishTime, message.NextConsumeTime, message.FirstConsumeTime, message.ConsumedTimes,
message.MessageBody,message.Properties,message.MessageKey,message.Properties.a);
var propA = message.Properties && message.Properties.a ? parseInt(message.Properties.a) : 0;
var opResp;
if (propA == 1 || (propA == 2 && message.ConsumedTimes > 1)) {
opResp = await mqTransProducer.commit(message.ReceiptHandle);
console.log("Commit msg when check half, %s", message.MessageId);
halfMessageCount++;
} else if (propA == 3) {
opResp = await mqTransProducer.rollback(message.ReceiptHandle);
console.log("Rollback msg when check half, %s", message.MessageId);
halfMessageCount++;
}
processTransResult(opResp, message.MessageId);
});
}
} catch(e) {
if (e.Code && e.Code.indexOf("MessageNotExist") > -1) {
// No half messages available. Long polling continues.
console.log("Consume Transaction Half msg: no new message, RequestId:%s, Code:%s", e.RequestId, e.Code);
} else {
console.log(e);
}
}
}
})();
Örnek kodun çalışma şekli
Kod iki eşzamanlı async işlevi çalıştırır:
Gönderme döngüsü: Etiket
tagAve döngü dizinine (0-3) ayarlanmışaadlı özel bir özellik ile dört yarım mesaj yayınlar. İlk mesaj (a=0), yayımlandıktan hemen sonra commit edilir.-
Yarım mesaj yoklama döngüsü: Commit edilmemiş yarım mesajları yoklar ve
aözelliğine göre bunları çözümler. Döngü, 3 yarım mesajı çözümledikten veya 15 yoklama girişiminden sonra sonlanır.**Özellik adeğeri**Eylem Koşul 0 Gönderim sırasında zaten commit edildi N/A 1 Commit İlk kontrol 2 Commit Yeniden denemeden sonra ( ConsumedTimes > 1)3 Geri alma İlk kontrol
Yanıt kodları
| Kod | Anlamı |
|---|---|
200 |
Mesajlar alındı |
204 |
Commit, geri alma veya ACK başarılı oldu |
204 dışı |
İşlem başarısız oldu (onay numarasının süresi dolmuş olabilir) |
İşlemsel mesajları alma
Tüketiciler, commit edilmiş işlemsel mesajları normal mesajlarla aynı şekilde alır. Uzun yoklama, bir mesaj gelene veya zaman aşımı dolana kadar isteği askıya alarak meşgul beklemeyi önler.
Tüketici API'si
| Yöntem | Amaç |
|---|---|
client.getConsumer(instanceId, topic, groupId) |
Bir tüketici oluşturma |
consumer.consumeMessage(numOfMessages, waitSeconds) |
Uzun yoklama kullanarak mesajları çekme |
consumer.ackMessage(handles) |
Tüketilen mesajları onaylama |
Parametreler
| Parametre | Açıklama | Maksimum Değer |
|---|---|---|
numOfMessages |
İstek başına alınacak maksimum mesaj sayısı | 16 |
waitSeconds |
Saniye cinsinden uzun yoklama süresi | 30 |
Örnek kod
const {
MQClient
} = require('@aliyunmq/mq-http-sdk');
// HTTP endpoint from the Instance Details page in the ApsaraMQ for RocketMQ console.
const endpoint = "${HTTP_ENDPOINT}";
// AccessKey credentials from environment variables.
const accessKeyId = process.env['ALIBABA_CLOUD_ACCESS_KEY_ID'];
const accessKeySecret = process.env['ALIBABA_CLOUD_ACCESS_KEY_SECRET'];
var client = new MQClient(endpoint, accessKeyId, accessKeySecret);
// Topic created in the ApsaraMQ for RocketMQ console.
const topic = "${TOPIC}";
// Consumer group ID created in the ApsaraMQ for RocketMQ console.
const groupId = "${GROUP_ID}";
// Instance ID. Set to null or "" if the instance has no namespace.
const instanceId = "${INSTANCE_ID}";
const consumer = client.getConsumer(instanceId, topic, groupId);
(async function(){
// Consume messages in a loop.
while(true) {
try {
// Long polling: if no message is available, the request suspends on the broker
// until a message arrives or the timeout elapses.
res = await consumer.consumeMessage(
3, // Max messages per request (up to 16)
3 // Long polling timeout in seconds (up to 30)
);
if (res.code == 200) {
console.log("Consume Messages, requestId:%s", res.requestId);
const handles = res.body.map((message) => {
console.log("\tMessageId:%s,Tag:%s,PublishTime:%d,NextConsumeTime:%d,FirstConsumeTime:%d,ConsumedTimes:%d,Body:%s" +
",Props:%j,MessageKey:%s,Prop-A:%s",
message.MessageId, message.MessageTag, message.PublishTime, message.NextConsumeTime, message.FirstConsumeTime, message.ConsumedTimes,
message.MessageBody,message.Properties,message.MessageKey,message.Properties.a);
return message.ReceiptHandle;
});
// ACK all processed messages. If the broker does not receive an ACK
// before NextConsumeTime, it redelivers the message with a new receipt handle.
res = await consumer.ackMessage(handles);
if (res.code != 204) {
// ACK failed. The receipt handle may have expired.
console.log("Ack Message Fail:");
const failHandles = res.body.map((error)=>{
console.log("\tErrorHandle:%s, Code:%s, Reason:%s\n", error.ReceiptHandle, error.ErrorCode, error.ErrorMessage);
return error.ReceiptHandle;
});
handles.forEach((handle)=>{
if (failHandles.indexOf(handle) < 0) {
console.log("\tSucHandle:%s\n", handle);
}
});
} else {
console.log("Ack Message suc, RequestId:%s\n\t", res.requestId, handles.join(','));
}
}
} catch(e) {
if (e.Code.indexOf("MessageNotExist") > -1) {
// No messages available. Long polling continues.
console.log("Consume Message: no new message, RequestId:%s, Code:%s", e.RequestId, e.Code);
} else {
console.log(e);
}
}
}
})();
Tüketicinin çalışma şekli
Tüketici şunları yapan sonsuz bir döngü çalıştırır:
3 saniyelik uzun yoklama zaman aşımı ile istek başına en fazla 3 mesaj çeker.
Her mesajı işler ve onay numaralarını toplar.
İşlenen tüm mesajlar için bir ACK gönderir.
Tüketici NextConsumeTime öncesinde bir mesajı ACK yapmazsa broker mesajı yeniden teslim eder. Her yeniden teslim yeni bir onay numarası oluşturur.
Mesaj mevcut olmadığında MessageNotExist hata kodu döndürülür. Bu, uzun yoklama modunda normaldir; döngü beklemeye devam eder.
Kullanım notları
Hemen commit edin veya geri alın. Çözülmemiş yarım mesajlar broker depolamasını tüketir. İşlem gerçekten hala devam etmiyorsa yerel işlem kontrolünüzden bilinmeyen bir durum döndürmekten kaçının.
Onay numarası süre sonlarını yönetin. Onay numaralarının sınırlı bir geçerlilik süresi vardır. Commit veya geri alma işlemi 204 dışı bir yanıtla başarısız olursa numaranın süresi dolmuş olabilir. Broker otomatik olarak bir işlem kontrolünü yeniden tetikler.
Yarım mesaj yoklama döngüsünü güvenilir şekilde çalıştırın. Üretim ortamında çözülmemiş yarım mesajların zamanında işlenmesini sağlamak için
consumeHalfMessagedöngüsünü ayrı bir uzun süre çalışan işlem olarak çalıştırın.
Sonraki adımlar
İşlemsel mesajların nasıl çalıştığını öğrenin: Transactional messages
Geliştirme ortamınızı kurun: Prepare the environment
Mesajlaşma kaynaklarını oluşturun: Create resources