ApsaraMQ for RocketMQ, sıralı mesajları katı bir ilk giren ilk çıkar (FIFO) diziliminde iletir. Uygulamanızın mesajların gönderildikleri sırayla işlenmesini gerektirdiği durumlarda, örneğin ticari siparişlerin gönderilme zamanına göre işlenmesi veya veritabanı değişikliklerinin ardışık olarak senkronize edilmesi gibi senaryolarda sıralı mesajları kullanın.
Bu konu, Java için HTTP istemci SDK'sını kullanarak sıralı mesaj gönderip almanıza ilişkin örnek kodlar sunar.
Çalışma biçimi
Sıralı mesajlar iki kategoriye ayrılır:
Genel olarak sıralı mesajlar: Bir konudaki tüm mesajlar tek bir FIFO dizilimini takip eder. Bu yaklaşım en güçlü sıralama garantisi sağlar ancak aktarım hızını tek bir parçayla sınırlar.
Parça bazında sıralı mesajlar: Mesajlar parçalama anahtarına göre parçalara dağıtılır. Her parça içinde mesajlar FIFO sırasını izler. Farklı parçalar bağımsız olarak tüketildiğinden, sıralamanın önemli olduğu yerlerde düzen korunurken daha yüksek aktarım hızı elde edilir.
Bir parçalama anahtarı, bir mesajın hangi parçaya ait olduğunu belirler. Aynı parçalama anahtarına sahip mesajlar her zaman aynı parçaya iletilir ve sırayla tüketilir. Parçalama anahtarı, mesaj anahtarından farklıdır.
Bir ApsaraMQ for RocketMQ aracı, mesajların oluşturulma sırasını göndericinin tek bir üretici veya iş parçacığı kullanarak mesajları gönderme sırasına göre belirler. Gönderici birden çok üretici veya iş parçacığı kullanarak eşzamanlı olarak mesaj gönderirse, mesaj sırası ApsaraMQ for RocketMQ aracının mesajları aldığı sıraya göre belirlenir. Bu sıra, iş tarafındaki gönderme sırasından farklı olabilir.
Üretim sırası ve tüketim sırası
Uçtan uca FIFO teslimatı iki bağımsız faktöre bağlıdır:
| Üretim sırası | Tüketim sırası | Sonuç |
|---|---|---|
| Tek üretici, seri gönderimler | Sıralı tüketim (consumeMessageOrderly) |
Parçalama anahtarı başına FIFO garantili |
| Birden çok üretici veya iş parçacığı | Sıralı tüketim | Sıra, broker varışına göre belirlenir; iş niyetine göre değil |
| Tek üretici, seri gönderimler | Eşzamanlı tüketim | Tüketim sırası garantili değildir |
Katı sıralama için her iki koşulun da sağlanması gerekir: tek bir üretici iş parçacığından gönderim yapılmalı ve consumeMessageOrderly ile tüketilmelidir.
Ön koşullar
Başlamadan önce aşağıdaki işlemleri tamamladığınızdan emin olun:
SDK for Java'yı yükleyin. Daha fazla bilgi için bkz. Java ortamınızı kurma.
Kodda belirtmek istediğiniz kaynakları ApsaraMQ for RocketMQ konsolunda oluşturun. Kaynaklar arasında örnekler, konular ve tüketici grupları bulunur. Daha fazla bilgi için bkz. Kaynak oluşturma.
Alibaba Cloud hesabınızın AccessKey çiftini edinin. Daha fazla bilgi için bkz. AccessKey oluşturma.
Sıralı mesaj gönderme
Aşağıdaki örnekte, parçalama anahtarları kullanılarak iki parça arasında sekiz sıralı mesaj gönderilmektedir.
Yer tutucuları 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> |
Konu adı | OrderTopic |
<your-instance-id> |
Örnek kimliği. Örneğin ad alanı yoksa null veya "" olarak ayarlayın |
MQ_INST_xxx |
import com.aliyun.mq.http.MQClient;
import com.aliyun.mq.http.MQProducer;
import com.aliyun.mq.http.model.TopicMessage;
import java.util.Date;
public class OrderProducer {
public static void main(String[] args) {
MQClient mqClient = new MQClient(
"<your-http-endpoint>",
// Read credentials from environment variables to avoid hardcoding secrets
System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
);
final String topic = "<your-topic>";
// Set to null or "" if the instance has no namespace
final String instanceId = "<your-instance-id>";
// Get the producer for the specified topic
MQProducer producer;
if (instanceId != null && instanceId != "") {
producer = mqClient.getProducer(instanceId, topic);
} else {
producer = mqClient.getProducer(topic);
}
try {
// Send 8 messages, alternating between 2 sharding keys (partitions)
for (int i = 0; i < 8; i++) {
TopicMessage pubMsg = new TopicMessage(
"hello mq!".getBytes(), // Message body
"A" // Message tag for filtering
);
// Sharding key determines the partition. Messages with the same
// sharding key are delivered to the same partition in FIFO order.
pubMsg.setShardingKey(String.valueOf(i % 2));
pubMsg.getProperties().put("a", String.valueOf(i));
// Publish synchronously. No exception means the send succeeded.
TopicMessage pubResultMsg = producer.publishMessage(pubMsg);
System.out.println(new Date() + " Send mq message success. Topic is:" + topic
+ ", msgId is: " + pubResultMsg.getMessageId()
+ ", bodyMD5 is: " + pubResultMsg.getMessageBodyMD5());
}
} catch (Throwable e) {
// Handle send failure: retry or persist the message for later delivery
System.out.println(new Date() + " Send mq message failed. Topic is:" + topic);
e.printStackTrace();
}
mqClient.close();
}
}
Her konu yalnızca bir mesaj türünü destekler. Sıralı mesajlar için oluşturulan bir konu, normal mesajları gönderemez veya alamaz.
Sıralı mesaj alma
Aşağıdaki örnekte, uzun oylama kullanan ve parça düzeyinde FIFO sırasını garanti eden consumeMessageOrderly kullanılarak sıralı mesajlar tüketilmektedir.
Yer tutucuları gerçek değerlerinizle değiştirin:
| Yer tutucu | Açıklama | Örnek |
|---|---|---|
<your-http-endpoint> |
Instance Details sayfasındaki HTTP uç noktası | http://xxx.mqrest.cn-hangzhou.aliyuncs.com |
<your-topic> |
Konu adı | OrderTopic |
<your-group-id> |
Tüketici grubu kimliği | GID_Order |
<your-instance-id> |
Örnek kimliği. Örneğin ad alanı yoksa null veya "" olarak ayarlayın |
MQ_INST_xxx |
import com.aliyun.mq.http.MQClient;
import com.aliyun.mq.http.MQConsumer;
import com.aliyun.mq.http.common.AckMessageException;
import com.aliyun.mq.http.model.Message;
import java.util.ArrayList;
import java.util.List;
public class OrderConsumer {
public static void main(String[] args) {
MQClient mqClient = new MQClient(
"<your-http-endpoint>",
System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
);
final String topic = "<your-topic>";
final String groupId = "<your-group-id>";
// Set to null or "" if the instance has no namespace
final String instanceId = "<your-instance-id>";
final MQConsumer consumer;
if (instanceId != null && instanceId != "") {
consumer = mqClient.getConsumer(instanceId, topic, groupId, null);
} else {
consumer = mqClient.getConsumer(topic, groupId);
}
// Continuously poll for messages
do {
List<Message> messages = null;
try {
// consumeMessageOrderly guarantees partition-level FIFO order.
// The consumer must ACK all messages in a batch before the broker
// delivers the next batch from the same partition.
messages = consumer.consumeMessageOrderly(
3, // Max messages per batch (up to 16)
3 // Long polling timeout in seconds (up to 30)
);
} catch (Throwable e) {
e.printStackTrace();
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
if (messages == null || messages.isEmpty()) {
System.out.println(Thread.currentThread().getName() + ": no new message, continue!");
continue;
}
// Process messages
System.out.println("Receive " + messages.size() + " messages:");
for (Message message : messages) {
System.out.println(message);
System.out.println("ShardingKey: " + message.getShardingKey()
+ ", a:" + message.getProperties().get("a"));
}
// ACK consumed messages. If the broker does not receive an ACK before
// the retry timeout, it redelivers the message.
{
List<String> handles = new ArrayList<String>();
for (Message message : messages) {
handles.add(message.getReceiptHandle());
}
try {
consumer.ackMessage(handles);
} catch (Throwable e) {
if (e instanceof AckMessageException) {
AckMessageException errors = (AckMessageException) e;
System.out.println("Ack message fail, requestId is:"
+ errors.getRequestId() + ", fail handles:");
if (errors.getErrorMessages() != null) {
for (String errorHandle : errors.getErrorMessages().keySet()) {
System.out.println("Handle:" + errorHandle
+ ", ErrorCode:" + errors.getErrorMessages().get(errorHandle).getErrorCode()
+ ", ErrorMsg:" + errors.getErrorMessages().get(errorHandle).getErrorMessage());
}
}
continue;
}
e.printStackTrace();
}
}
} while (true);
}
}
Sıralı tüketimin çalışma biçimi
consumeMessageOrderly çağrıldığında broker şu davranışları uygular:
Parça düzeyinde kilitleme: Tüketici birden çok parçadan mesaj çekebilir ancak her parça içinde mesajlar gönderilme sırasına göre gelir.
Toplu onaylama kapısı: Tüketici, aynı parçadan sonraki toplu işi almadan önce mevcut toplu işteki tüm mesajları onaylamalıdır (ACK). Broker, yeniden deneme zaman aşımından önce onay almazsa onaylanmamış mesajı yeniden iletir.
Uzun oylama: Kullanılabilir mesaj yoksa broker isteği belirtilen oylama süresi boyunca (en fazla 30 saniye) bekletir ve bir mesaj geldiğinde hemen yanıt verir.
Kullanım notları
Tek tür konular: Her konu yalnızca bir mesaj türünü destekler. Aynı konuda sıralı mesajları normal mesajlarla karıştırmayın.
Katı sıra için tek üretici: Göreceli sıralama gerektiren tüm mesajları tek bir iş parçacığındaki tek bir üreticiden gönderin. Birden çok üretici veya eşzamanlı iş parçacığı kullanmak sıralama garantisini bozar.
Parçalama anahtarı tasarımı: İlgili mesajları gruplayan ancak sıcak parçalar oluşturmayan bir parçalama anahtarı seçin. Örneğin, tek bir parçada çok fazla mesaj yoğunlaşmasına neden olabilecek bir müşteri kimliği yerine, bir siparişe ait tüm olayları sıralı tutmak için bir sipariş kimliği kullanın.
Hızlı onaylama: Gecikmeli onaylama, tüm parça için sonraki mesaj teslimatlarını engeller. Her toplu işi mümkün olduğunca hızlı şekilde işleyin ve onaylayın.
Alındı tanıtıcısının sona ermesi: Her mesaj alındı tanıtıcısının benzersiz bir zaman damgası vardır. Tanıtıcı onaylamadan önce sona ererse onaylama başarısız olur ve broker mesajı yeniden iletir.
Ayrıca bkz.
Sıralı mesajlar: Sıralama garantileri, sıralama türleri ve ayrıntılı kavramlar
Kaynak oluşturma: Konsolda örnekleri, konuları ve tüketici gruplarını kurma