Three main ways of implementing message filtering in Spring Cloud Stream

Keywords: Java Lombok SQL Spring Attribute

Message filtering

Message filtering means that consumers do not want to consume all the messages in the topic, but only some specific messages. Selecting these specific messages from topic is called message filtering. Message shunting can be achieved through message filtering, such as producer-produced messages. Headers may be different, so we can write two or more consumers to process different header s'messages pertinently.

Spring Cloud Stream implements message filtering in three ways. One is to specify conditional expressions by using the condition attribute of @StreamListener annotation, the other is to set TAGS in message header, and the third is to use SQL92 grammar to filter messages.

1,condition

This approach is applicable to all MQ and is the simplest and easiest to use. First, we set a header for the producer's message. The specific header name and value can be set according to the actual business needs. As an example, the code is as follows:

package com.zj.node.contentcenter.controller.content;

import lombok.RequiredArgsConstructor;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Producer
 *
 * @author 01
 * @date 2019-08-03
 **/
@RestController
@RequiredArgsConstructor
public class TestProducerController {

    private final Source source;

    @GetMapping("/stream-send-msg")
    public String streamSendMsg(String flagHeader) {
        source.output().send(
                MessageBuilder.withPayload("Message Body")
                        // Setting the header for filtering messages
                        .setHeader("flag-header", flagHeader)
                        .build()
        );

        return "send message success!";
    }
}

Then the condition attribute is specified in the consumer's @StreamListener annotation, and the conditional expression is written according to the specific header. The following example:

package com.zj.node.usercenter.rocketmq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.stereotype.Service;

/**
 * Consumer
 *
 * @author 01
 * @date 2019-08-10
 **/
@Slf4j
@Service
public class TestStreamConsumer {

    @StreamListener(
            value = Sink.INPUT,
            // The conditional expression of message filtering, which only meets the expression, will execute the method
            condition = "headers['flag-header']=='UP'"
    )
    public void receive(String messageBody) {
        log.info("adopt stream Received the message, messageBody = {}", messageBody);
    }
}

Now let's do a simple test where the producer delivers two messages, one flagHeader=UP and the other flagHeader=DOWN. The log information output by consumers is as follows:

2,TAGS

This approach is supported only by RocketMQ, but not by Kafka and RabbitMQ. This approach also requires headers for messages produced by producers, but this header is a little special. We modify the code based on the previous section. The modified code is as follows:

package com.zj.node.contentcenter.controller.content;

import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Producer
 *
 * @author 01
 * @date 2019-08-03
 **/
@RestController
@RequiredArgsConstructor
public class TestProducerController {

    private final Source source;

    @GetMapping("/stream-send-msg")
    public String streamSendMsg() {
        source.output().send(
                MessageBuilder.withPayload("Message Body")
                        // tag can only be set one, not multiple
                        .setHeader(RocketMQHeaders.TAGS, "tag-1")
                        .build()
        );

        return "send message success!";
    }
}

Rebuilding Consumers

Since stream only provides an input channel in the Ink interface by default, in order to more intuitively see messages from different tag s entering different channels, we need to use a custom interface to define multiple "input". The code is as follows:

public interface MySink {

    String T1_INPUT = "t1-input";
    String T2_INPUT = "t2-input";

    @Input(T1_INPUT)
    SubscribableChannel input1();

    @Input(T2_INPUT)
    SubscribableChannel input2();
}

Modify the configuration file as follows:

spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: 192.168.190.129:9876
        bindings:
          t1-input:
            consumer:
              # Represents that t1-input consumes only messages with tag-1
              tags: tag-1
          t2-input:
            consumer:
              # Indicates that t2-input can consume messages with tag-2 or tag-3 (| | for separating different tags)
              tags: tag-2||tag-3
      bindings:
        t1-input:
          destination: stream-test-topic
          group: binder-group1
        t2-input:
          destination: stream-test-topic
          group: binder-group2

Modify the annotations for the startup class as follows:

import com.zj.node.usercenter.rocketmq.MySink;
import org.springframework.cloud.stream.annotation.EnableBinding;

