RocketMQ 发送普通消息

普通消息的应用场景

普通消息一般应用于微服务解耦、事件驱动、数据集成等场景,这些场景大多数要求数据传输通道具有可靠传输的能力,且对消息的处理时机、处理顺序没有特别要求。如:上游订单系统支付后,下游的物流配送系统、用户积分系统的相关操作。

1. 创建主题

主题的类型与消息的类型要对应,所以普通消息的主题类型为 Normal

./mqadmin updatetopic -n localhost:9876 -c DefaultCluster -t MY_NORMAL_TOPIC -a +message.type=NORMAL

示例中创建了一个名为 MY_NORMAL_TOPIC 的普通消息主题

注:我们的入门示例其实就是一个普通消息,只是我们没有设置消息类型,不设置默认就是NORMAL

rocketmq-client-java 示例(gRPC 协议)

生产者示例代码

import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.message.Message;
import org.apache.rocketmq.client.apis.producer.Producer;
import org.apache.rocketmq.client.apis.producer.SendReceipt;

import java.io.IOException;

public class NormalProducerDemo {

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

        // 用于提供:生产者、消费者、消息对应的构建类 Builder
        ClientServiceProvider provider = ClientServiceProvider.loadService();

        // 构建配置类(包含端点位置、认证以及连接超时等的配置)
        ClientConfiguration configuration = ClientConfiguration.newBuilder()
                // endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
                .setEndpoints(MyMQProperties.ENDPOINTS)
                .build();

        // 构建生产者
        Producer producer = provider.newProducerBuilder()
                // Topics 列表:生产者和主题是多对多的关系,同一个生产者可以向多个主题发送消息
                .setTopics("MY_NORMAL_TOPIC","TestTopic")
                .setClientConfiguration(configuration)
                // 构建生产者,此方法会抛出 ClientException 异常
                .build();

        // 构建消息类
        Message message = provider.newMessageBuilder()
                // 设置消息发送到的主题
                .setTopic("MY_NORMAL_TOPIC")
                // 设置消息索引键,可根据关键字精确查找某条消息。其一般为业务上的唯一值。如:订单id
                .setKeys("order_id_1001")
                // 设置消息Tag,用于消费端根据指定Tag过滤消息。其一般用作区分不同的业务,最好给它定义好命名规范
                .setTag("ORDER_SUBMIT")
                // 消息体,单条消息的传输负载不宜过大。所以此处的字节大小最好有个限制
                .setBody("{\"success\":true}".getBytes())
                .build();

        // 发送消息(此处最好进行异常处理,对消息的状态进行一个记录)
        try {
            SendReceipt sendReceipt = producer.send(message);
            System.out.println("Send message successfully, messageId=" + sendReceipt.getMessageId());
        } catch (ClientException e) {
            System.out.println("Failed to send message");
        }


        // 发送完,关闭生产者
        try {
            producer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

生产者异步发送消息

final CompletableFuture<SendReceipt> future = producer.sendAsync(message);
ExecutorService sendCallbackExecutor = Executors.newCachedThreadPool();
future.whenCompleteAsync((sendReceipt, throwable) -> {
    if (null != throwable) {
        System.out.println("Failed to send message" + throwable.getMessage());
        // Return early.
        return;
    }
    System.out.println("Send message successfully, messageId={}" + sendReceipt.getMessageId());
}, sendCallbackExecutor);

生产者注意事项

  1. 消息体有最大大小限制,不建议传输大负载

RocketMQ 一般传输的是都是业务事件数据。单个原子消息事件的数据大小需要严格控制,如果单条消息过大容易造成网络传输层压力,不利于异常重试和流量控制。

系统默认的消息最大限制如下:

  • 普通和顺序消息:4 MB
  • 事务和定时或延时消息:64 KB

生产环境中如果需要传输超大负载,建议按照固定大小做报文拆分,或者结合文件存储等方法进行传输。

  1. 不建议单一进程创建大量生产者

RocketMQ 的生产者和主题是多对多的关系,支持同一个生产者向多个主题发送消息。对于生产者的创建和初始化,建议遵循够用即可、最大化复用原则,如果有需要发送消息到多个主题的场景,无需为每个主题都创建一个生产者。

  1. 不建议频繁创建和销毁生产者

RocketMQ 的生产者是可以重复利用的底层资源,类似数据库的连接池。因此不需要在每次发送消息时动态创建生产者,且在发送结束后销毁生产者。这样频繁的创建销毁会在服务端产生大量短连接请求,严重影响系统性能。

综上,我们的示例仅为示例,请勿轻易在生产环境中直接使用。实际中可能需要将其定义为一个线程安全的单例,更复杂一点可能需要定义为对象池的方式来提供生产者实例。

消费者示例代码

import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
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 java.nio.ByteBuffer;
import java.util.Collections;

public class NormalConsumerDemo {

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

        // 用于提供:生产者、消费者、消息对应的构建类 Builder
        ClientServiceProvider provider = ClientServiceProvider.loadService();

        // 构建配置类(包含端点位置、认证以及连接超时等的配置)
        ClientConfiguration configuration = ClientConfiguration.newBuilder()
                // endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
                .setEndpoints(MyMQProperties.ENDPOINTS)
                .build();


        // 设置过滤条件(这里为使用 tag 进行过滤)
        String tag = "ORDER_SUBMIT";
        FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);

        // 构建消费者
        PushConsumer pushConsumer = provider.newPushConsumerBuilder()
                .setClientConfiguration(configuration)
                // 设置消费者分组
                .setConsumerGroup("MY_ORDER_SUBMIT_GROUP")
                // 设置主题与消费者之间的订阅关系
                .setSubscriptionExpressions(Collections.singletonMap("MY_NORMAL_TOPIC", filterExpression))
                .setMessageListener(messageView -> {
                    System.out.println(messageView);
                    ByteBuffer rs = messageView.getBody();
                    byte[] rsByte = new byte[rs.limit()];
                    rs.get(rsByte);

                    System.out.println("Message body:" + new String(rsByte));
                    // 处理消息并返回消费结果。
                    System.out.println("Consume message successfully, messageId=" + messageView.getMessageId());
                    return ConsumeResult.SUCCESS;
                }).build();


        // 如果不需要再使用 PushConsumer,可关闭该实例。
        // pushConsumer.close();

    }

}

