RocketMQ系列(六)批量傳送與過濾

牛初九發表於2020-06-15

今天我們再來看看RocketMQ的另外兩個小功能,訊息的批量傳送和過濾。這兩個小功能提升了我們使用RocketMQ的效率。

批量傳送

以前我們傳送訊息的時候,都是一個一個的傳送,這樣效率比較低下。能不能一次傳送多個訊息呢?當然是可以的,RocketMQ為我們提供了這樣的功能。但是它也有一些使用的條件:

  • 同一批傳送的訊息的Topic必須相同;
  • 同一批訊息的waitStoreMsgOK 必須相同;
  • 批量傳送的訊息不支援延遲,就是上一節說的延遲訊息;
  • 同一批次的訊息,大小不能超過1MiB;

好了,只要我們滿足上面的這些限制,就可以使用批量傳送了,我們來看看傳送端的程式碼吧,

@Test
public void producerBatch() throws Exception {

    List<Message> messages = new ArrayList<>();
    for (int i = 0;i<3;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is batchMQ,my NO is "+i+"---"+new Date()).getBytes());
        messages.add(message);
    }
    SendResult sendResult = defaultMQProducer.send(messages);
    System.out.println("sendResult:" + sendResult.getSendStatus().toString());
}
  • 其實批量傳送很簡單,我們只是把訊息放到一個List當中,然後統一的呼叫send方法傳送就可以了。

再來看看消費端的程式碼,