@EnableBinding({MySink.class})
...

Modify the consumer code as follows:

package com.zj.node.usercenter.rocketmq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Service;

/**
 * Consumer
 *
 * @author 01
 * @date 2019-08-10
 **/
@Slf4j
@Service
public class TestStreamConsumer {

    @StreamListener(MySink.T1_INPUT)
    public void receive1(String messageBody) {
        log.info("Consumption tag-1 News, messageBody = {}", messageBody);
    }

    @StreamListener(MySink.T2_INPUT)
    public void receive2(String messageBody) {
        log.info("Consumption tag-2 or tag-3 News, messageBody = {}", messageBody);
    }
}

After completing the above modifications, the producer is used to deliver the message, and the console output is as follows:

2019-08-11 18:06:28.491 INFO 16172 - - [MessageThread_1] c.z.n.u.rocketmq.TestStream Consumer: Consumption of tag-1 messages, message body = message body

3,SQL92

This method is only supported by RocketMQ, but not by Kafka and RabbitMQ. If you use this method, do not use TAGS. This method uses SQL grammar to filter messages, referring specifically to official documents:

http://rocketmq.apache.org/rocketmq/filter-messages-by-sql92-in-rocketmq/

By default, RocketMQ's SQL filtering support is turned off, so we need to turn it on by adding some configuration items. First enter the installation directory of RocketMQ, and then edit the conf/broker.conf file:

[root@study-01 ~]# cd /usr/local/rocketmq-4.5.1/
[root@study-01 /usr/local/rocketmq-4.5.1]# vim conf/broker.conf

Add the following configuration items at the end of the file:

enablePropertyFilter = true

Note: After adding the configuration, the Broker of RocketMQ will need to be restarted to take effect.

nohup sh $ROCKETMQ_HOME/bin/mqbroker -n localhost:9876 -c $ROCKETMQ_HOME/conf/broker.conf &

Rebuilding Producers

Also based on the code in the previous section, the producer's code is as follows:

package com.zj.node.contentcenter.controller.content;

import lombok.RequiredArgsConstructor;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Producer
 *
 * @author 01
 * @date 2019-08-03
 **/
@RestController
@RequiredArgsConstructor
public class TestProducerController {

    private final Source source;

    @GetMapping("/stream-send-msg")
    public String streamSendMsg() {
        source.output().send(
                MessageBuilder.withPayload("Message Body")
                        .setHeader("amount", "100")
                        .build()
        );

        return "send message success!";
    }
}

Rebuilding Consumers

Because of the foreshadowing of the previous section, there is not much need to be changed here. First, modify the consumer profile, as follows:

spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: 192.168.190.129:9876
        bindings:
          t1-input:
            consumer:
              # Messages indicating that t1-input consumes only amount less than or equal to 100
              sql: 'amount <= 100'
          t2-input:
            consumer:
              # Messages indicating that t2-input consumes only amount greater than 100
              sql: 'amount > 100'
      bindings:
        t1-input:
          destination: stream-test-topic
          group: binder-group1
        t2-input:
          destination: stream-test-topic
          group: binder-group2

Finally, the consumer code is modified as follows:

package com.zj.node.usercenter.rocketmq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Service;

/**
 * Consumer
 *
 * @author 01
 * @date 2019-08-10
 **/
@Slf4j
@Service
public class TestStreamConsumer {

    @StreamListener(MySink.T1_INPUT)
    public void receive1(String messageBody) {
        log.info("Consumption amount <= 100 News, messageBody = {}", messageBody);
    }

    @StreamListener(MySink.T2_INPUT)
    public void receive2(String messageBody) {
        log.info("Consumption amount > 100 News, messageBody = {}", messageBody);
    }
}

After completing the above modifications, the producer is used to deliver the message, and the console output is as follows:

2019-08-11 18:40:30.019 INFO 9928 - - [MessageThread_1] c.z.n.u. rocketmq.TestStream Consumer: consumed amount <= 100 messages, message body = message body

Source code for tag and sql configuration items:

  • com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties

Posted by abhishekphp6 on Sun, 11 Aug 2019 04:16:22 -0700