ApsaraMQ for RocketMQ, dağıtık hizmetler arasında işlem tutarlılığını sağlamak için işlemsel mesajları kullanır. İşlemsel mesaj, mesaj teslimini yerel bir işlemin sonucuna bağlar: Tüketiciler mesaja yalnızca yerel işlem tamamlandıktan (commit) sonra erişebilir.
Aşağıdaki Python örnek kodu, HTTP istemci SDK'sını kullanarak üç temel işlemi gösterir: İşlemsel mesajların gönderilmesi, işlem durumu denetimlerinin yönetilmesi ve tamamlanmış mesajların tüketilmesi.
Çalışma mekanizması
İşlemsel mesajlar, X/Open XA'ya benzer bir iki aşamalı tamamlama (two-phase commit) protokolünü izler.
Üretici (producer), broker ve tüketici (consumer) aşağıdaki gibi etkileşime girer:
Üretici broker'a bir yarım mesaj (half message) gönderir. Yarım mesaj broker'da kalıcı olarak saklanır ancak tüketicilere geçici olarak teslim edilemez durumda işaretlenir.
Broker yarım mesajı onaylar ve bir makbuz tanıtıcısı (receipt handle) döndürür.
Üretici yerel işlemi çalıştırır (örneğin, veritabanına yazma).
Üretici, yerel işlemin sonucuna bağlı olarak ikinci aşama onayını göndererek mesajın tüketicilere iletilmesi için tamamlanmasını (commit) veya mesajın atılması için geri alınmasını (rollback) sağlar.
Broker ağ hatası veya üreticinin yeniden başlatılması nedeniyle ikinci aşama onayını almazsa, üreticiyi sorgulayarak bir işlem durumu denetimi başlatır.
Üretici yerel işlem durumunu tekrar değerlendirir ve tamamlama (commit), geri alma (rollback) veya bilinmiyor (unknown) yanıtını verir.
Broker bu denetimi 24 saate kadar her 10 saniyede bir tekrarlar.

