Rocket消息机制和ACL权限控制

普通消息(cluster模式默认)

consumer.setMessageModel(MessageModel.CLUSTERING);

当多个消费者组订阅了同一个 Topic 时,每个组中其中一个消费者都会收到这条消息的副本。

由broker以消费组为单位维护消费进度也就是消费位点

广播消息(broadcasting模式)

consumer.setMessageModel(MessageModel.BROADCASTING);

所有订阅的消费者都会收到这条消息的副本。适合

  • 每个消费者 将自己的消费进度保存在本地(默认是磁盘)。
  • 不会提交到 Broker(Broker 不知道你消费到哪了)。
  • 每个消费者维护一个独立的 offset 文件。
  • 消费位点文件路径(默认)$HOME/.rocketmq_offsets/ip@instanceName/消费组名/offsets.json
  • 消费者用本地的消费位点去消费消息,文件不存在则创建,并且从最新的消息开始消费
  • 不会自动进行重试,即使加了RECONSUME_LATER

消息的过滤

同⼀个Topic下有多种不同的消息,消费者只希望关注某⼀类消息。
Rocket消息机制和ACL权限控制_第1张图片

Tag 标签过滤

  • 生产者发送消息时指定一个 tag
  • 消费者订阅时可以指定过滤的 tag 表达式(支持 ||)
consumer.subscribe("TopicTest", "TagA || TagB");  // 只消费 TagA 和 TagB 的消息

SQL 表达式过滤(功能强,适合复杂场景)

  • 消息中携带自定义属性
  • 消费者使用 SQL 表达式筛选消息
  • 需要 Broker 开启支持 SQL 过滤功能

enablePropertyFilter=true

msg.putUserProperty("a", "15");

//TAGS是msg的tags,msg还可以put多个属性
consumer.subscribe("TopicTest", MessageSelector.bySql("(TAGS is not null and TAGS in ('TagA', 'TagB')) and (a is not null and a between 0 and 3)"));

顺序消息机制

每⼀个订单有从下单、锁库存、⽀付、下物流等⼏个业务步骤。每个业务步骤都由⼀个消息⽣产者通知给下游服务。如何保证对每个订单的业务处理顺序不乱?
Rocket消息机制和ACL权限控制_第2张图片

生产者核心代码

用 MessageQueueSelector 选择队列,利用单个队列fifo机制来实现顺序发送。
通过MessageQueueSelector,将orderId相同的消息,都转发到同⼀个MessageQueue中。

            for (int i = 0; i < 20; i++) {
               int orderId = i;
               for(int j = 0 ; j < 5; j++){
                   Message msg =
                           new Message("OrderTopic", "order_"+orderId, "KEY" + orderId,
                                   ("order_" + orderId + " step " + j).getBytes(RemotingHelper.DEFAULT_CHARSET));
                   SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                       @Override
                       public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                           Integer id = (Integer) arg;
                           int index = id % mqs.size();
                           return mqs.get(index);
                       }
                   }, orderId);
                   System.out.printf("%s%n", sendResult);
               }
           }

消费者核心代码

