RocketMQ 消息过滤、存储和ACL

消息过滤

我们前面提到,一个主题只能有一种消息类型,那么只要是同一个消息类型,我们就可以发送到同一个主题。但这些消息中可能涉及到多个业务,他们对应的消费者的消费逻辑可能不一样。如果我们单个消费者接收该主题的所有消息进行消费,那么在我们的消费逻辑中需要很多的判断来判断其走哪个业务逻辑,无疑增加了消费逻辑的复杂度。使用消息过滤的方式,即可在 MQ 服务端进行消息的过滤,将消息投递给特定的消费者,使每个消费者只需关注与自身相关的消息进行消费。

消息过滤的方式

  • tag 标签过滤:我们之前的所有示例都是通过 tag 进行过滤的。其实现简单,方便。
  • SQL 属性过滤:通过生产者为消息设置的属性(Key)及属性值(Value)进行匹配。适用于复杂逻辑。

tag 标签过滤

tag 标签过滤方式为,生产者发送消息时为消息设置一个 Tag 标签,消费者在消费时匹配该 Tag ,如果匹配则消费发送给该消费者,如果不匹配则不发送。

  1. 消费者,设置 tag
Message message = messageBuilder.setTopic("topic")
//设置消息索引键,可根据关键字精确查找某条消息。
.setKeys("messageKey")
//设置消息Tag,用于消费端根据指定Tag过滤消息。
//该示例表示消息的Tag设置为"TagA"。
// Tag使用可见字符,建议长度不超过128字符
.setTag("TagA")
//消息体。
.setBody("messageBody".getBytes())
.build();
  1. 消费者,订阅消息
    2.1 匹配单个标签
String topic = "Your Topic";
//只订阅消息标签为"TagA"的消息。
FilterExpression filterExpression = new FilterExpression("TagA", FilterExpressionType.TAG);
pushConsumer.subscribe(topic, filterExpression);

2.2 匹配多个Tag标签

String topic = "Your Topic";
//只订阅消息标签为"TagA"、"TagB"或"TagC"的消息。
FilterExpression filterExpression = new FilterExpression("TagA||TagB||TagC", FilterExpressionType.TAG);
pushConsumer.subscribe(topic, filterExpression);

2.3 匹配所有消息

String topic = "Your Topic";
//使用Tag标签过滤消息,订阅所有消息。
FilterExpression filterExpression = new FilterExpression("*", FilterExpressionType.TAG);
pushConsumer.subscribe(topic, filterExpression);

SQL属性过滤

SQL属性过滤是 RocketMQ 提供的高级消息过滤方式,通过生产者为消息设置的属性(Key)及属性值(Value)进行匹配。生产者在发送消息时可设置多个属性,消费者订阅时可设置SQL语法的过滤表达式过滤多个属性。

注:Tag是一种系统属性,所以SQL过滤方式也兼容Tag标签过滤。在SQL语法中,Tag的属性名称为TAGS。

  1. 生产者设置消息tag标签和自定义属性
Message message = messageBuilder.setTopic("topic")
//设置消息索引键,可根据关键字精确查找某条消息。
.setKeys("messageKey")
//设置消息Tag,用于消费端根据指定Tag过滤消息。
//该示例表示消息的Tag设置为"messageTag"。
.setTag("messageTag")
//消息也可以设置自定义的分类属性,例如环境标签、地域、逻辑分支。
//该示例表示为消息自定义一个属性,该属性为地域,属性值为杭州。
.addProperty("Region", "Hangzhou")
//消息体。
.setBody("messageBody".getBytes())
.build();
  1. 消费者订阅消息
    2.1 根据单个自定义属性匹配消息
String topic = "topic";
//只订阅地域属性为杭州的消息。
FilterExpression filterExpression = new FilterExpression("Region IS NOT NULL AND Region='Hangzhou'", FilterExpressionType.SQL92);
simpleConsumer.subscribe(topic, filterExpression);

2.2 同时根据多个自定义属性匹配消息

String topic = "topic";
//只订阅地域属性为杭州且价格属性大于30的消息。
FilterExpression filterExpression = new FilterExpression("Region IS NOT NULL AND price IS NOT NULL AND Region = 'Hangzhou' AND price > 30", FilterExpressionType.SQL92);
simpleConsumer.subscribe(topic, filterExpression);

2.3 匹配Topic中的所有消息,不进行过滤

String topic = "topic";
//订阅所有消息。
FilterExpression filterExpression = new FilterExpression("True", FilterExpressionType.SQL92);
simpleConsumer.subscribe(topic, filterExpression);

SQL 属性过滤规则

