Todos los productos
Search
Centro de documentos

ApsaraMQ for RocketMQ:Mensajes programados y diferidos

Última actualización: Sep 04, 2026

Los mensajes programados se entregan a los consumidores en un momento específico en lugar de hacerlo de inmediato. Úselos para crear activadores basados en tiempo en aplicaciones distribuidas, como la cancelación de pedidos por tiempo de espera agotado, notificaciones diferidas o la programación de tareas periódicas.

Los mensajes programados y los mensajes diferidos funcionan de la misma manera: el broker retiene ambos y los entrega en una marca de tiempo específica. El término "mensajes programados" que se usa a continuación abarca ambos tipos.

Antes de empezar

Antes de enviar mensajes programados, asegúrese de que:

  • El topic tenga su MessageType configurado como Delay. Los mensajes programados solo pueden enviarse a topics de tipo Delay.

  • La marca de tiempo de entrega sea posterior a la hora actual. Si la marca de tiempo corresponde al pasado o excede el rango permitido, el broker entrega el mensaje de inmediato.

  • La marca de tiempo de entrega se encuentre dentro de la ventana máxima de programación para su tipo de instancia:

    Tipo de instancia

    Ventana máxima de programación

    Standard Edition (suscripción, pay-as-you-go o serverless) y Professional Edition serverless

    7 días

    Professional Edition y Enterprise Platinum Edition (suscripción o pay-as-you-go)

    40 días

Casos de uso

Programación de tareas distribuidas

Programe mensajes con diferentes niveles de granularidad temporal para reemplazar los temporizadores tradicionales basados en bases de datos. Por ejemplo, active una tarea de limpieza de archivos diariamente a una hora fija o envíe una notificación de recordatorio cada 2 minutos. Los mensajes programados eliminan la complejidad de gestionar trabajos cron distribuidos y ofrecen mayor escalabilidad.

Distributed timed scheduling

Gestión de tiempos de espera de tareas

Difiera una acción hasta que se cumpla un plazo límite. Por ejemplo, en comercio electrónico, cancele un pedido no pagado 30 minutos después de su creación en lugar de consultar repetidamente una base de datos para verificar el estado del pago.

Task timeout processing

En comparación con la consulta repetida de registros expirados en una base de datos, los mensajes programados ofrecen:

  • Granularidad temporal flexible: active tareas en cualquier intervalo, sin incrementos de tiempo fijos ni necesidad de lógica de deduplicación.

  • Mayor rendimiento: evite los cuellos de botella que generan los escaneos frecuentes de la base de datos. La infraestructura de mensajería gestiona la concurrencia y escala horizontalmente.

Funcionamiento de la programación

Marca de tiempo de entrega

La hora de entrega se expresa como una marca de tiempo Unix en milisegundos. Establézcala en el mensaje mediante setDeliveryTimestamp(). El broker retiene el mensaje hasta que se alcanza dicha marca de tiempo.

  • Para mensajes programados, calcule la hora absoluta de entrega: si la hora actual es 2022-06-09 17:30:00 y desea la entrega a las 19:20:00, establezca la marca de tiempo en 1654773600000.

  • Para mensajes diferidos, agregue un desplazamiento relativo a la hora actual: si la hora actual es 2022-06-09 17:30:00 y desea la entrega después de 1 hora, establezca la marca de tiempo en 1654770600000.

Importante

La marca de tiempo de entrega no puede modificarse ni cancelarse después de enviar el mensaje.

Ciclo de vida del mensaje

Un mensaje programado pasa por seis etapas desde su producción hasta su eliminación:

Scheduled message lifecycle

Etapa

Descripción

Inicialización

El productor construye el mensaje y lo envía al broker.

Programado

El broker almacena el mensaje en un sistema de almacenamiento basado en tiempo. Aún no se crea ningún índice visible para el consumidor.

Listo para consumo

Al llegar la marca de tiempo de entrega, el mensaje se traslada al motor de almacenamiento habitual y se vuelve visible para los consumidores.

En consumo

Un consumidor recibe y procesa el mensaje. Si no llega un acuse de recibo dentro del tiempo de espera, el broker reintenta la entrega. Para más información, consulte Consumption retry.

Confirmado

El consumidor confirma la recepción del mensaje. El broker lo marca como consumido, pero no lo elimina de inmediato.

Eliminado

El broker elimina el mensaje de forma rotativa cuando expira el periodo de retención o queda poco espacio de almacenamiento. Hasta su eliminación, el mensaje puede volver a consumirse. Para más información, consulte Message storage and cleanup.

Límites

Restricción

Detalle