注⼊⼀个MessageListenerOrderly实现。
一个消息队列在任一时刻只会被一个线程消费,消息按发送顺序依次拉取和处理。

        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(true);
                for(MessageExt msg:msgs){
                    System.out.println("收到消息内容 "+new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

注意点:

  • 理解局部有序与全局有序。⼤部分业务场景下,我们需要的其实是局部有序。如果要保持全局有序,那就只保留⼀个MessageQueue。性能显然⾮常低。
  • ⽣产者端尽可能将有序消息打散到不同的MessageQueue上,避免过于集中导致数据热点竞争。
  • 消费者端只进⾏有限次数的重试。如果⼀条消息处理失败,RocketMQ会将后续消息阻塞住,让消费者进⾏重试。但是,如果消费者⼀直处理失败,超过最⼤重试次数,那么RocketMQ就会跳过这⼀条消息,处理后⾯的消息,这会造成消息乱序。
  • 消费者端如果确实处理逻辑中出现问题,不建议抛出异常,可以返回ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT作为替代。

会阻塞整个队列过一段时间在从返回SUSPEND_CURRENT_QUEUE_A_MOMENT的消息开始消费。

延迟消息

RocketMQ 的 延迟消息(Scheduled / Delayed Message) 是一种机制,允许消息 在指定时间后再被消费,适用于如订单超时取消、延时通知、定时任务等场景。

message.setDeliverTimeMs(System.currentTimeMillis() + 10_000L);
//指定固定的延迟级别
message.setDelayTimeLevel(3);

可配置延迟级别(不推荐)
在这里插入图片描述
延迟级别实现:
对于指定固定延迟级别的延迟消息,RocketMQ的实现⽅式是预设⼀个系统Topic,名字叫做SCHEDULE_TOPIC_XXXXX。在这个Topic下,预设18个MessageQueue。这⾥每个对列就对应了⼀种延迟级别。然后每次扫描这18个队列⾥的消息,进⾏延迟操作就可以了。

指定时间点的延迟消息,RocketMQ是通过时间轮算法实现的。

批量消息

在 RocketMQ 中发送 批量消息(Batch Messages) 是一种优化吞吐量的常用方式。它允许你一次发送多条消息,减少网络开销,提升发送性能。但批量消息也有一些使用限制和注意事项。

  • 只支持相同的 Topic
  • 不支持延时消息
  • 不支持事务消息
  • 不支持顺序消息
  • 批量消息的⼤⼩不要超过1M,如果太⼤就需要⾃⾏分割
  • 默认会落在同一个队列
List<Message> messages = new ArrayList<>();
messages.add(new Message(TOPIC, TAG, "OrderID001", "Hello world 0".getBytes(StandardCharsets.UTF_8)));
messages.add(new Message(TOPIC, TAG, "OrderID002", "Hello world 1".getBytes(StandardCharsets.UTF_8)));
messages.add(new Message(TOPIC, TAG, "OrderID003", "Hello world 2".getBytes(StandardCharsets.UTF_8)));
SendResult sendResult = producer.send(messages);

事务消息

事务消息是RocketMQ⾮常有特⾊的⼀个⾼级功能。他的基础诉求是通过RocketMQ的事务机制,来保证上下游的数据⼀致性。
Rocket消息机制和ACL权限控制_第3张图片
考虑到事务的安全性,即要保证相关联的这⼏个业务⼀定是同时成功或者同时失败的。如果要将四个服务⼀起作为⼀个分布式事务来控制,可以做到,但是会⾮常麻烦。⽽使⽤RocketMQ在中间串联了之后,事情可以得到⼀定程度的简化。由于RocketMQ与消费者端有失败重试机制,所以,只要消息成功发送到RocketMQ了,那么可以认为Branch2.1,Branch2.2,Branch2.3这⼏个分⽀步骤,是可以保证最终的数据⼀致性的。这样,⼀个复杂的分布式事务问题,就变成了MinBranch1和Branch2两个步骤的分布式事务问题。

RocketMQ提出了事务消息机制,采⽤两阶段提交的思路,保证Main Branch1和Branch2之间的事务⼀致性。
Rocket消息机制和ACL权限控制_第4张图片

  1. ⽣产者将消息发送⾄Apache RocketMQ服务端。(发送半消息)
  2. Apache RocketMQ服务端将消息持久化成功之后,向⽣产者返回Ack确认消息已经发送成功,此时消息被标记为"暂不能投递",这种状态下的消息即为半事务消息
  3. ⽣产者开始执⾏本地事务逻辑。发送成功后,RocketMQ 会回调 Producer 实现的 executeLocalTransaction() 方法,在这里执行业务逻辑(如数据库操作)。
  4. ⽣产者根据本地事务执⾏结果向服务端提交⼆次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
  • ⼆次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
  • ⼆次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  1. 在断⽹或者是⽣产者应⽤重启的特殊情况下,若服务端未收到发送者提交的⼆次确认结果,或服务端收到的⼆次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息⽣产者即⽣产者集群中任⼀⽣产者实例发起消息回查。
  2. ⽣产者收到消息回查后,需要检查对应消息的本地事务执⾏的最终结果。也就是调 checkLocalTransaction() 方法。
  3. ⽣产者根据检查到的本地事务的最终状态再次提交⼆次确认,服务端仍按照步骤4对半事务消息进⾏处理。

RocketMQ保证了本地事务和消息生产两步骤的原子性,下游消费者有重试机制和死信机制,做最终的成功。代码需要注意的消息的幂等性,防止重复消费

TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer(PRODUCER_GROUP, Arrays.asList(TOPIC));\
producer.setExecutorService(executorService);//设置 RocketMQ 事务消息生产者在执行本地事务回查(TransactionCheck)任务时,所用的线程池。
producer.setTransactionListener(transactionListener);

ACL权限控制机制

RocketMQ提供了针对队列、⽤户等不同维度的⾮常全⾯的权限管理机制。通常来说,RocketMQ作为⼀个内部服务,是不需要进⾏权限控制的,但是,如果要通过RocketMQ进⾏跨部⻔甚⾄跨公司的合作,权限控制的重要性就显现出来了。

  • ACL 是基于用户名(AccessKey)和密码(SecretKey)的身份认证与权限控制机制。
  • 通过配置访问权限,控制客户端对 RocketMQ 资源的操作权限,提升系统安全性。
  • 支持细粒度权限控制,例如生产权限、消费权限、管理权限等。

权限控制体系

RocketMQ针对每个Topic,就有完整的权限控制。⽐如,在控制平台中,就可以很⽅便的给每个Topic配置权限。
Rocket消息机制和ACL权限控制_第5张图片

perm字段表示Topic的权限。有三个可选项。 2:禁写禁订阅,4:可订阅,不能写,6:可写可订阅

在Broker端还提供了更详细的权限控制机制。主要是在broker.conf中打开acl的标志:aclEnable=true。然后就可以⽤他提供的plain_acl.yml来进⾏权限配置了。并且plain_acl这个配置⽂件是热加载的
broker.conf

# ACL 开关
enableAcl=true
# 配置 ACL 白名单 IP(可选)
aclWhiteRemoteAddress=192.168.1.0/24;127.0.0.1

plain_acl.yml

#全局⽩名单,不受ACL控制
#通常需要将主从架构中的所有节点加进来 
globalWhiteRemoteAddresses:
- 10.10.103.*
- 192.168.0.*

accounts:
#第⼀个账户 
- accessKey: RocketMQ
  secretKey: 12345678
  whiteRemoteAddress:
  admin: false 
  defaultTopicPerm: DENY #默认Topic访问策略是拒绝 
  defaultGroupPerm: SUB #默认Group访问策略是只允许订阅 
  topicPerms:
   - topicA=DENY #topicA拒绝 
   - topicB=PUB|SUB #topicB允许发布和订阅消息 
   - topicC=SUB #topic只允许订阅 
  groupPerms:
  # the group should convert to retry topic
   - groupA=DENY
   - groupB=PUB|SUB
   - groupC=SUB
#第⼆个账户,只要是来⾃192.168.1.*的IP,就可以访问所有资源 
- accessKey: rocketmq2
  secretKey: 12345678
  whiteRemoteAddress: 192.168.1.*
# if it is admin, it could access all resources
  admin: true

客户端登录
mave

<dependency>
 <groupId>org.apache.rocketmqgroupId>
 <artifactId>rocketmq-aclartifactId>
 <version>4.9.1version>
 dependency>

然后在声明客户端时,传⼊⼀个RPCHook。

//声明时传⼊RPCHook
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName", getAclRPCHook());
   private static final String ACL_ACCESS_KEY = "RocketMQ";
   private static final String ACL_SECRET_KEY = "1234567";
   static RPCHook getAclRPCHook() {
   		return new AclClientRPCHook(new SessionCredentials(ACL_ACCESS_KEY,ACL_SECRET_KEY));
   }

你可能感兴趣的:(RocketMQ,rocketmq,java)