MQ系列1:消息中间件执行原理
MQ系列2:消息中间件的技术选型
MQ系列3:RocketMQ 架构分析
MQ系列4:NameServer 原理解析
MQ系列5:RocketMQ消息的发送模式
MQ系列6:消息的消费
MQ系列7:消息通信,追求极致性能
MQ系列8:数据存储,消息队列的高可用保障
MQ系列9:高可用架构分析
MQ系列10:如何保证消息幂等性消费
MQ系列11:如何保证消息可靠性传输
MQ系列12:如何保证消息顺序性
MQ系列13:消息大量堆积如何为解决
MQ系列14:MQ如何做到消息延时处理
MQ系列15:MQ实现批量消息处理
1 背景
消息队列作为发布订阅模型的消息中间件广泛应用于上下游业务集成场景。在实际业务场景中,同一个主题下的消息往往会被多个不同的下游业务方处理,各下游的处理逻辑不同,只需要关注自身逻辑需要的消息子集。所以在 消息中心和消费者之间,需要有一种消息过滤功能,可以帮助消费者更高效地过滤自己需要的消息集合,避免大量无效消息投递给消费者,降低下游系统处理压力。
Apache RocketMQ 很好的支持了这一能力,它解决了单个业务域即同一个主题内不同消息子集的过滤问题。
2 关于消息过滤
2.1 概念
在消费者订阅了某个主题后,Apache RocketMQ 会将该主题中的所有消息投递给消费者。若消费者只需要关注部分消息,可通过设置过滤条件在 Apache RocketMQ 服务端进行过滤,只获取到需要关注的消息子集,避免接收到大量无效的消息。所以,过滤的本质就是将符合条件的消息投递给消费者,而不是将匹配到的消息过滤掉。
Apache RocketMQ 的消息过滤功能通过生产者和消费者对消息的属性、标签进行定义,并在 Apache RocketMQ 服务端根据过滤条件进行筛选匹配,将符合条件的消息投递给消费者进行消费。
2.2 消息过滤说明
2.2.1 原理介绍
备注:图片直接使用官网提供的
消息过滤主要通过以下几个关键流程实现:
- 生产者:生产者在初始化消息时预先为消息设置一些属性和标签,用于后续消费时指定过滤目标。
- 消费者:消费者在初始化及后续消费流程中通过调用订阅关系注册接口,向服务端上报需要订阅指定主题的哪些消息,即过滤条件。
- 服务端:消费者获取消息时会触发服务端的动态过滤计算,Apache RocketMQ 服务端根据消费者上报的过滤条件的表达式进行匹配,并将符合条件的消息投递给消费者。
2.2.2 消息过滤分类
Apache RocketMQ 支持Tag标签过滤和SQL属性过滤,这两种过滤方式对比如下:
对比项 | Tag标签过滤 | SQL属性过滤 |
---|---|---|
过滤目标 | 消息的Tag标签。 | 消息的属性,包括用户自定义属性以及系统属性(Tag是一种系统属性)。 |
过滤能力 | 精准匹配。 | SQL语法匹配。 |
适用场景 | 简单过滤场景、计算逻辑简单轻量。 | 复杂过滤场景、计算逻辑较复杂。 |
2.3 Tag标签过滤
Tag标签过滤方式是 Apache RocketMQ 提供的基础消息过滤能力,基于生产者为消息设置的Tag标签进行匹配。生产者在发送消息时,设置消息的Tag标签,消费者需指定已有的Tag标签来进行匹配订阅。
Tag标签设置规则:
- Tag由生产者发送消息时设置,每条消息允许设置一个Tag标签。
- Tag使用可见字符,建议长度不超过128字符。
生产消息:发送的时候,需要设置Tag标签
Message message = messageBuilder.setTopic("topicTest")
//设置消息索引键,可根据关键字精确查找某条消息
.setKeys("msgKey")
//设置消息Tag,这样消费端可以根据Tag过滤消息
//该语句表示消息的Tag设置为"TagTest1"
.setTag("TagTest1")
// 设置消息体
.setBody("hello world!".getBytes())
.build();
订阅消息:匹配单个或者多个Tag标签。
String topic = "topicTest";
//1、第一种情况,只订阅消息标签为"TagTest1"的消息。
FilterExpression filterExpression = new FilterExpression("TagTest1", FilterExpressionType.TAG);
//2、第二种情况,订阅消息标签为"TagTest1"、"TagTest2"或"TagTest3"的消息。
FilterExpression filterExpression = new FilterExpression("TagTest1||TagTest2||TagTest3", FilterExpressionType.TAG);
pushConsumer.subscribe(topic, filterExpression);
如上,消费者可以限制接收包含 TagTest1 或 TagTest2 或 TagTest3 的消息,但是限制是一个消息只能有一个标签,这无法应对互联网复杂的应用场景。在这种情况下,可以在消息中设置一些属性,再使用SQL表达式通过筛选属性来筛选消息。下面我们看看怎么实现。
2.4 SQL属性过滤
SQL属性过滤是 RocketMQ 提供的高级消息过滤方式,通过生产者为消息设置的属性(Key)及属性值(Value)进行匹配。生产者在发送消息时可设置多个属性,消费者订阅时可设置SQL语法的过滤表达式过滤多个属性。
2.4.1 基本语法
RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。
- 数值比较,比如:>,>=,<,<=,BETWEEN,=;
- 字符比较,比如:=,<>,IN;
- IS NULL 或者 IS NOT NULL;
- 逻辑符号 AND,OR,NOT;
常量支持类型为:
- 数值,比如:123,3.1415;
- 字符,比如:'abc',必须用单引号包裹起来;
- NULL,特殊的常量
- 布尔值,TRUE 或 FALSE
只有使用push模式的消费者才能用使用SQL92标准的sql语句,接口如下:
public void subscribe(finalString topic, final MessageSelector messageSelector)
2.4.2 消息生产者
发送消息时,设置了消息Tag标签并定义了属性,用于做消息过滤
Message message = messageBuilder.setTopic("topicTest")
//设置消息索引键,可根据关键字精确查找某条消息
.setKeys("msgKey")
//设置消息Tag,这样消费端可以根据Tag过滤消息
//该语句表示消息的Tag设置为"TagTest1"
.setTag("TagTest1")
//消息也可以设置自定义的分类属性,比如下面这句话表示为消息自定义一个属性,该属性为性别,属性值为1(男)或者0(女)。
.addProperty("Sex", 1)
// 设置消息体
.setBody("hello world!".getBytes())
.build();
2.4.3 消息消费者
使用过滤表达式进行消息筛选,如下:
String topic = "topic";
//只订阅性别为1(男)的消息。
FilterExpression filterExpression = new FilterExpression("Sex IS NOT NULL AND Sex=1", FilterExpressionType.SQL92);
simpleConsumer.subscribe(topic, filterExpression);
//只订阅性别为1(男)且年龄大于18的消息。
FilterExpression filterExpression = new FilterExpression("Sex IS NOT NULL AND Age IS NOT NULL AND Sex = 1 AND Age > 18", FilterExpressionType.SQL92);
simpleConsumer.subscribe(topic, filterExpression);
//订阅所有消息
FilterExpression filterExpression = new FilterExpression("True", FilterExpressionType.SQL92);
simpleConsumer.subscribe(topic, filterExpression);
3 总结
Rocket MQ的消息过滤功能是在服务端进行的,可以根据消息的标签、属性等进行过滤。具体来说,Rocket MQ主要支持以下两种过滤方式:
TAG过滤:TAG是消息的业务标识,可以通过设置Tag表达式,判断消息是否包含指定的Tag,从而进行过滤。这种过滤方式简单直观,适用于基于Tag进行消息分类的场景。
SQL92过滤:可以使用SQL92表达式来灵活地过滤消息的Tag和属性。这种方式提供了更强大的过滤能力,可以根据复杂的条件进行消息筛选。
需要注意的是,Rocket MQ的消息过滤功能虽然强大,但是也会增加服务端的处理负担。因此,在使用时需要根据实际情况进行权衡,避免过度依赖消息过滤功能导致系统性能下降。