Tipo de topic

Solo los topics con MessageType configurado como Delay aceptan mensajes programados.

Precisión temporal

Exactitud en milisegundos. La granularidad predeterminada es de 1,000 ms.

Persistencia

Los mensajes programados sobreviven a los reinicios del broker. No obstante, las excepciones o reinicios del sistema de almacenamiento pueden provocar breves retrasos en la entrega.

Cancelación

No compatible. Una vez enviado el mensaje, la marca de tiempo de entrega no puede modificarse ni revocarse.

Envío y consumo de mensajes programados

A continuación, los ejemplos usan el SDK de Java de Apache RocketMQ 5.x. Cada ejemplo de productor establece una marca de tiempo de entrega con setDeliveryTimestamp(); esta es la única diferencia respecto al envío de un mensaje normal. Para consultar la referencia completa del SDK, véase Apache RocketMQ 5.x SDKs.

Asegúrese de que su topic se haya creado con MessageType configurado como Delay antes de ejecutar estos ejemplos.

Código de ejemplo

Enviar un mensaje programado

import org.apache.rocketmq.client.apis.*;
import org.apache.rocketmq.client.apis.message.Message;
import org.apache.rocketmq.client.apis.producer.Producer;
import org.apache.rocketmq.client.apis.producer.SendReceipt;

public class ProducerExample {
    public static void main(String[] args) throws ClientException {
        // Instance endpoint. Find this on the Endpoints tab of the Instance Details
        // page in the ApsaraMQ for RocketMQ console.
        // For internal access from ECS in the same VPC, use the VPC endpoint.
        // For public access, use the public endpoint and enable Internet access
        // on the instance.
        String endpoints = "rmq-cn-xxx.{regionId}.rmq.aliyuncs.com:8080";

        // Topic name. Create this topic in the console first, with MessageType
        // set to Delay.
        String topic = "Your Topic";

        ClientServiceProvider provider = ClientServiceProvider.loadService();
        ClientConfigurationBuilder builder = ClientConfiguration.newBuilder().setEndpoints(endpoints);

        // For public endpoint access, provide your instance username and password.
        // Find these on the Intelligent Authentication tab of the Access Control
        // page in the console.
        // For VPC access from ECS, credentials are obtained automatically.
        // For serverless instances with authentication-free in VPCs enabled,
        // credentials are not required for VPC access.
        //builder.setCredentialProvider(new StaticSessionCredentialsProvider("Instance UserName", "Instance Password"));

        ClientConfiguration configuration = builder.build();
        Producer producer = provider.newProducerBuilder()
                .setTopics(topic)
                .setClientConfiguration(configuration)
                .build();

        // Deliver the message 10 minutes from now
        long deliverTimeStamp = System.currentTimeMillis() + 10L * 60 * 1000;

        Message message = provider.newMessageBuilder()
                .setTopic("topic")
                .setKeys("messageKey")       // Message key for tracing
                .setTag("messageTag")        // Tag for consumer-side filtering
                .setDeliveryTimestamp(deliverTimeStamp)
                .setBody("messageBody".getBytes())
                .build();
        try {
            SendReceipt sendReceipt = producer.send(message);
            System.out.println(sendReceipt.getMessageId());
        } catch (ClientException e) {
            e.printStackTrace();
        }
    }
}

Consumo con un push consumer

Un push consumer recibe mensajes mediante una función de callback de escucha de mensajes. El broker envía los mensajes al consumidor conforme están listos.

import org.apache.rocketmq.client.apis.*;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.PushConsumer;
import org.apache.rocketmq.shaded.org.slf4j.Logger;
import org.apache.rocketmq.shaded.org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collections;

public class PushConsumerExample {
    private static final Logger LOGGER = LoggerFactory.getLogger(PushConsumerExample.class);

    private PushConsumerExample() {
    }

    public static void main(String[] args) throws ClientException, IOException, InterruptedException {
        // Instance endpoint (see producer example for details)
        String endpoints = "rmq-cn-xxx.{regionId}.rmq.aliyuncs.com:8080";

        // Create the topic and consumer group in the console before running
        // this example
        String topic = "Your Topic";
        String consumerGroup = "Your ConsumerGroup";

        final ClientServiceProvider provider = ClientServiceProvider.loadService();
        ClientConfigurationBuilder builder = ClientConfiguration.newBuilder().setEndpoints(endpoints);

        // Uncomment for public endpoint access (see producer example for details)
        //builder.setCredentialProvider(new StaticSessionCredentialsProvider("Instance UserName", "Instance Password"));
        ClientConfiguration clientConfiguration = builder.build();

        // Subscribe to all messages in the topic (tag = "*")
        String tag = "*";
        FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);

