RocketMQ源码学习(二)消费者包装对象及启动源码学习

 1. DefaultMQPushConsumer类主要内容

public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsumer {

    // 消费者实现对象
    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;

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

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

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

    private boolean unitMode = false;

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


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

 1.1 ConsumerFromWhere

public enum ConsumeFromWhere {
  //默认 CONSUME_FROM_LAST_OFFSET,
// 从有效offset的开始 进行消费 CONSUME_FROM_FIRST_OFFSET, // 从指定时间戳的消息开始消费(距离该时间戳最近的一条消息 offset 开始) CONSUME_FROM_TIMESTAMP, }

 2 DefaultMQPushConsumerImpl 

2.1 消费者实现类字段

public class DefaultMQPushConsumerImpl implements MQConsumerInner {// 门面对象(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;
}

 2.2 消费者启动类源码分    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;

                // 检查配置(很多细节,检查consumerGroup、messageModel等是否为null)
                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方法 什么也不干..广播模式下offset存储在消费者本地的,集群模式下,offset存储在broker上的,因此广播模式下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。遍历订阅的队列,遍历每一个主题和订阅数据,调用客户端实例的updateTopicRouteInfoFromNameServer方法。该方法首先到nameserver拉取最新的 路由数据,跟本地的路由数据做一个对比,
//本地如果没有或者路由发生变化会更新broker节点映射表,看topicRouteData主题都分布在哪几个主机上,将主题队列信息生成出来,每一个队列都会有一个MessageQueue对象,最终将队列主题信息交给了rebalanceImpl对象的topicSubscribeInfoTable(消费者订阅主题队列信息),就知道当前队列主题订阅了哪些信息,
//有了这些信息才能够用分配算法
this.updateTopicSubscribeInfoWhenSubscriptionChanged(); // 检查服务器是否支持“消息过滤模式”(一般咱们都是 tag 过滤,服务器默认支持),如果不支持,这里会抛异常 this.mQClientFactory.checkClientInBroker(); // 向所有已知的broker节点,发送心跳 this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); // 唤醒rbl 线程,让rbl线程 立马去做 触发负载均衡的事情。 this.mQClientFactory.rebalanceImmediately(); }

 2.2.1 拷贝“订阅信息”到“rbl对象”的方法源码学习

    private void copySubscription() throws MQClientException {
        try {
            // 获取门面对象“Subscription”
            Map<String, String> sub = this.defaultMQPushConsumer.getSubscription();

            if (sub != null) {
                for (final Map.Entry<String, String> entry : sub.entrySet()) {

                    final String topic = entry.getKey();
                    final String subString = entry.getValue();

                    SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPushConsumer.getConsumerGroup(),
                        topic, subString);
                    // 将创建的“订阅数据对象”加入到 “rbl对象的map内”
                    this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
                }
            }

            if (null == this.messageListenerInner) {
                // 将门面的messageListener 保存到 实现对象内
                this.messageListenerInner = this.defaultMQPushConsumer.getMessageListener();
            }

            switch (this.defaultMQPushConsumer.getMessageModel()) {
                case BROADCASTING:
                    break;
                case CLUSTERING:// 集群模式
                    // 获取当前“消费者组”的重试主题,规则:%RETRY%ConsumerGroup
                    final String retryTopic = MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup());

                    // 创建“%RETRY%ConsumerGroup”主题的 订阅数据对象
                    SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPushConsumer.getConsumerGroup(),
                        retryTopic, SubscriptionData.SUB_ALL);

                    // 将创建的“%RETRY%ConsumerGroup”主题 加入到 “rbl对象的map内”
                    // 这里为什么要订阅这样一个主题?
                    // 消息重试时,消息最终会再次加入到该主题,消费者订阅这个主题之后,就有机会再次拿到该“消息”,再次进行消费处理。
                    this.rebalanceImpl.getSubscriptionInner().put(retryTopic, subscriptionData);

                    break;
                default:
                    break;
            }
        } catch (Exception e) {
            throw new MQClientException("subscription exception", e);
        }
    }

 

posted @ 2022-10-16 22:06  Mistolte  阅读(107)  评论(0)    收藏  举报