全网最细RocketMQ源码五:消息消费

1. DefaultMQPushConsumer属性

public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsumer {

    private final InternalLogger log = ClientLogger.getLog();

    // 消费者实现对象
    protected final transient DefaultMQPushConsumerImpl defaultMQPushConsumerImpl;

   
    // 消费者组名
    private String consumerGroup;

   
    // 消费模式:默认集群模式,还支持 广播模式
    private MessageModel messageModel = MessageModel.CLUSTERING;

  
    // 当从broker获取当前组内 该 queue 的offset 不存在时,consumeFromWhere 才有效
    // 默认:CONSUME_FROM_LAST_OFFSET   表示从队列的最后offset开始消费,当队列内再有一条新的msg加入进去时,消费者才会去消息
    private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;

  
    // 当ConsumeFromWhere配置的是CONSUME_FROM_TIMESTAMP 的时候,并且服务器中不存在 group 内该queue 的offset 时,会使用该时间戳
    // 值:消费者创建时间 - 30秒   转换成 格式: 年月日小时分钟秒   (20131223171201)
    private String consumeTimestamp = UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30));

    
    // 主题下的队列分配策略,rbl对象依赖该算法。
    private AllocateMessageQueueStrategy allocateMessageQueueStrategy;

    // 订阅信息集合,key :主题   val:过滤表达式 (一般都是tag)
    private Map<String /* topic */, String /* sub expression */> subscription = new HashMap<String, String>();

   
    // 消息监听器,消息处理逻辑 全部由它提供(具体怎么消费)..
    // 两种接口:1. MessageListenerConcurrently    2. MessageListenerOrderly
    private MessageListener messageListener;

    
    // 消费者本地消费进度存储(一般这里都是 RemoteBrokerOffsetStore)
    private OffsetStore offsetStore;

   
    // 消费服务线程池 线程数最小值,默认:20
    private int consumeThreadMin = 20;

    // 消费服务线程池 线程数最大值,默认:20
    private int consumeThreadMax = 20;

  
    // 该参数已经作废了..
    private long adjustThreadPoolNumsThreshold = 100000;

    // 本地队列快照(processQueue)中,第一条消息 和 最后一条消息  它俩之间的offset跨度 不能超过 2000  (队列流控使用)
    private int consumeConcurrentlyMaxSpan = 2000;

   
    // 本地队列快照(processQueue)中,消息数量限制  (队列流控使用)
    private int pullThresholdForQueue = 1000;

   
    // 本地队列快照(processQueue)中,消息总size 不能超过 “100”mib  (队列流控使用)
    private int pullThresholdSizeForQueue = 100;

  
    // 主题流控,消费者消费的指定主题的所有消息  在本地 不能超过 “pullThresholdForTopic” 值。 -1 表示不限制
    private int pullThresholdForTopic = -1;

  
    // 主题流控,消费者消费的指定主题的所有消息  在本地 不能超过 “pullThresholdSizeForTopic”mib大小。 -1 表示不限制
    private int pullThresholdSizeForTopic = -1;

    
    // 消费者 两次拉取请求时间间隔
    private long pullInterval = 0;


    // 消费任务最多可消费的消息数量(默认是:1,每一个消费任务 只消费一条消息)
    private int consumeMessageBatchMaxSize = 1;

    // 一次拉请求,最多可以从服务器拉取的消息数量
    private int pullBatchSize = 32;

    
    // 拉请求时,是否提交本地“订阅数据(过滤信息)”,默认不提交(心跳时,订阅数据已经同步到broker,没必要再提交了)
    private boolean postSubscriptionWhenPull = false;

   
    private boolean unitMode = false;

 
    // 消息最大重试次数, (-1)
    private int maxReconsumeTimes = -1;

 
    private long suspendCurrentQueueTimeMillis = 1000;

    
    // 消费者本地如果某条消息 “15” min 还未被消费,则需要回退该消息
    private long consumeTimeout = 15;

2. DefaultMQPushConsumerImpl属性

public class DefaultMQPushConsumerImpl implements MQConsumerInner {
   