Daha fazla bilgi için bkz. İşlemsel mesajlar.
Ön koşullar
Başlamadan önce şunlara sahip olduğunuzdan emin olun:
Yüklenen Python HTTP istemci SDK'sı -- bkz. Ortamı hazırlama
Bir ApsaraMQ for RocketMQ örneği, konu (topic) ve tüketici grubu -- bkz. Kaynak oluşturma
Alibaba Cloud hesabınız için bir AccessKey çifti -- bkz. AccessKey çifti oluşturma
Temel kavramlar
| Terim | Açıklama |
|---|---|
| Yarım mesaj (Half message) | Broker'a gönderilen ancak geçici olarak teslim edilemez olarak işaretlenen mesaj. Üretici işlemi tamamladığında (commit) tüketiciler tarafından görülebilir hale gelir. |
| İşlem durumu denetimi | Broker tamamlama veya geri alma onayı almadığında, işlemin sonucunu öğrenmek için üreticiyi proaktif olarak sorgular. |
TransCheckImmunityTime |
Yarım mesaj gönderildikten sonra broker'ın ilk durum denetimini başlatmadan önceki gecikme süresi (saniye cinsinden). Geçerli değerler: 10 ila 300. |
| Makbuz tanıtıcısı (Receipt handle) | Bir mesaj her tüketildiğinde atanan benzersiz belirteç. Mesajları tamamlamak, geri almak veya onaylamak için kullanılır. |
İşlemsel mesajları gönderme
İşlemsel bir mesaj göndermek üç bölümden oluşur:
İstemciyi başlatma ve yarım mesajları yayınlama -- Üretici, broker'ın yarım mesaj olarak tuttuğu mesajları gönderir.
İşlem durumu denetimlerini yönetme -- Arka plan iş parçacığı, çözülmemiş yarım mesajları yoklar ve yerel işlem sonucuna göre her birini tamamlar veya geri alır.
Yayınlamadan hemen sonra tamamlama -- İsteğe bağlı olarak, yayınlama yanıtından alınan makbuz tanıtıcısını kullanarak yarım mesajı yayınlandıktan hemen sonra tamamlayın.
Aşağıdaki kod bu üç bölümü de göstermektedir. Yer tutucuları kendi gerçek değerlerinizle değiştirin:
| Yer Tutucu | Açıklama | Örnek |
|---|---|---|
<your-http-endpoint> |
ApsaraMQ for RocketMQ konsolundaki Instance Details sayfasındaki HTTP uç noktası | http://xxx.mqrest.cn-hangzhou.aliyuncs.com |
<your-topic> |
ApsaraMQ for RocketMQ konsolunda oluşturulan konu | TransTopic |
<your-group-id> |
ApsaraMQ for RocketMQ konsolunda oluşturulan tüketici grubu kimliği | GID_Trans |
<your-instance-id> |
Örnek kimliği. Örneğin bir ad alanı varsa kimliği belirtin. Yoksa boş bir dize "" kullanın. Ad alanı ayarı için Instance Details sayfasını kontrol edin. |
MQ_INST_xxx |
#!/usr/bin/env python
# coding=utf8
import sys
import os
import time
import threading
from mq_http_sdk.mq_exception import MQExceptionBase
from mq_http_sdk.mq_producer import *
from mq_http_sdk.mq_client import *
# ---------------------------------------------------------------------------
# 1. Initialize the client
# ---------------------------------------------------------------------------
# Get the HTTP endpoint from the Instance Details page in the
# ApsaraMQ for RocketMQ console.
mq_client = MQClient(
"<your-http-endpoint>",
# Retrieve credentials from environment variables to avoid
# hardcoding sensitive information.
os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID'],
os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
)
topic_name = "<your-topic>"
group_id = "<your-group-id>"
instance_id = "<your-instance-id>"
# Number of transactional messages to send in this example.
msg_count = 4
# ---------------------------------------------------------------------------
# 2. Define error handling for commit/rollback failures
# ---------------------------------------------------------------------------
def process_trans_error(exp):
"""Handle commit or rollback failures.
A failure occurs when the operation takes longer than the
TransCheckImmunityTime value or the consume_half_message timeout
(10 seconds in this example).
"""
print("\nCommit/Roll Transaction Message Fail! Exception:%s" % exp)
if exp.sub_errors:
for sub_error in exp.sub_errors:
print("\tErrorHandle:%s,ErrorCode:%s,ErrorMsg:%s" %
(sub_error["ReceiptHandle"], sub_error["ErrorCode"],
sub_error["ErrorMessage"]))
# ---------------------------------------------------------------------------
# 3. Set up the transaction status checker (background thread)
# ---------------------------------------------------------------------------
# The broker queries this checker when it has not received a commit or
# rollback for a half message. Run it in a separate thread so it can
# process checks while the producer continues sending messages.
class TransactionCheckThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.count = 0
# Create a separate client for the checker thread.
self.mq_client = MQClient(
"<your-http-endpoint>",
os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID'],
os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
)
self.trans_producer = self.mq_client.get_trans_producer(
instance_id, topic_name, group_id
)
def run(self):
while True:
if self.count == 3:
break
try:
# Poll for half messages awaiting a transaction decision.
# Arguments: batch size = 1, wait timeout = 3 seconds.
half_msgs = self.trans_producer.consume_half_message(1, 3)
for half_msg in half_msgs:
print("Receive Half Message, MessageId: %s\n"
"MessageBodyMD5: %s\n"
"MessageTag: %s\n"
"ConsumedTimes: %s\n"
"PublishTime: %s\n"
"Body: %s\n"
"NextConsumeTime: %s\n"
"ReceiptHandle: %s\n"
"Properties: %s" %
(half_msg.message_id,
half_msg.message_body_md5,
half_msg.message_tag,
half_msg.consumed_times,
half_msg.publish_time,
half_msg.message_body,
half_msg.next_consume_time,
half_msg.receipt_handle,
half_msg.properties))
# Decide whether to commit or roll back based on local
# transaction results. In this example, the decision
# depends on the custom property "a":
# a == 1 -> commit immediately
# a == 2 -> commit on the second check (simulates
# delayed local transaction completion)
# a == 3 -> rollback (local transaction failed)
# other -> unknown (check again later)
a = int(half_msg.get_property("a"))
try:
if a == 1:
self.trans_producer.commit(half_msg.receipt_handle)
self.count += 1
print("------>commit")
elif a == 2 and half_msg.consumed_times > 1:
self.trans_producer.commit(half_msg.receipt_handle)
self.count += 1
print("------>commit")
elif a == 3:
self.trans_producer.rollback(half_msg.receipt_handle)
self.count += 1
print("------>rollback")
else:
print("------>unknown")
except MQExceptionBase as rec_commit_roll_e:
process_trans_error(rec_commit_roll_e)
except MQExceptionBase as half_e:
if half_e.type == "MessageNotExist":
print("No half message! RequestId: %s" % half_e.req_id)
continue
print("Consume half message Fail! Exception:%s\n" % half_e)
break
# ---------------------------------------------------------------------------
# 4. Start the checker thread and send transactional messages
# ---------------------------------------------------------------------------
check_thread = TransactionCheckThread()
check_thread.setDaemon(True)
check_thread.start()
try:
trans_producer = mq_client.get_trans_producer(
instance_id, topic_name, group_id
)
for i in range(msg_count):
msg = TopicMessage(
"I am test message %s." % i, # Message body
"tagA" # Message tag
)
# Set a custom property used by the checker to simulate
# different transaction outcomes.
msg.put_property("a", i)
msg.set_message_key("MessageKey")
# TransCheckImmunityTime: delay (in seconds) before the first
# transaction status check. Valid values: 10 to 300.
# After the first check, the broker retries every 10 seconds
# for up to 24 hours.
msg.set_trans_check_immunity_time(10)
re_msg = trans_producer.publish_message(msg)
print("Publish Transaction Message Succeed. MessageID:%s, "
"BodyMD5:%s, Handle:%s" %
(re_msg.message_id, re_msg.message_body_md5,
re_msg.receipt_handle))
time.sleep(1)
# For the first message (i == 0), commit immediately using
# the receipt handle returned by publish_message.
if i == 0:
try:
trans_producer.commit(re_msg.receipt_handle)
except MQExceptionBase as pub_commit_roll_e:
process_trans_error(pub_commit_roll_e)
except MQExceptionBase as pub_e:
if pub_e.type == "TopicNotExist":
print("Topic not exist, please create it.")
sys.exit(1)
print("Publish Message Fail. Exception:%s" % pub_e)
# Wait for the checker thread to finish processing.
while True:
if not check_thread.is_alive():
break
time.sleep(1)
Örnek ne yapar?
Üretici, özel özelliği a 0, 1, 2 ve 3 olarak ayarlanmış dört işlemsel mesaj gönderir. Her değer farklı bir işlem sonucu tetikler:
**Özellik a** |
Denetleyici davranışı | Sonuç |
|---|---|---|
| 0 | Üretici, yayınladıktan hemen sonra tamamlar (denetleyiciyi bypass eder). | Mesaj tüketicilere iletilir. |
| 1 | Denetleyici ilk durum denetiminde tamamlar. | Mesaj tüketicilere iletilir. |
| 2 | Denetleyici ilk denetimde bilinmeyen döndürür ve ikinci denetimde tamamlar. | Mesaj bir gecikme sonrasında iletilir. |
| 3 | Denetleyici geri alır. | Mesaj atılır, hiçbir zaman iletilmez. |
İşlemsel mesajlara abone olma
Tamamlanmış işlemsel mesajlar, normal mesajlarla aynı şekilde tüketilir. Tüketici yalnızca işlemleri tamamlanmış mesajları alır.
Aşağıdaki kod, uzun oylama (long polling) kullanarak mesajları toplu halde alır ve onaylar.
import os
import time
from mq_http_sdk.mq_exception import MQExceptionBase
from mq_http_sdk.mq_consumer import *
from mq_http_sdk.mq_client import *
# Initialize the consumer client.
mq_client = MQClient(
"<your-http-endpoint>",
os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID'],
os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
)
topic_name = "<your-topic>"
group_id = "<your-group-id>"
instance_id = "<your-instance-id>"
consumer = mq_client.get_consumer(instance_id, topic_name, group_id)
# Long polling timeout in seconds. Maximum: 30.
wait_seconds = 3
# Maximum messages per batch. Maximum: 16.
batch = 3
print("Consume Message From Topic: %s, Consumer: %s, WaitSeconds: %s\n" %
(topic_name, group_id, wait_seconds))
while True:
try:
# Long polling: if no message is available, the request is held
# on the broker for up to wait_seconds before returning.
recv_msgs = consumer.consume_message(batch, wait_seconds)
for msg in recv_msgs:
print("Receive, MessageId: %s\n"
"MessageBodyMD5: %s\n"
"MessageTag: %s\n"
"ConsumedTimes: %s\n"
"PublishTime: %s\n"
"Body: %s\n"
"NextConsumeTime: %s\n"
"ReceiptHandle: %s\n"
"Properties: %s\n" %
(msg.message_id, msg.message_body_md5,
msg.message_tag, msg.consumed_times,
msg.publish_time, msg.message_body,
msg.next_consume_time, msg.receipt_handle,
msg.properties))
except MQExceptionBase as e:
if e.type == "MessageNotExist":
print("No new message! RequestId: %s" % e.req_id)
continue
print("Consume Message Fail! Exception:%s\n" % e)
time.sleep(2)
continue
# Acknowledge consumed messages. If the broker does not receive an
# ACK before msg.next_consume_time, it redelivers the message.
# Each delivery assigns a new receipt handle.
try:
receipt_handle_list = [msg.receipt_handle for msg in recv_msgs]
consumer.ack_message(receipt_handle_list)
print("Ack %s Message Succeed.\n\n" % len(receipt_handle_list))
except MQExceptionBase as e:
print("\nAck Message Fail! Exception:%s" % e)
if e.sub_errors:
for sub_error in e.sub_errors:
print("\tErrorHandle:%s,ErrorCode:%s,ErrorMsg:%s" %
(sub_error["ReceiptHandle"],
sub_error["ErrorCode"],
sub_error["ErrorMessage"]))
Kullanım notları
**
TransCheckImmunityTimearalığı**: Geçerli değerler 10 ila 300 saniyedir.Durum denetimi yeniden denemesi: İlk denetimden sonra broker, 24 saate kadar her 10 saniyede bir üreticiyi sorgular.
Denetleyici iş parçacığı için ayrı istemci: İşlem durumu denetleyicisi kendi
MQClientörneğini kullanmalıdır. Üretici iş parçacığı ile denetleyici iş parçacığı arasında istemci paylaşımı eşzamanlılık sorunlarına neden olur.ACK zaman aşımı: Tüketici,
next_consume_timesınırından önce bir mesajı onaylamazsa broker mesajı yeniden iletir.
Sonraki adımlar
İşlemsel mesajlar -- İşlemsel mesaj modelini ve yaşam döngüsünü anlayın.
Ortamı hazırlama -- Python HTTP istemci SDK'sını yükleyin ve yapılandırın.
Kaynak oluşturma -- Örnekleri, konuları ve tüketici gruplarını kurun.