语法说明示例
IS NULL判断属性不存在。a IS NULL :属性a不存在。
IS NOT NULL判断属性存在。a IS NOT NULL:属性a存在。
> >= < <=用于比较数字,不能用于比较字符串,否则消费者客户端启动时会报错。 说明 可转化为数字的字符串也被认为是数字。a IS NOT NULL AND a > 100:属性a存在且属性a的值大于100。 a IS NOT NULL AND a > ‘abc’:错误示例,abc为字符串,不能用于比较大小。
BETWEEN xxx AND xxx用于比较数字,不能用于比较字符串,否则消费者客户端启动时会报错。等价于>= xxx AND <= xxx。表示属性值在两个数字之间。a IS NOT NULL AND (a BETWEEN 10 AND 100):属性a存在且属性a的值大于等于10且小于等于100。
NOT BETWEEN xxx AND xxx用于比较数字,不能用于比较字符串,否则消费者客户端启动会报错。等价于< xxx OR > xxx,表示属性值在两个值的区间之外。a IS NOT NULL AND (a NOT BETWEEN 10 AND 100):属性a存在且属性a的值小于10或大于100。
IN (xxx, xxx)表示属性的值在某个集合内。集合的元素只能是字符串。a IS NOT NULL AND (a IN (‘abc’, ‘def’)):属性a存在且属性a的值为abc或def。
= <>等于和不等于。可用于比较数字和字符串。a IS NOT NULL AND (a = ‘abc’ OR a<>‘def’):属性a存在且属性a的值为abc或a的值不为def。
AND OR逻辑与、逻辑或。可用于组合任意简单的逻辑判断,需要将每个逻辑判断内容放入括号内。a IS NOT NULL AND (a > 100) OR (b IS NULL):属性a存在且属性a的值大于100或属性b不存在。

由于SQL属性过滤是生产者定义消息属性,消费者设置SQL过滤条件,因此过滤条件的计算结果具有不确定性,服务端的处理方式如下:

  • 异常情况处理:如果过滤条件的表达式计算抛异常,消息默认被过滤,不会被投递给消费者。例如比较数字和非数字类型的值。
  • 空值情况处理:如果过滤条件的表达式计算值为null或不是布尔类型(true和false),则消息默认被过滤,不会被投递给消费者。例如发送消息时未定义某个属性,在订阅时过滤条件中直接使用该属性,则过滤条件的表达式计算结果为null。
  • 数值类型不符处理:如果消息自定义属性为浮点型,但过滤条件中使用整数进行判断,则消息默认被过滤,不会被投递给消费者。

消息存储和清理机制

消息按照达到服务器的先后顺序被存储到队列中,理论上每个队列都支持无限存储。但实际情况下,服务端节点物理存储空间有限,消息是无法永久存储的。

RocketMQ 使用存储时长作为消息存储的依据,即每个节点对外承诺消息的存储时长。在存储时长范围内的消息都会被保留,无论消息是否被消费;超过时长限制的消息则会被清理掉。

消息保存时长并不能完整控制消息的实际保存时间,因为消息存储仍然使用本地磁盘,本地磁盘空间不足时,为保证服务稳定性消息仍然会被强制清理,导致消息的实际保存时长小于设置的保存时长。 意味着当消费能力严重不足时,消息会产生大量堆积,导致没有可以磁盘空间,进一步导致未消费消息被清理机制清理掉。

消息持久化机制

RocketMQ 消息默认存储在本地磁盘文件中,存储文件的根目录由 Broker 配置参数 storePathRootDir 决定,其默认路径为:~/store。用户路径下的 store 目录。该目录的目录结构如下:

├── timerwheel
├── timerlog
│   └── 00000000000000000000
├── lock
├── index
│   └── 20230908092441339
├── consumequeue
│   ├── TestTopic
│   │   ├── 7
│   │   │   └── 00000000000000000000
│   │   └── 4
│   │       └── 00000000000000000000
│   ├── SCHEDULE_TOPIC_XXXX
│   │   ├── 2
│   │   │   └── 00000000000000000000
│   │   └── 17
│   │       └── 00000000000000000000
│   ├── rmq_sys_wheel_timer
│   │   └── 0
│   │       └── 00000000000000000000
│   ├── RMQ_SYS_TRANS_OP_HALF_TOPIC
│   │   └── 0
│   │       └── 00000000000000000000
│   ├── RMQ_SYS_TRANS_HALF_TOPIC
│   │   └── 0
│   │       └── 00000000000000000000
│   ├── rmq_sys_REVIVE_LOG_DefaultCluster
│   │   ├── 7
│   │   │   └── 00000000000000000000
│   │   ├── 6
│   │   │   └── 00000000000000000000
│   │   ├── 5
│   │   │   └── 00000000000000000000
│   │   ├── 4
│   │   │   └── 00000000000000000000
│   │   ├── 3
│   │   │   └── 00000000000000000000
│   │   ├── 2
│   │   │   └── 00000000000000000000
│   │   ├── 1
│   │   │   └── 00000000000000000000
│   │   └── 0
│   │       └── 00000000000000000000
│   ├── %RETRY%MY_RETRY_GROUP_MY_NORMAL_TOPIC
│   │   └── 0
│   │       └── 00000000000000000000
│   ├── %RETRY%DLQ_CONSUMER_GROUP_%DLQ%MY_RETRY_GROUP
│   │   └── 0
│   │       └── 00000000000000000000
│   ├── MY_TRANSACTION_TOPIC
│   │   ├── 4
│   │   │   └── 00000000000000000000
│   │   └── 1
│   │       └── 00000000000000000000
│   ├── MY_NORMAL_TOPIC
│   │   ├── 7
│   │   │   └── 00000000000000000000
│   │   ├── 5
│   │   │   └── 00000000000000000000
│   │   ├── 4
│   │   │   └── 00000000000000000000
│   │   ├── 2
│   │   │   └── 00000000000000000000
│   │   └── 0
│   │       └── 00000000000000000000
│   ├── MY_FIFO_TOPIC
│   │   ├── 6
│   │   │   └── 00000000000000000000
│   │   ├── 5
│   │   │   └── 00000000000000000000
│   │   ├── 3
│   │   │   └── 00000000000000000000
│   │   ├── 2
│   │   │   └── 00000000000000000000
│   │   └── 1
│   │       └── 00000000000000000000
│   ├── MY_DELAY_TOPIC
│   │   ├── 6
│   │   │   └── 00000000000000000000
│   │   ├── 5
│   │   │   └── 00000000000000000000
│   │   ├── 3
│   │   │   └── 00000000000000000000
│   │   └── 1
│   │       └── 00000000000000000000
│   ├── %DLQ%MY_RETRY_GROUP
│   │   └── 0
│   │       └── 00000000000000000000
│   └── %DLQ%DLQ_CONSUMER_GROUP
│       └── 0
│           └── 00000000000000000000
├── config
│   ├── topics.json.bak
│   ├── topics.json
│   ├── timermetrics.bak
│   ├── timermetrics
│   ├── timercheck
│   ├── subscriptionGroup.json.bak
│   ├── subscriptionGroup.json
│   ├── delayOffset.json.bak
│   ├── delayOffset.json
│   ├── consumerOrderInfo.json.bak
│   ├── consumerOrderInfo.json
│   ├── consumerOffset.json.bak
│   ├── consumerOffset.json
│   ├── consumerFilter.json.bak
│   └── consumerFilter.json
├── compaction
│   ├── position-checkpoint.bak
│   └── position-checkpoint
├── commitlog
│   ├── 00000000001073741824
│   └── 00000000000000000000
├── checkpoint
├── abort.bak
└── abort
  • index: 该文件夹下的文件是以创建时的时间戳为文件名,其作为消息的索引来使用。
    一个 index 存储单元包含如:key 的hashCode、物理偏移位置、时间差异、下一个 index 的位置,这些内容的写入也是按顺序的
┌───────────────┬───────────────────────────────┬───────────────┬───────────────┐
│ Key HashCode  │        Physical Offset        │   Time Diff   │ Next Index Pos│
│   (4 Bytes)(8 Bytes)(4 Bytes)(4 Bytes)   │
├───────────────┴───────────────────────────────┴───────────────┴───────────────┤
│                                 Index Store Unit                              │
│                                                                               │
  • consumequeue:该文件夹下以各个主题命名的文件夹中(如:TestTopic),有按队列序号的文件夹(如:0~7,默认8个队列)。这里存储的是逻辑队列,其包含队列在 commitlog 文件中的位置 offset,消息内容的大小和消息 tag 的 hash 值。这些信息在文件中是顺序写入的。
  • config:该文件夹下存储了主题、消费者分组等重要的配置信息。比如:我们的消费者分组信息就存储在 subscriptionGroup.json。
  • commitlog:日志数据文件。消息实际被持久化存储的文件,其按照收到消息的顺序写入文件(写入过程是加锁的),之后将消息在 commitlog 存储的位置 offset、消息大小、tag 等信息发送给 consumequeue。

commitlog 文件夹下,00000000000000000000 代表了第一个日志数据文件,其最大大小为 1G = 1073741824;其起始偏移量为 0,当第一个文件写满,则生成第二个文件,其文件名为 00000000001073741824,其起始偏移量为 1073741824,依此类推。

RocketMQ 开启 ACL 权限控制

在安全要求较高的服务器环境下,或者多项目组共享一个 RocketMQ 集群时,建议大家开启权限控制,这样可以防止多项目混用 RocketMQ 资源,也可以控制非法的访问请求。

plain_acl.yml 配置文件

plain_acl.yml 配置文件,在 ${RocketMQ_HOME}/conf/plain_acl.yml,其默认配置如下:

# 全局IP白名单
globalWhiteRemoteAddresses:
  - 10.10.103.*
  - 192.168.0.*
