Explicar el uso de RocketMQ en detalle

Tabla de contenido

1. Medio ambiente

2. Modelo de productor y consumidor

3. Mensajes secuenciales

4. Mensaje de difusión

5. Mensajes retrasados

6. Mensajes por lotes

7. Filtrar mensajes

8. Mensajes transaccionales


Este artículo se centra en el modelo de programación de RocketMQ. La descarga, la instalación y el concepto se pueden mover a las otras dos publicaciones del blog del blogger:

Conceptos básicos del blog RocketMQ__BugMan-CSDN Blog

Descarga e instalación de RocketMQ, tutorial de creación de clústeres a nivel de niñera_Blog de BugMan-Blog de CSDN

1. Medio ambiente

Versión de RocketMQ: 4.7.1

Maven depende de:

<dependencia> 
    <groupId>org.apache.rocketmq</groupId> 
    <artifactId>rocketmq-client</artifactId> 
    <version>4.7.1</version> 
</dependency>

2. Modelo de productor y consumidor

El productor tiene tres modos de producción:

  • Sincrónicamente, el productor espera la respuesta del corredor antes de bajar.

  • Asíncrono, el productor no espera la respuesta del corredor y continúa bajando.La respuesta del corredor activa una función de devolución de llamada a través del monitoreo de eventos para notificar al productor.

  • Unidireccional, el productor simplemente envía, independientemente de la respuesta del corredor, este modo no puede hacer el remedio después de que se pierde el mensaje

Los consumidores tienen dos modos de consumo:

  • Tirón activo

  • esperando para empujar

Ejemplo de productor:

