ApsaraMQ for RocketMQ livre et consomme les messages ordonnés selon un ordre strict premier entré, premier sorti (FIFO). L'exemple de code ci-dessous montre comment envoyer et consommer des messages ordonnés avec le SDK client HTTP pour Go.
Types d'ordonnancement
Il existe deux catégories de messages ordonnés :
| Type | Comportement | Cas d'utilisation |
|---|---|---|
| Globally ordered | Tous les messages d'un topic suivent une unique séquence FIFO. | Chaque message doit être traité dans l'ordre exact d'envoi, par exemple pour l'exécution séquentielle de commandes. |
| Partitionally ordered | Les messages sont répartis entre plusieurs partitions via une clé de partitionnement. Chaque partition conserve son propre ordre FIFO. | Seuls les messages partageant un regroupement logique, tel qu'un ID de commande ou un ID utilisateur, nécessitent un ordonnancement strict. |
Une sharding key identifie la partition à laquelle appartient un message. Elle diffère de la clé de message : cette dernière sert d'identifiant métier pour le traçage, tandis que la sharding key contrôle l'ordonnancement.
Pour plus d'informations, consultez la rubrique Ordered messages.
Fonctionnement de l'ordonnancement
L'ordonnancement repose sur une coordination entre le producteur et le consommateur.
Côté envoi : Utilisez un seul producteur et un seul thread pour envoyer les messages. Le broker préserve la séquence exacte de réception des messages provenant de ce producteur.
Côté consommation : Appelez la méthode ConsumeMessageOrderly pour extraire les messages partition par partition. Au sein de chaque partition, le lot suivant n'est délivré qu'après accusé de réception de tous les messages du lot actuel. Si le broker ne reçoit pas d'accusé de réception avant l'échéance définie par NextConsumeTime, il renvoie le message non acquitté.
Prérequis
Avant d'exécuter l'exemple de code, effectuez les configurations suivantes :
Create an instance, a topic, and a consumer group dans la console ApsaraMQ for RocketMQ
Create an AccessKey pair pour votre compte Alibaba Cloud
Envoi de messages ordonnés
Le broker détermine l'ordre des messages selon la séquence d'envoi par un unique producteur ou thread. Si plusieurs producteurs ou threads envoient des données simultanément, l'ordre d'arrivée au broker peut diverger de l'ordre métier souhaité. Pour garantir l'ordonnancement, envoyez les messages depuis un seul producteur utilisant un seul thread.
Le code suivant envoie huit messages ordonnés par partition. La sharding key i % 2 répartit les messages sur deux partitions : les messages dont l'index est pair vont dans une partition, tandis que ceux dont l'index est impair vont dans l'autre.
Remplacez les espaces réservés avant d'exécuter le code :
| Espace réservé | Description | Exemple |
|---|---|---|
${HTTP_ENDPOINT} |
Endpoint HTTP disponible sur la page Instance Details de la console ApsaraMQ for RocketMQ | http://xxx.mqrest.cn-hangzhou.aliyuncs.com |
${TOPIC} |
Topic créé dans la console | OrderedTestTopic |
${INSTANCE_ID} |
ID de l'instance. Si l'instance possède un namespace, spécifiez l'ID. Sinon, laissez ce champ vide. Consultez la page Instance Details pour vérifier la présence d'un namespace. | MQ_INST_xxx |
package main
import (
"fmt"
"time"
"strconv"
"os"
"github.com/aliyunmq/mq-http-go-sdk"
)
func main() {
// HTTP endpoint from the Instance Details page in the ApsaraMQ for RocketMQ console.
endpoint := "${HTTP_ENDPOINT}"
// Set the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET
// environment variables before running this code.
accessKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
secretKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
// Topic created in the ApsaraMQ for RocketMQ console.
topic := "${TOPIC}"
// Instance ID. If the instance has a namespace, specify the ID.
// If not, set this to an empty string.
instanceId := "${INSTANCE_ID}"
client := mq_http_sdk.NewAliyunMQClient(endpoint, accessKey, secretKey, "")
mqProducer := client.GetProducer(instanceId, topic)
// Send 8 messages across 2 partitions.
for i := 0; i < 8; i++ {
msg := mq_http_sdk.PublishMessageRequest{
MessageBody: "hello mq!",
MessageTag: "",
Properties: map[string]string{},
}
msg.MessageKey = "MessageKey"
msg.Properties["a"] = strconv.Itoa(i)
// Sharding key determines the partition.
// Messages with the same sharding key are delivered in FIFO order.
msg.ShardingKey = strconv.Itoa(i % 2)
ret, err := mqProducer.PublishMessage(msg)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Printf("Publish ---->\n\tMessageId:%s, BodyMD5:%s, \n", ret.MessageId, ret.MessageBodyMD5)
}
time.Sleep(time.Duration(100) * time.Millisecond)
}
}
Consommation de messages ordonnés
Ce code consomme des messages ordonnés via le long polling. Le consommateur extrait les messages partition par partition et accuse réception de chaque lot avant de demander le suivant.
Remplacez les espaces réservés par vos valeurs réelles. En plus des éléments listés dans la section Envoi de messages ordonnés, spécifiez les informations suivantes :
| Espace réservé | Description | Exemple |
|---|---|---|
${GROUP_ID} |
Consumer group créé dans la console ApsaraMQ for RocketMQ | GID_OrderedTest |
package main
import (
"fmt"
"github.com/gogap/errors"
"strings"
"time"
"os"
"github.com/aliyunmq/mq-http-go-sdk"
)
func main() {
// HTTP endpoint from the Instance Details page in the ApsaraMQ for RocketMQ console.
endpoint := "${HTTP_ENDPOINT}"
// Set the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET
// environment variables before running this code.
accessKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
secretKey := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
// Topic created in the ApsaraMQ for RocketMQ console.
topic := "${TOPIC}"
// Instance ID. If the instance has a namespace, specify the ID.
// If not, set this to an empty string.
instanceId := "${INSTANCE_ID}"
// Consumer group created in the ApsaraMQ for RocketMQ console.
groupId := "${GROUP_ID}"
client := mq_http_sdk.NewAliyunMQClient(endpoint, accessKey, secretKey, "")
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"+
"\tShardingKey: %s\n",
v.MessageId, v.PublishTime, v.MessageTag, v.ConsumedTimes,
v.FirstConsumeTime, v.NextConsumeTime, v.MessageBody, v.Properties, v.ShardingKey)
}
// Acknowledge all messages in this batch.
// If the broker does not receive an ACK before NextConsumeTime,
// it redelivers the message.
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
}
}
}()
// The consumer pulls partitionally ordered messages partition by partition.
// Within each partition, the next batch is delivered only after all messages
// in the current batch are acknowledged.
// Long polling: if no message is available, the request is held on the broker
// for up to the specified polling duration. The network timeout is 35 seconds.
mqConsumer.ConsumeMessageOrderly(respChan, errChan,
3, // Max messages per batch (up to 16).
3, // Long polling duration in seconds (up to 30).
)
<-endChan
}
}
Comportements clés de la consommation
| Comportement | Détail |
|---|---|
| Ordonnancement au niveau de la partition | Le consommateur peut extraire des données depuis plusieurs partitions simultanément, mais les messages au sein de chaque partition sont toujours livrés dans l'ordre d'envoi. |
| Accusé de réception par lot obligatoire | Le lot suivant d'une partition n'est délivré qu'après accusé de réception de tous les messages du lot précédent. |
| Renvoi automatique | Si le broker ne reçoit aucun accusé de réception avant NextConsumeTime, il renvoie le message. Chaque renvoi attribue un nouveau handle de réception avec un horodatage unique. |
| Long polling | En l'absence de messages disponibles, le broker maintient la requête pendant la durée de sondage spécifiée (3 secondes dans cet exemple) avant de renvoyer une réponse vide. Le délai d'expiration réseau est fixé à 35 secondes. |
Étapes suivantes
Ordered messages : types d'ordonnancement, garanties et considérations de conception
Envoyez et recevez d'autres types de messages à l'aide du SDK HTTP pour Go
Prepare the environment : configurez l'environnement de développement Go