Rocketmq 消息过滤

 

RocketMQ分布式消息队列的消息过滤方式有别于其它MQ中间件,是在Consumer端订阅消息时再做消息过滤的。
RocketMQ这么做是在于其Producer端写入消息和Consumer端订阅消息采用分离存储的机制来实现的,Consumer端订阅消息是需要通过ConsumeQueue这个消息消费的逻辑队列拿到一个索引,然后再从CommitLog里面读取真正的消息实体内容。

流程:生产者向broker写入消息,先将消息写入到commitlog文件中,然后异步的创建每一个MessageQueue对应的ConsumerQueue,消费者在消费消息时,先找ConsumerQueue,拿到消息的元数据,元数据里有tag的哈希值,判断哈希值是否一样,如果一样,就根据偏移量去commit log中获取消息的内容,不一样丢弃。

consumeQueue位于/root/store,其ConsumeQueue的存储结构如下,存储有消息的偏移量、消息的大小和tag的哈希值,可以看到其中有8个字节存储的Message Tag的哈希值,基于Tag的消息过滤正式基于这个字段值的。

,目录如下:

2种过滤方式

(1) Tag过滤方式:

Consumer端在订阅消息时除了指定Topic还可以指定TAG,如果一个消息有多个TAG,可以用||分隔。
1. Consumer端会将这个订阅请求构建成一个 SubscriptionData,发送一个Pull消息的请求给Broker端。
2. Broker端从RocketMQ的文件存储层—Store读取数据之前,会用这些数据先构建一个MessageFilter,然后传给Store。
3. Store从 ConsumeQueue读取到一条记录后,会用它记录的消息tag hash值去做过滤。
4. 在服务端只是根据hashcode进行判断,无法精确对tag原始字符串进行过滤,在消息消费端拉取到消息后,还需要对消息的原始tag字符串进行比对,如果不同,则丢弃该消息,不进行消息消费。

public class MyProducer1 {

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

        DefaultMQProducer producer = new DefaultMQProducer("producer_grp_06");
        producer.setNamesrvAddr("node1:9876");

        producer.start();

        Message message = null;

        for (int i = 0; i < 100; i++) {
            message = new Message(
                    "tp_demo_06",
                    "tag-" + (i % 3),
                    ("hello lagou - " + i).getBytes()
            );

            producer.send(message, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println(sendResult.getSendStatus());
                }

                @Override
                public void onException(Throwable e) {
                    System.out.println(e.getMessage());
                }
            });

        }

        Thread.sleep(3_000);

        producer.shutdown();
    }

}

 

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

        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_grp_06_03");

        consumer.setNamesrvAddr("node1:9876");

//        consumer.subscribe("tp_demo_06", "*");
//        consumer.subscribe("tp_demo_06", "tag-1");
        consumer.subscribe("tp_demo_06", "tag-1||tag-0");

        consumer.setMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {

                final MessageQueue messageQueue = context.getMessageQueue();
                final String brokerName = messageQueue.getBrokerName();
                final String topic = messageQueue.getTopic();
                final int queueId = messageQueue.getQueueId();

                System.out.println(brokerName + "\t" + topic + "\t" + queueId);

                for (MessageExt msg : msgs) {
                    System.out.println(msg);
                }

                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        // 初始化并启动消费者
        consumer.start();

    }
}

(2) SQL92的过滤方式:

仅对push的消费者起作用。
Tag方式虽然效率高,但是支持的过滤逻辑比较简单。
SQL表达式可以更加灵活的支持复杂过滤逻辑,这种方式的大致做法和上面的Tag过滤方式一样,只是在Store层的具体过滤过程不太一样,真正的 SQL expression 的构建和执行由rocketmq-filter模块负责的。
每次过滤都去执行SQL表达式会影响效率,所以RocketMQ使用了BloomFilter避免了每次都去执行。
SQL92的表达式上下文为消息的属性。

开启支持SQL92的特性,修改配置文件conf/broker.conf

RocketMQ仅定义了几种基本的语法,用户可以扩展:

1. 数字比较: >, >=, <, <=, BETWEEN, =
2. 字符串比较: =, <>, IN; IS NULL或者IS NOT NULL;
3. 逻辑比较: AND, OR, NOT;
4. Constant types are: 数字如:123, 3.1415; 字符串如:'abc',必须是单引号引起来 NULL,特殊常量 布尔型如:TRUE or FALSE;

消费者:

for (int i = 0; i < 100; i++) {
            message = new Message(  "tp_demo_06_02", ("hello lagou - " + i).getBytes() );

            String value = null;

            switch (i % 3) {
                case 0:
                    value = "v0";
                    break;
                case 1:
                    value = "v1";
                    break;
                default:
                    value = "v2";
                    break;
            }
            // 给消息添加用户属性
            message.putUserProperty("mykey", value);

消费者:

consumer.setNamesrvAddr("node1:9876");

//        consumer.subscribe("tp_demo_06_02", MessageSelector.bySql("mykey in ('v0', 'v1')"));
//        consumer.subscribe("tp_demo_06_02", MessageSelector.bySql("mykey = 'v0'"));
        consumer.subscribe("tp_demo_06_02", MessageSelector.bySql("mykey IS NOT NULL"));

 

 

(3) Filter Server方式。

这是一种比SQL表达式更灵活的过滤方式,允许用户自定义Java函数,根据Java函数的逻辑对消息进行过滤。要使用Filter Server,首先要在启动Broker前在配置文件里加上filterServer-Nums=3 这样的配置,Broker在启动的时候,就会在本机启动3个Filter Server进程。Filter Server类似一个RocketMQ的Consumer进程,它从本机Broker获取消息,然后根据用户上传过来的Java函数进行过滤,过滤后的消息再传给远端的Consumer。这种方式会占用很多Broker机器的CPU资源,要根据实际情况谨慎使用。上传的java代码也要经过检查,不能有申请大内存、创建线程等这样的操作,否则容易造成Broker服务器宕机。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值