# 账号配置列表
accounts:
	# Access Key 类似账号名
  - accessKey: RocketMQ
  	# Secret Key
    secretKey: 12345678
    # 用户IP白名单列表
    whiteRemoteAddress:
    # 当前用户是否为管理员用户
    admin: false
    # 默认的Topic权限
    defaultTopicPerm: DENY
    # 默认的ConsumerGroup权限
    defaultGroupPerm: SUB
    # 各个Topic的权限
    topicPerms:
      - topicA=DENY
      - topicB=PUB|SUB
      - topicC=SUB
	# 各个ConsumerGroup的权限
    groupPerms:
      # the group should convert to retry topic
      - groupA=DENY
      - groupB=PUB|SUB
      - groupC=SUB

  - accessKey: rocketmq2
    secretKey: 12345678
    whiteRemoteAddress: 192.168.1.*
    # 如果是管理员用户,则拥有所有权限
    admin: true

权限说明

  • DENY 拒绝
  • ANY PUB 或者 SUB 权限
  • PUB 发送权限
  • SUB 订阅权限

注1:特殊的权限,比如 UPDATE_AND_CREATE_TOPIC 等,只能由 admin 账号操作。
注2:对于某个资源(比如:topic),如果有其对应的权限配置,则使用配置的权限,如果没有配置则使用默认的权限。

权限校验的步骤

  1. 检查是否命中全局 IP 白名单,如果是,则校验通过。否则验证第二步
  2. 检查是否命中用户 IP 白名单;如果是,则认为校验通过;否则验证第三步
  3. 校验签名,校验不通过,抛出异常;校验通过,则验证第四步
  4. 对用户请求所需的权限 和 用户所拥有的权限进行校验;不通过,抛出异常

注意:IP 白名单如果通过,则不会验证签名,如果你要使用签名方式认证,最好就不要设置对应的 IP 白名单。

启动配置

通过 broker 的 aclEnable 配置来开启权限校验。

我们前面还提到建议关闭 autoCreateSubscriptionGroup (是否自动创建消费者分组)、autoCreateTopicEnable (是否自动创建Topic),这两个参数也都是 broker 的配置参数,我们在此一并配置了。

新建或修改 broker.conf


brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH

# 以上为 broker.conf 的默认配置
# 开启权限验证
aclEnable = true
# 关闭自动创建Topic
autoCreateTopicEnable = false
# 关闭自动创建消费者分组
autoCreateSubscriptionGroup = false

关闭自动创建 Topic 后,生产者执行时将报没有对应主题的错误。

关闭自动创建消费者分组后,消费者示例可以成功启动,但其无法消费任何消息。

重启 broker

$> ./bin/mqshutdown proxy
$> ./bin/mqshutdown broker
$> nohup sh bin/mqbroker -n localhost:9876 -c conf/broker.conf --enable-proxy &

注:以上命令在 RocketMQ home 目录下执行的

Remoting 协议设置 ACL 信息

DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName", 
	new AclClientRPCHook(new SessionCredentials("accessKey","secretKey"));
);

gRPC 协议设置 ACL 信息

ClientConfiguration configuration = ClientConfiguration.newBuilder()
                // endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
                .setEndpoints(MyMQProperties.ENDPOINTS)
.setCredentialProvider(new StaticSessionCredentialsProvider("accessKey","secretKey"))
.build();

其他的 broker 配置

属性名默认值说明
listenPort10911接受客户端连接的监听端口
namesrvAddrnullnameServer 地址
brokerIP1网卡的 InetAddress当前 broker 监听的 IP
brokerIP2跟 brokerIP1 一样存在主从 broker 时,如果在 broker 主节点上配置了 brokerIP2 属性,broker 从节点会连接主节点配置的 brokerIP2 进行同步
brokerNamenullbroker 的名称
brokerClusterNameDefaultCluster当前 broker 所属的 Cluser 名称
brokerId0broker id, 0 表示 master, 其他的正整数表示 slave
storePathCommitLog$HOME/store/commitlog/存储 commit log 的路径
storePathConsumerQueue$HOME/store/consumequeue/存储 consume queue 的路径
mapedFileSizeCommitLog1024 * 1024 * 1024(1G)commit log 的映射文件大小
deleteWhen04在每天的什么时间删除已经超过文件保留时间的 commit log
fileReservedTime72以小时计算的文件保留时间
brokerRoleASYNC_MASTERSYNC_MASTER/ASYNC_MASTER/SLAVE
flushDiskTypeASYNC_FLUSHSYNC_FLUSH/ASYNC_FLUSH SYNC_FLUSH 模式下的 broker 保证在收到确认生产者之前将消息刷盘。ASYNC_FLUSH 模式下的 broker 则利用刷盘一组消息的模式,可以取得更好的性能。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值