@Bean(initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer pushConsumer()  {
    try {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("DefaultMQPushConsumer");
        consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
        consumer.subscribe("cluster-topic", "*");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                System.out.println("msgs.size():"+msgs.size());
                if (msgs != null && msgs.size() > 0) {
                    for (MessageExt msg : msgs) {
                        System.out.println(new String(msg.getBody()));
                    }
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        return consumer;
    }catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
  • 消費端的程式碼沒有任何的變化,正常的接收訊息就可以了,我們只是列印出了msgs.size(),看看一次接收一個訊息,還是一次可以批量的接收多個訊息。

我們啟動專案,批量傳送一下,看看效果吧,

傳送端的日誌如下:

sendResult:SEND_OK

傳送成功,看來我們批量傳送的3個訊息都進入到了佇列中,再看看消費端,是一次消費一個,還是一次消費3個,如下:

msgs.size():1
this is batchMQ,my NO is 0---Mon Jun 15 09:31:04 CST 2020
msgs.size():1
this is batchMQ,my NO is 1---Mon Jun 15 09:31:04 CST 2020
msgs.size():1
this is batchMQ,my NO is 2---Mon Jun 15 09:31:04 CST 2020

看樣子是一次只消費了一個訊息,那麼能不能一次消費3個訊息呢?當然是可以的,不過要進行特殊的設定,

consumer.setConsumeMessageBatchMaxSize(5);

在消費端,我們設定批量消費訊息的數量是5,這個值預設是1。我們再看看消費端的日誌,

msgs.size():3
this is batchMQ,my NO is 0---Mon Jun 15 09:35:47 CST 2020
this is batchMQ,my NO is 1---Mon Jun 15 09:35:47 CST 2020
this is batchMQ,my NO is 2---Mon Jun 15 09:35:47 CST 2020

這次一次消費了3個訊息,如果訊息比較多的話,最大一次能消費5個。這就是RocketMQ的批量傳送和批量消費。

訊息過濾

其實我們在大多數情況下,使用tag標籤就能夠很好的實現訊息過濾。雖然tag標籤我們們並沒有過多的介紹,其實也很好理解,就是一個子Topic的概念,我們們在構建訊息message的時候,message.setTags("xxx")。然後在消費的時候,訂閱Topic的時候,也可以指定訂閱的tag,

consumer.subscribe("cluster-topic", "*");

看到那個"*"了嗎?它就是訂閱的tag,"*"代表全部的tag,如果您想訂閱其中的一個或幾個,可以使用這種方式"tagA || tagB || tagC",這是訂閱了cluster-topic下的3個tag,其他的tag是不會被消費的。

這裡我們所說的訊息過濾比tag要高階很多,是可以支援sql的,怎麼樣?高階吧。比如:我們訂閱"a > 5 and b = 'abc'"的訊息,如下圖:

但是,RocketMQ畢竟不是資料庫,它只能支援一些基礎的SQL語句,並不是所有的SQL都支援,

  • 數字型的支援,>, >=, <, <=, BETWEEN, =

  • 字串支援,=, <>, IN

  • IS NULL或者IS NOT NULL

  • 邏輯判斷,ANDORNOT

欄位的型別也只是簡單的幾種,

  • 數字型,支援123,543.123,整型、浮點都可以;
  • 字串,必須使用單引號''括起來;
  • 空值,NULL;
  • 布林型,TRUE或者FALSE;

並且對消費者的型別也有一定的限制,只能使用push consumer才可以進行訊息過濾。好了,說了這麼多了,我們看看怎麼使用吧,消費端和生產端都要進行相應的改造,先看看生產端吧,

@Test
public void producerBatch() throws Exception {

    List<Message> messages = new ArrayList<>();
    for (int i = 0;i<3;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is batchMQ,my NO is "+i+"---"+new Date()).getBytes());

        int a = i+4;
        message.putUserProperty("a",String.valueOf(a));

        messages.add(message);
    }
    SendResult sendResult = defaultMQProducer.send(messages);
    System.out.println("sendResult:" + sendResult.getSendStatus().toString());
}

我們在之前批量傳送的基礎上進行了修改,定義了a的值,等於i+4,這樣迴圈3次,a的值就是4,5,6。然後呼叫message.putUserProperty("a",String.valueOf(a))注意,在使用訊息過濾的時候,這些附加的條件屬性都是通過putUserProperty方法進行設定。這裡,我們設定了a的值。再看看消費端,

consumer.subscribe("cluster-topic", MessageSelector.bySql("a > 5"));

消費端,整體上沒有變化,只是在訂閱的方法中,使用MessageSelector.bySql("a > 5"),進行了條件的過濾。有的小夥伴可能會有疑問,我既想用sql過濾又想用tag過濾怎麼辦?當然也是可以,我們可以使用MessageSelector.bySql("a > 5").byTag("xx),byTag和bySql不分前後,怎麼樣,很強大吧。我們執行一下程式,看看效果吧。

我們啟動一下服務,報錯了,怎麼回事?錯誤資訊如下:

The broker does not support consumer to filter message by SQL92

佇列不支援過濾訊息,我們查詢了RocketMQ原始碼中的BrokerConfig類,這個類就是對broker的一些設定,其中發現了這兩個屬性,

// whether do filter when retry.
private boolean filterSupportRetry = false;
private boolean enablePropertyFilter = false;
  • filterSupportRetry是在重試的時候,是否支援filter;
  • enablePropertyFilter,這個就是是否支援過濾訊息的屬性;

我們把這兩個屬性在broker的配置檔案改為true吧,如下:

filterSupportRetry=true
enablePropertyFilter=true

然後,再重新部署一下我們兩主兩從的叢集環境。環境部署完以後,我們再重啟應用,沒有報錯。在生產端傳送一下訊息看看吧,

sendResult:SEND_OK

生產端傳送訊息沒有問題,說明3個訊息都傳送成功了。再看看消費端的日誌,

msgs.size():1
this is batchMQ,my NO is 2---Mon Jun 15 10:59:37 CST 2020

只消費了一個訊息,並且這個訊息中i的值是2,那麼a的值就是2+4=6,它是>5的,滿足SQL的條件,所以被消費掉了。這完全符合我們的預期。

總結

今天的兩個小功能還是比較有意思的,但裡邊也有需要注意的地方,

  • 訊息的批量傳送,只要我們滿足它的條件,然後使用List傳送就可以了;批量消費,預設的消費個數是1,我們可以調整它的值,這樣就可以一次消費多個訊息了;
  • 過濾訊息中,最大的坑就是佇列的配置裡,需要設定enablePropertyFilter=true,否則消費端在啟動的時候報不支援SQL的錯誤;

我們在使用的時候,多加留意就可以了,有問題,評論區留言吧~

相關文章