RocketMQ系列(三)消息生产消费原生写法

上篇文章《RocketMQ系列(二)双主双从集群搭建》中,我们搭建了双主双从的集群环境,并启动了可视化平台rocketmq-console,本文我们使用原生api发送一些消息到mq中,然后进行消费。

一.生产消息

导入mq客户端的依赖包:

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

1.1 同步消息

 public static void main(String[] args) throws Exception {
        //1.创建消息生产者,并指定组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定nameserver的地址
        producer.setNamesrvAddr("192.168.17.101:9876;192.168.17.102:9876");
        //3.启动producer
        producer.start();
        //4.创建消息
        for (int i = 0; i < 10; i++) {
            Message message = new Message("base", "tag1", ("hello world" + i).getBytes());

            //5.发送消息
            SendResult sendResult = producer.send(message);
            System.out.println(sendResult.toString());
            TimeUnit.SECONDS.sleep(2);
        }
        //6.关闭生产者
        producer.shutdown();
    }

打印输出如下:

SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C4F520000, offsetMsgId=C0A8116600002A9F00000000000000A9, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=2], queueOffset=1]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C57420001, offsetMsgId=C0A8116600002A9F0000000000000152, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=3], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C5F180002, offsetMsgId=C0A8116600002A9F00000000000001FB, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=0], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C66F90003, offsetMsgId=C0A8116600002A9F00000000000002A4, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=1], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C6ECE0004, offsetMsgId=C0A8116600002A9F000000000000034D, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=2], queueOffset=2]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C76A60005, offsetMsgId=C0A8116600002A9F00000000000003F6, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=3], queueOffset=1]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C7E7A0006, offsetMsgId=C0A8116600002A9F000000000000049F, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=0], queueOffset=1]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C86500007, offsetMsgId=C0A8116600002A9F0000000000000548, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=1], queueOffset=1]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C8E2C0008, offsetMsgId=C0A8116600002A9F00000000000005F1, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=2], queueOffset=3]
SendResult [sendStatus=SEND_OK, msgId=C0A8010603A018B4AAC2618C96030009, offsetMsgId=C0A8116600002A9F000000000000069A, messageQueue=MessageQueue [topic=base, brokerName=broker-b, queueId=3], queueOffset=2]
22:36:45.438 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.17.101:9876] result: true
22:36:45.443 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.17.101:10911] result: true
22:36:45.443 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.17.102:10909] result: true
22:36:45.443 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.17.102:11011] result: true
22:36:45.443 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.17.101:11011] result: true
22:36:45.443 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.17.102:10911] result: true

在mq-console控制台上观察如下:

topic:
在这里插入图片描述
运行两次后,产生20条信息:
在这里插入图片描述

在这里插入图片描述

1.2 异步消息

public static void main(String[] args) throws Exception {
    //1.创建消息生产者,并指定组名
    DefaultMQProducer producer = new DefaultMQProducer("group1");
    //2.指定nameserver的地址
    producer.setNamesrvAddr("192.168.17.101:9876;192.168.17.102:9876");
    //3.启动producer
    producer.start();
    //4.创建消息
    for (int i = 0; i < 10; i++) {
        Message message = new Message("base", "tag2", ("hello world" + i).getBytes());
        //5.发送消息
        producer.send(message, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("发送结果" + sendResult.toString());
            }
            @Override
            public void onException(Throwable e) {
                System.out.println("发送异常" + e);
            }
        });
        TimeUnit.SECONDS.sleep(2);
    }
    //6.关闭生产者
    producer.shutdown();
}

其中 SendCallback是回调函数,生产者的异步发送线程会根据发送消息的结果,调用成功或者失败的接口方法。

1.3 顺序消息

生产者把消息发送到broker时,由于每个broker内部不是只有1个队列,因此消费者也不是从某一个队列进行消费,因此默认生产者的消息顺序和消费者的消费顺序并不一定是一样的。

从rocketmq-console可以查看消费消息的队列情况:
在这里插入图片描述
上图信息可以看出,同一个broker-b,有0-3,共4个队列。