        PushConsumer pushConsumer = provider.newPushConsumerBuilder()
                .setClientConfiguration(clientConfiguration)
                .setConsumerGroup(consumerGroup)
                .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression))
                .setMessageListener(messageView -> {
                    System.out.println("Consume Message: " + messageView);
                    return ConsumeResult.SUCCESS;
                })
                .build();

        Thread.sleep(Long.MAX_VALUE);
        // Shut down when no longer needed
        //pushConsumer.close();
    }
}

Consumo con un simple consumer

Un simple consumer extrae mensajes del broker y confirma explícitamente cada uno después de procesarlo.

import org.apache.rocketmq.client.apis.*;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.SimpleConsumer;
import org.apache.rocketmq.client.apis.message.MessageId;
import org.apache.rocketmq.client.apis.message.MessageView;
import org.apache.rocketmq.shaded.org.slf4j.Logger;
import org.apache.rocketmq.shaded.org.slf4j.LoggerFactory;

import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
import java.util.List;

public class SimpleConsumerExample {
    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleConsumerExample.class);

    private SimpleConsumerExample() {
    }

    public static void main(String[] args) throws ClientException, IOException {
        // Instance endpoint (see producer example for details)
        String endpoints = "rmq-cn-xxx.{regionId}.rmq.aliyuncs.com:8080";

        // Create the topic and consumer group in the console before running
        // this example
        String topic = "Your Topic";
        String consumerGroup = "Your ConsumerGroup";

        final ClientServiceProvider provider = ClientServiceProvider.loadService();
        ClientConfigurationBuilder builder = ClientConfiguration.newBuilder().setEndpoints(endpoints);

        // Uncomment for public endpoint access (see producer example for details)
        //builder.setCredentialProvider(new StaticSessionCredentialsProvider("Instance UserName", "Instance Password"));
        ClientConfiguration clientConfiguration = builder.build();

        // Long-polling timeout
        Duration awaitDuration = Duration.ofSeconds(10);

        // Subscribe to all messages in the topic (tag = "*")
        String tag = "*";
        FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);

        SimpleConsumer consumer = provider.newSimpleConsumerBuilder()
                .setClientConfiguration(clientConfiguration)
                .setConsumerGroup(consumerGroup)
                .setAwaitDuration(awaitDuration)
                .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression))
                .build();

        int maxMessageNum = 16;
        Duration invisibleDuration = Duration.ofSeconds(10);

        // Pull and process messages in a loop.
        // For real-time consumption, use multiple threads to pull concurrently.
        while (true) {
            final List<MessageView> messages = consumer.receive(maxMessageNum, invisibleDuration);
            messages.forEach(messageView -> {
                System.out.println("Received message: " + messageView);
            });
            for (MessageView message : messages) {
                final MessageId messageId = message.getMessageId();
                try {
                    consumer.ack(message);
                    System.out.println("Message is acknowledged successfully, messageId= " + messageId);
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        }
        // Shut down when no longer needed
        // consumer.close();
    }
}

Mejores prácticas

Escalone los tiempos de entrega. Evite programar un lote grande de mensajes para la misma marca de tiempo exacta. Cuando muchos mensajes comparten una hora de entrega, el broker debe procesarlos todos simultáneamente, lo que aumenta la carga y puede retrasar la entrega. Distribuya las marcas de tiempo en un rango siempre que sea posible.

Implemente lógica de cancelación en los consumidores. Dado que no es posible revocar ni reprogramar un mensaje después de enviarlo, gestione la cancelación en el lado del consumidor. Por ejemplo, cuando un consumidor reciba un mensaje de "cancelar pedido no pagado", compruebe primero el estado actual del pedido; si el usuario ya pagó, omita la cancelación.

Preguntas frecuentes

¿Puedo cancelar o reprogramar un mensaje después de enviarlo?

No. La marca de tiempo de entrega es definitiva una vez que se envía el mensaje. Para manejar escenarios en los que una acción programada deja de ser necesaria, implemente esa lógica en su consumidor; por ejemplo, compruebe el estado actual del pedido antes de cancelar un pedido no pagado.

¿Qué sucede si establezco una hora de entrega en el pasado?

El broker lo entrega de inmediato, igual que un mensaje normal.

¿Por qué no encuentro mi mensaje programado en la consola?

Los mensajes programados permanecen invisibles hasta que se alcanza la marca de tiempo de entrega. Vuelva a comprobarlo después de esa hora; el mensaje aparecerá y estará disponible para los consumidores.