    private final InternalLogger log = ClientLogger.getLog();
    // 门面对象(config)
    private final DefaultMQPushConsumer defaultMQPushConsumer;
    // rbl对象,职责:分配订阅主题的队列给当前消费者,20秒钟一个周期执行rbl算法(客户端实例触发)
    private final RebalanceImpl rebalanceImpl = new RebalancePushImpl(this);
    // 过滤消息hook
    private final ArrayList<FilterMessageHook> filterMessageHookList = new ArrayList<FilterMessageHook>();
    // 消费者启动时间
    private final long consumerStartTimestamp = System.currentTimeMillis();
    // 消息执行hook,在消息处理前 和 处理后 分别执行 hook.before  hook.after 系列方法 ,留给用户扩展的接口。
    private final ArrayList<ConsumeMessageHook> consumeMessageHookList = new ArrayList<ConsumeMessageHook>();
    // ...
    private final RPCHook rpcHook;
    // 消费者状态
    private volatile ServiceState serviceState = ServiceState.CREATE_JUST;
    // 客户端实例(整个进程内只有一个客户端实例对象)
    private MQClientInstance mQClientFactory;
    // 拉消息API封装(这里为什么要封装? 内部有推荐下次pull时 主机的算法,服务器broker 返回结果中,包装着 下次 pull 推荐的brokerId,
    // 根据本次请求数据 冷热 来进行推荐。)
    private PullAPIWrapper pullAPIWrapper;
    // 是否暂停
    private volatile boolean pause = false;
    // 是否顺序消费
    private boolean consumeOrderly = false;
    // 消息监听器
    private MessageListener messageListenerInner;
    // 消息进度存储器
    private OffsetStore offsetStore;
    // 消费消息服务
    // 1. ConsumeMessageConcurrentlyService
    // 2. ConsumeMessageOrderlyService
    private ConsumeMessageService consumeMessageService;

    // 队列流控次数(打印日志使用..默认每1000次流控,进行一次日志打印)
    private long queueFlowControlTimes = 0;
    // 流控使用 (控制打印日志..)
    private long queueMaxSpanFlowControlTimes = 0;

3. DefaultMQPushConsumerImpl start()

 public synchronized void start() throws MQClientException {
        switch (this.serviceState) {
            case CREATE_JUST:
                log.info("the consumer [{}] start beginning. messageModel={}, isUnitMode={}", this.defaultMQPushConsumer.getConsumerGroup(),
                    this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode());
                // 首先将启动状态设置为“失败”,启动成功之后,立马就改为“启动成功”
                this.serviceState = ServiceState.START_FAILED;

                // 检查配置(很多细节,自己去看)
                this.checkConfig();

                // 拷贝“订阅信息”到“rbl对象”
                this.copySubscription();


                if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) {
                    // 修改消费者 实例名称:pid
                    this.defaultMQPushConsumer.changeInstanceNameToPID();
                }

                // 获取客户端实例对象(进程内只有一个该实例)
                this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook);

                // 初始化 rbl 对象
                this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup());
                this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel());
                // 将队列分配策略对象,赋值给rbl
                this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy());
                this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);

                // 创建 拉消息API 对象(内部封装了查询推荐主机算法)
                this.pullAPIWrapper = new PullAPIWrapper(
                    mQClientFactory,
                    this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode());

                // 将 “过滤hook列表”注册到 pullApi 内,消息拉取下来之后,会执行该hook,再进行一次自定义的过滤
                this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);


                if (this.defaultMQPushConsumer.getOffsetStore() != null) {
                    this.offsetStore = this.defaultMQPushConsumer.getOffsetStore();
                } else {
                    // 一般走这里
                    switch (this.defaultMQPushConsumer.getMessageModel()) {
                        case BROADCASTING:
                            this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
                            break;
                        case CLUSTERING:// 只考虑“集群模式”
                            // 创建 offsetStore 实例
                            this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
                            break;
                        default:
                            break;
                    }
                    // 将创建的 store 对象 回写 到 门面对象
                    this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);
                }

                // 集群模式:load方法 什么也不干..
                this.offsetStore.load();


                if (this.getMessageListenerInner() instanceof MessageListenerOrderly) {
                    // 是否顺序消费 属性 设置为 true ,表示是顺序消费
                    this.consumeOrderly = true;

                    // 顺序消费的话,创建的消费服务,是顺序消费服务(ConsumeMessageOrderlyService)
                    this.consumeMessageService =
                        new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());

                } else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) {
                    // 是否顺序消费 属性 设置为 false,表示当前是 并发消费
                    this.consumeOrderly = false;
                    // 并发消费的话,创建的消费服务,是并发消费服务(ConsumeMessageConcurrentlyService)
                    this.consumeMessageService =
                        new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());
                }

                // 启动消费服务(回头讲..)
                this.consumeMessageService.start();

                // 将消费者注册到客户端实例内
                // 为什么注册?客户端实例给消费者提供了什么服务?
                // 1.心跳服务(把订阅数据同步到关注主题的broker)
                // 2.拉消息服务(内部PullMessageService 启动线程,基于 PullRequestQueue 工作,消费者负载均衡分配到队列后 会向该队列提交 pullRequest,后面细聊)
                // 3.队列负载服务(每20秒,调用一次consumer.doRebalance() 接口)
                // 4.消息进度持久化
                // 5.动态调整消费者 消费服务线程池(ps:看源码得知,这块逻辑被注释掉了...)
                boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this);

                if (!registerOK) {
                    this.serviceState = ServiceState.CREATE_JUST;
                    this.consumeMessageService.shutdown(defaultMQPushConsumer.getAwaitTerminationMillisWhenShutdown());
                    throw new MQClientException("The consumer group[" + this.defaultMQPushConsumer.getConsumerGroup()
                        + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
                        null);
                }

                // 启动客户端实例
                mQClientFactory.start();

                log.info("the consumer [{}] start OK.", this.defaultMQPushConsumer.getConsumerGroup());
                // 消费者状态设置为:RUNNING 运行中
                this.serviceState = ServiceState.RUNNING;
                break;
            case RUNNING:
            case START_FAILED:
            case SHUTDOWN_ALREADY:
                throw new MQClientException("The PushConsumer service state not OK, maybe started once, "
                    + this.serviceState
                    + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
                    null);
            default:
                break;
        }

        // 从nameserver 强制加载 主题路由数据,生成 主题的 set<mq> 交给 rbl 的 table。
        this.updateTopicSubscribeInfoWhenSubscriptionChanged();

        // 检查服务器是否支持“消息过滤模式”(一般咱们都是 tag 过滤,服务器默认支持),如果不支持,这里会抛异常
        this.mQClientFactory.checkClientInBroker();

        // 向所有已知的broker节点,发送心跳
        this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
        // 唤醒rbl 线程,让rbl线程 立马去做 触发负载均衡的事情。
        this.mQClientFactory.rebalanceImmediately();
    }