实际生产和消费的模型图如下:
在这里插入图片描述

因此,要向保证生产和消费的消息顺序一致,生产者和消费者必须使用同一个队列,生产消息的时候,必须把消息往同一个queue中推送。

但是这里要区分全局消息和局部消息,实际业务场景中,只要保证局部消息有序即可,比如张三和李四下单付款过程,只需要保证单个人下单、付款、推送的顺序即可,张三和李四俩人的消息顺序无所谓。

所以我们可以根据唯一标识,比如订单id,与队列数目取余,就是队列下标了,

demo如下:

//4.创建消息
//4.创建消息
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        Message message = new Message("orderTopic", "order", "i" + i, ("hello world" + i + "-" + j).getBytes());
        //5.发送消息
        int orderId = i;
        SendResult sendResult = producer.send(message, new MessageQueueSelector() {
            /**
             *
             * @param mqs broker的队列集合
             * @param msg 消息对象
             * @param arg 业务唯一标识,就是send方法的第3个参数
             * @return
             */
            @Override
            public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                int orderId = (int) arg;
                return mqs.get(orderId % mqs.size());
            }
        }, orderId);
        System.out.println("发送结果:" + sendResult);
        TimeUnit.SECONDS.sleep(2);
    }

}

上述demo,关键的地方是使用了带MessageQueueSelector参数的send方法。
我们使用变量 i 作为业务唯一标识orderId,总共发送了3组,每组4个消息。

1.4 延迟消息

发送延迟消息很简单,只需要对消息对象设置如下属性:

 //设置消息延迟
 message.setDelayTimeLevel(2);

其中,参数表示延迟时间的等级,具体等级如下,

//org\apache\rocketmq\store\config\MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

参数设置为2,表示延迟5s。

二.消息消费

2.1 广播模式

 public static void main(String[] args) throws Exception {
   //1.创建消费者,指定组名
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
    //2.指定nameserver地址
    consumer.setNamesrvAddr("192.168.17.101:9876;192.168.17.102:9876");
    //3.订阅主题和tag
    consumer.subscribe("base", "tag1");
    //广播模式||负载均衡模式
    consumer.setMessageModel(MessageModel.BROADCASTING);
    //4.设置回调函数,处理消息
    consumer.registerMessageListener(new MessageListenerConcurrently() {
        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
            for (MessageExt msg : msgs) {
                System.out.println(new String(msg.getBody()));
            }

            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });
    //5.启动消费者
    consumer.start();
}

说明:

  • MessageModel.BROADCASTING:广播模式,当有多个消费者订阅同一个topic时,每个消费者消费的消息是相同的。
  • consumer.subscribe(“base”, “tag1”); 可以同时订阅多个tag,比如tag1||tag2,也可以直接用 * 号代替,表示所有。

上面demo中,我们使用了DefaultMQPushConsumer,属于push模式,属于broker推送到消费者;
还有一种pull模式,DefaultMQPullConsumer,属于消费者主动去broker拉取消息。

2.2 负载均衡

只需要如下设置,或者不设置此属性,默认就是负载均衡模式,

 consumer.setMessageModel(MessageModel.CLUSTERING);

MessageModel.CLUSTERING:属于集群模式,也叫负载均衡模式,当有多个消费者订阅同一个topic时,每个消费者轮流消费生产者的消息,每台消费者消费的并集和等于生产消息总和。

2.3 顺序消息

上面消费消息的时候,监听器的参数是MessageListenerConcurrently,实际还有一个重载方法,参数是MessageListenerOrderly,顺序消费,就是使用这个:
在这里插入图片描述

 //顺序消费
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
       for (MessageExt msg : msgs) {
                    System.out.println("线程名称" + Thread.currentThread().getName() +"消费消息" + new String(msg.getBody()));
                }
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

原理
保证一个队列的消息,由同一个线程进行消费。