public static void main(String[] args) throws MQClientException, InterruptedException {
		//创建消费者,创建的时候可以指定该消费者属于哪个消费者组
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        //指定name server的地址
        producer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
        producer.start();
		//发送一千条信息
        for (int i = 0; i < 1000; i++) {
            try {
            	//消息,topic为TopicTest,后面跟的一串是tag
                Message msg = new Message("TopicTest" /* Topic */,
                    "TagA" /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );
                //同步发送
                SendResult sendResult = producer.send(msg);
				/**异步发送,通过自定义回调函数的方式来触发响应
				producer.send(msg, new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId());
                    }

                    @Override
                    public void onException(Throwable e) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d Exception %s %n", index, e);
                        e.printStackTrace();
                    }
                }**/
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }

Ejemplo de consumidor:

modo de empuje:

public static void main(String[] args) throws InterruptedException, MQClientException {
		//创建消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
        //设置name server
        consumer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }

Modo de extracción:

private static final Map<MessageQueue, Long> OFFSE_TABLE = new HashMap<MessageQueue, Long>();

    public static void main(String[] args) throws MQClientException {
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("please_rename_unique_group_name_5");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.start();

        Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("broker-a");
        for (MessageQueue mq : mqs) {
            System.out.printf("Consume from the queue: %s%n", mq);
            SINGLE_MQ:
            while (true) {
                try {
                    PullResult pullResult =
                        consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32);
                    System.out.printf("%s%n", pullResult);
                    putMessageQueueOffset(mq, pullResult.getNextBeginOffset());
                    switch (pullResult.getPullStatus()) {
                        case FOUND:
                            break;
                        case NO_MATCHED_MSG:
                            break;
                        case NO_NEW_MSG:
                            break SINGLE_MQ;
                        case OFFSET_ILLEGAL:
                            break;
                        default:
                            break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        consumer.shutdown();
    }

    private static long getMessageQueueOffset(MessageQueue mq) {
        Long offset = OFFSE_TABLE.get(mq);
        if (offset != null)
            return offset;

        return 0;
    }

    private static void putMessageQueueOffset(MessageQueue mq, long offset) {
        OFFSE_TABLE.put(mq, offset);
    }

}

3. Mensajes secuenciales

En algunos escenarios, debemos asegurar el orden de los mensajes, como los siguientes escenarios:

1. Regístrese como miembro e inicialice los puntos a 10 puntos

2. Completar la operación de ganar puntos, +5 puntos

3. Completar la operación ilegal, -10 puntos

quedan 5 puntos

Si el mensaje fuera de orden es 321, la puntuación final es de 10 puntos.

El productor envía los mensajes a MessageQueue secuencialmente, y luego el consumidor va a MessageQueue para obtener los mensajes, de modo que se pueda garantizar el orden de los mensajes.

RocketMQ ayuda a los productores y consumidores a especificar MessageQueue durante la producción y el consumo, pero cómo usar esto para lograr un consumo secuencial requiere que los desarrolladores escriban a mano.

Productor:

for (int i = 0; i < 100; i++) {
                for(int j=0;i<5;i++) {
                    //每一个订单用同一个orderId
                    int orderId = i;
                    Message msg =
                            new Message("TopicTest","order_"+orderId, "KEY" + orderId,
                                    ("order_" + orderId+"step"+j).getBytes(RemotingHelper.DEFAULT_CHARSET));
                    SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                        /**
                         * 回调函数
                         * @param mqs broker中的所有MessageQueue
                         * @param msg 发送的消息
                         * @param arg 就是传过来的orderId
                         * @return
                         */
                        @Override
                        public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                            Integer id = (Integer) arg;
                            //每个订单的message用同一个orderId,必然会选中同一个MessageQueue,自然会顺序存进去
                            int index = id % mqs.size();
                            return mqs.get(index);
                        }
                    }, orderId);
                    System.out.printf("%s%n", sendResult);
                }
            }

consumidor:

//MessageListenerOrderly这种类型的监听器,会让consumer一直去读一个messagequeue的内容,一直读完为止
consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(true);
                for (MessageExt msg : msgs) {
                    System.out.println("收到的消息内容:"+new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

4. Mensaje de difusión

De forma predeterminada, un mensaje en un tema solo será consumido por un consumidor. En el modo de transmisión, un mensaje en un tema puede ser consumido por todos los consumidores suscritos al tema.

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_1");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//设置为广播模式
consumer.setMessageModel(MessageModel.BROADCASTING);
consumer.subscribe("TopicTest", "TagA || TagC || TagD");
consumer.registerMessageListener(new MessageListenerConcurrently() {
	@Override
	public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
           ConsumeConcurrentlyContext context) {
           System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
             return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
    });
    consumer.start();
    System.out.printf("Broadcast Consumer Started.%n");
    }

5. Mensajes retrasados

Retrasar el mensaje, es decir, especificar un tiempo de residencia de un mensaje en MQ, después de cuánto tiempo, después del tiempo de residencia, los consumidores pueden consumir el mensaje.

Los mensajes retrasados ​​se pueden usar para tareas de temporización.

En términos de API, en el lado del productor, simplemente especifique un nivel de retraso para el mensaje:

//messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
//3即第三个等级,10s
msg.setDelayTimeLevel(3);

Obviamente, los niveles de retraso anteriores son fijos, si queremos usarlo para tareas de cronometraje, debemos personalizar el nivel de retraso. La versión de código abierto de RocketMQ no admite niveles de latencia personalizados y solo la versión comercial (RocketMQ implementada en Alibaba Cloud) lo admite.

La versión de código abierto de RocketMQ solo puede cambiar el código fuente por sí misma, por lo que este también es el foco de la transformación cuando las empresas crean su propio RocketMQ personalizado.

6. Mensajes por lotes

RocketMQ ayuda a los productores a enviar mensajes por lotes para reducir la red de IO de los productores. Los mensajes por lotes solo están relacionados con los productores, y los consumidores siguen consumiendo uno por uno.

public class SimpleBatchProducer {

    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("BatchProducerGroupName");
        producer.start();

        //If you just send messages of no more than 1MiB at a time, it is easy to use batch
        //Messages of the same batch should have: same topic, same waitStoreMsgOK and no schedule support
        String topic = "BatchTest";
        List<Message> messages = new ArrayList<>();
        messages.add(new Message(topic, "Tag", "OrderID001", "Hello world 0".getBytes()));
        messages.add(new Message(topic, "Tag", "OrderID002", "Hello world 1".getBytes()));
        messages.add(new Message(topic, "Tag", "OrderID003", "Hello world 2".getBytes()));

        producer.send(messages);
    }
}

7. Filtrar mensajes

A través de etiquetas, los consumidores pueden consumir las noticias que les interesan bajo un mismo tema

productor:

public class TagFilterProducer {

    public static void main(String[] args) throws Exception {

        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        producer.start();

        String[] tags = new String[] {"TagA", "TagB", "TagC"};

        for (int i = 0; i < 60; i++) {
            Message msg = new Message("TagFilterTest",
                tags[i % tags.length],
                "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));

            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }

        producer.shutdown();
    }

consumidor:

public class TagFilterConsumer {

    public static void main(String[] args) throws InterruptedException, MQClientException, IOException {

        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name");

        consumer.subscribe("TagFilterTest", "TagA || TagC");

        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();

        System.out.printf("Consumer Started.%n");
    }
}

8. Mensajes transaccionales

Los mensajes transaccionales, es decir, los mensajes enviados a MQ se pueden revertir junto con las transacciones locales.

De hecho, habrá problemas de consistencia similares a las transacciones distribuidas entre mensajes MQ y empresas, por ejemplo:

Tomando el escenario de transacciones de comercio electrónico como ejemplo, la operación central del pago de los pedidos por parte del usuario también implicará cambios en múltiples subsistemas, como la entrega logística posterior, los cambios de puntos y la compensación del estado del carrito de compras. Obviamente, las llamadas de estos pasos deben estar en una transacción, y estas llamadas las puede hacer MQ, entonces surge el problema. Una vez que uno de estos negocios falla, los otros negocios deberían fallar al mismo tiempo, pero ¿qué sucede si el mensaje ya se envió?

Aquí es donde los mensajes de transacción son útiles.

Cuando el productor de un mensaje de transacción envía un mensaje, convertirá el mensaje en un medio (medio mensaje) y lo almacenará en un Tema RMQ_SYS_TRANS_HALF_TOPIC dentro de RocketMQ. Este Tema es invisible para los consumidores. Cuando la transacción local se ejecuta con éxito, se Envíe una señal de compromiso a MQ, y MQ convertirá el mensaje al tema original. Cuando la ejecución de la transacción local falla, enviará una señal de retroceso a MQ, y MQ descartará el mensaje correspondiente. Si la transacción local no se completa, puede enviar una señal desconocida a MQ. Después de recibir la señal desconocida, MQ hará esperar el mensaje correspondiente y verificará periódicamente el mensaje cuyo estado es desconocido.

 Personalice la lógica de confirmación y verifique la lógica de las transacciones locales:

public class TransactionListenerImpl implements TransactionListener {
    private AtomicInteger transactionIndex = new AtomicInteger(0);

    private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        int value = transactionIndex.getAndIncrement();
        int status = value % 3;
        localTrans.put(msg.getTransactionId(), status);
        return LocalTransactionState.UNKNOW;
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        Integer status = localTrans.get(msg.getTransactionId());
        if (null != status) {
            switch (status) {
                case 0:
                    return LocalTransactionState.UNKNOW;
                case 1:
                    return LocalTransactionState.COMMIT_MESSAGE;
                case 2:
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                default:
                    return LocalTransactionState.COMMIT_MESSAGE;
            }
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}

 Productor:

public class TransactionProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        TransactionListener transactionListener = new TransactionListenerImpl();
        TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });

        producer.setExecutorService(executorService);
        producer.setTransactionListener(transactionListener);
        producer.start();

        String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
        for (int i = 0; i < 10; i++) {
            try {
                Message msg =
                    new Message("TopicTest1234", tags[i % tags.length], "KEY" + i,
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.sendMessageInTransaction(msg, null);
                System.out.printf("%s%n", sendResult);

                Thread.sleep(10);
            } catch (MQClientException | UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        for (int i = 0; i < 100000; i++) {
            Thread.sleep(1000);
        }
        producer.shutdown();
    }
}

Supongo que te gusta

Origin blog.csdn.net/Joker_ZJN/article/details/131744728
Recomendado
Clasificación