messageView 对象还可以获取 messageId 、Topic 等信息。

这里有消费者分组、消息过滤等知识点,我们后续都会提到,这里先有个体验即可。

消费者注意事项

  1. 不建议单一进程创建大量消费者

RocketMQ 的消费者在通信协议层面支持非阻塞传输模式,网络通信效率较高,并且支持多线程并发访问。因此,大部分场景下,单一进程内同一个消费分组只需要初始化唯一的一个消费者即可,开发过程中应避免以相同的配置初始化多个消费者。

  1. 不建议频繁创建和销毁消费者

RocketMQ 的消费者是可以重复利用的底层资源,类似数据库的连接池。因此不需要在每次接收消息时动态创建消费者,且在消费完成后销毁消费者。这样频繁地创建销毁会在服务端产生大量短连接请求,严重影响系统性能。

rocketmq-client 示例(Remoting 协议)

生产者

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

public class NormalProducerDemo {

    /**
     * 生产者分组
     */
    private static final String PRODUCER_GROUP = "NORMAL_PRODUCER_GROUP";

    /**
     * 主题
     */
    private static final String TOPIC = "MY_NORMAL_TOPIC";

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


        /*
         * 创建生产者,并使用生产者分组初始化
         */
        DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);

        /*
         * NamesrvAddr 的地址,多个用分号隔开。如:xxx:9876;xxx:9876
         */
        producer.setNamesrvAddr(MyMQProperties.NAMESRV_ADDR);

        /*
         * 发送消息超时时间,默认即为 3000
         */
        producer.setSendMsgTimeout(3000);

        /*
         * 启动生产者,此方法抛出 MQClientException
         */
        producer.start();


        /*
         * 发送消息
         */
        for (int i = 1; i <= 2; i++) {

            try {
                Message msg = new Message();
                msg.setTopic(TOPIC);
                // 设置消息索引键,可根据关键字精确查找某条消息。
                msg.setKeys("messageKey");
                // 设置消息Tag,用于消费端根据指定Tag过滤消息。
                msg.setTags("messageTag");
                // 设置消息体
                msg.setBody(("messageBody" + i).getBytes());

                // 此为同步发送方式
                /*SendResult rs = producer.send(msg);
                System.out.printf("%s%n",rs);*/

                producer.send(msg, new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        System.out.printf("消息发送成功:%s%n", sendResult);
                    }

                    @Override
                    public void onException(Throwable e) {
                        System.out.println("消息发送失败:" + e.getMessage());
                    }
                });

            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("消息发送失败!i = " + i);
            }

        }


        // 如果生产者不再使用,则调用关闭
        // 异步发送消息注意:异步发送消息,建议此处不关闭或者在sleep一段时间后再关闭
        // 因为异步 SendCallback 执行的时候,shutdow可能已经执行了,生产者被关闭了
        // producer.shutdown();
    }

}

消费者

import com.yyoo.mq.rocket.MyMQProperties;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;

public class NormalConsumerDemo {

    /**
     * 设置消费者分组
     */
    public static final String CONSUMER_GROUP = "NORMAL_CONSUMER_GROUP";
    /**
     * 主题
     */
    public static final String TOPIC = "MY_NORMAL_TOPIC";


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

        /*
         * 通过消费者分组,创建消费者
         */
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);

        /*
         * NamesrvAddr 的地址,多个用分号隔开。如:xxx:9876;xxx:9876
         */
        consumer.setNamesrvAddr(MyMQProperties.NAMESRV_ADDR);

        /*
         * 指定从哪一个消费位点开始消费 CONSUME_FROM_FIRST_OFFSET 表示从第一个开始
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        /*
         * 消费者订阅的主题,和过滤条件
         * 我们这里使用 * 表示,消费者消费主题下的所有消息,多个tag 使用 || 隔开
         */
        consumer.subscribe(TOPIC, "*");

        /*
         * 注册消费监听
         */
        consumer.registerMessageListener((MessageListenerConcurrently) (msg, context) -> {
            System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        /*
         * 启动消费者.
         */
        consumer.start();

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

        // 如果消费者不再使用,关闭
        // consumer.shutdown();

    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值