上文中,顺序消息发送的时候,我们使用变量 i 作为业务唯一标识orderId,总共发送了3组,每组4个消息,存入broker时,应该使用了3个queue,
因此消费者顺序消费时,应该启用3个线程,分别针对broker的3个队列,运行结果如下:

线程名称ConsumeMessageThread_2消费消息hello world1-0
线程名称ConsumeMessageThread_1消费消息hello world2-0
线程名称ConsumeMessageThread_3消费消息hello world0-0
线程名称ConsumeMessageThread_2消费消息hello world1-1
线程名称ConsumeMessageThread_1消费消息hello world2-1
线程名称ConsumeMessageThread_3消费消息hello world0-1
线程名称ConsumeMessageThread_1消费消息hello world2-2
线程名称ConsumeMessageThread_2消费消息hello world1-2
线程名称ConsumeMessageThread_1消费消息hello world2-3
线程名称ConsumeMessageThread_2消费消息hello world1-3
线程名称ConsumeMessageThread_3消费消息hello world0-2
线程名称ConsumeMessageThread_3消费消息hello world0-3

从结果看出,实际确实使用了3个线程,每个线程消费的顺序是有序的,都是0123。

2.4 过滤消息

消费者在消费消息时,可以有针对性的进行消费,有两种方式。

1. tag过滤

在创建消费者的时候,我们需要指定订阅的组和tag,如下,

 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");   
 consumer.subscribe("myTopic", "tag1||tag2||tag3");

我们看下subcribe的方法描述:

/**
 * Subscribe a topic to consuming subscription.
 *
 * @param topic topic to subscribe.
 * @param subExpression subscription expression.it only support or operation such as "tag1 || tag2 || tag3" <br>
 * if null or * expression,meaning subscribe all
 * @throws MQClientException if there is any client error.
 */
@Override
public void subscribe(String topic, String subExpression) throws MQClientException {
    this.defaultMQPushConsumerImpl.subscribe(topic, subExpression);
}

其中参数:

  • topic: 订阅的主题,消费时只会找这个主题下的消息;
  • subExpression : 发送消息时会指定tag参数,这里就是tag的名字,如果要从多个tag消费,可以写为tag1 || tag2 || tag3,或者直接写 “*”。

2. sql语法过滤

sql过滤,使用的subcribe方法如下:

 /**
  * Subscribe a topic by message selector.
  *
  * @param topic topic to consume.
  * @param messageSelector {@link org.apache.rocketmq.client.consumer.MessageSelector}
  * @see org.apache.rocketmq.client.consumer.MessageSelector#bySql
  * @see org.apache.rocketmq.client.consumer.MessageSelector#byTag
  */
 @Override
 public void subscribe(final String topic, final MessageSelector messageSelector) throws MQClientException {
     this.defaultMQPushConsumerImpl.subscribe(topic, messageSelector);
 }

其中参数:

  • topic: 订阅的主题,消费时只会找这个主题下的消息;
  • messageSelector : 指定一段sql语句。

具体例子如下:

consumer.subscribe("filterSqlTopic", MessageSelector.bySql("i>5"));      

其中sql就是 “i>5”,这个i是什么意思呢?它是生产者在生产消息时指定的属性,生产者例子如下:

for (int i = 0; i < 10; i++) {
    Message message = new Message("filterSqlTopic", "tag1", ("hello world" + i).getBytes());
    //设置消息属性
    message.putUserProperty("i", String.valueOf(i));
    //5.发送消息
    SendResult sendResult = producer.send(message);
    System.out.println(sendResult.toString());
    TimeUnit.SECONDS.sleep(2);
}

上面生产者代码,发送了10条信息,putUserProperty方法输入了一个key和一个value,其中key的名字就是"i",值是i的值,也就是0到10;
那么消费者指定 MessageSelector.bySql(“i>5”),就是只会消费 i 值大于5的。

其中,sql的写法还支持类似如下几种方式:

  • a>5 AND b=‘abc’ ; > ,>=,<,<=,BETWEEN
  • IS NULL IS NOT NULL

以及其他sql中常用比较和逻辑符号。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值