具体做了几件事:


4. 负载均衡的原理

入口是 MQClientInstance里面的rebalanceService -> 查询注册到MQClientInstance所有的消费者 ->消费者的doReblance方法

在这里插入图片描述

  1. 获取当前消费者 想要消费的主题, 这个SubscriptionInner就是消费者在订阅数据时候的配置在这里插入图片描述在这里插入图片描述

  2. 从topicSubscribeInfoTable(这里具体在哪里生成? MqInstance有个定时任务去拉取主题路由数据,并同时生成主题发布数据,同时也会生成主题订阅数据,也就是topicSubscribeInfoTable)找到该主题对应的Set在这里插入图片描述

  3. 获取当前消费者下的所有消费者id,然后对消费者id排序,保证每个消费者的视图是一样的

  4. 调用分配策略,最终给当前消费者 分配一个 set

  5. 如果负载均衡之后发现分给消费者的messageQueue变更了,如果变少了,从processQueueTable中将这条 <messagequeue, processQueue>删除,如果队列数目变多了,新建一个<messageQueue,processQueue>, processQueue作为 messageQueue的快照数据, 暂存从 messageQueue中拿取的数据在这里插入图片描述

  6. 准备为拉消息服务做嫁衣:将 messageQueue和processQueue封装到 PullRequest,并且放到阻塞队列中,PullMessageServie基于这个阻塞队列从 messageQueue拉取数据在这里插入图片描述

5.拉消息的逻辑

  • 入口是MQClientInstance里面的PullMessageService,它也是继承了 serviceThread的一个后台线程,PullMessageService从阻塞队列中take -> DefaultMQPushConsumerImpl.pullMessage(重点就是看这个流程) -> pullAPIWrapper.pullKernelImpl -> MQClientAPIImpl.pullMessage 网络层调用获取message数据
    • 设置 客户端回调PullCallback:
    • 构建 sys Flag:拉消息的时候,是否提交本地offset、是否允许服务器长轮训、是否使用类过滤

image.png

  • pullAPIWrapper.pullKernelImpl :使用网络层发起网络请求,从存储层拉取数据
  • MQClientAPIImpl.pullMessage:发起网络请求,从存储层拉取数据, 当存储层返回数据之后,交给 上面的pullCallback.onSucess方法
  • pullCallback.onSuceess:调用pullAPIWrapper.processPullResult,

image.png
再然后就是根据拉取的不同状态:

  1. 如果是拉取到数据了,更新下一个拉取offset、将拉取到的数据放到mq的processQueue、将拉取下来的messageQueue、processQueue 提交给_ConsumeMessageService,内部会封装成消费任务,提交给消费线程池去处理_
  2. 如果是拉取到,但是被过滤掉了


6. 并发消费的逻辑

入口是 pullCallback的处理逻辑:

在这里插入图片描述

● 后续的处理逻辑:
a. 根据配置的最大批量消费消息的数量,将拉取到的消息分割,比如拉取到的数据为32,最大提交任务的消息数目为1,那就切成32个1的ConsumeRequest�任务,提交给消费任务线程池
b. 消费者线程池调度到ConsumeRequest任务,也就是说走到它的run方法里面去 1、获取到外部传入的MessageListenerConcurrently 2、创建消费上下文对象 ConsumeConcurrentlyContext 3、将 上下文对象和 message消息 传入到listener,listener里面处理完成后,肯定会返回 success、fail、later等,并且调用它的consumeMessage方法(这就是我们自己控制的地方了) 4、processConsumeResult�:会拿到上面的success、fail、later等状态进行处理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值