offsetTable由来


offsetTable由来

接上篇文章
广播模式下TrackType为NOT_ONLINE

上篇文章分析并跟踪到源码 出现NOT_ONLINE的最终位置是因为获取不到offsetTable导致源码抛出异常

继续跟进
【MQClientAPIImpl.java】

//获取消费统计
public ConsumeStats getConsumeStats(final String addr, final String consumerGroup, final String topic, final long timeoutMillis)  
        throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException {  
    //创建 获取消费统计请求头  
    GetConsumeStatsRequestHeader requestHeader = new GetConsumeStatsRequestHeader();  
    //消费者组  
    requestHeader.setConsumerGroup(consumerGroup);  
    //订阅消息名称  
    requestHeader.setTopic(topic);  
    //创建 通信对象, code GET_CONSUME_STATS = 208  
    RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET\_CONSUME\_STATS, requestHeader);  
    //主要数据这里出来:10911 是broker的端口,这里是使用netty客户端调用,获取的offsetTable  
    RemotingCommand response = this.remotingClient.invokeSync(  
            MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr),  
            request,  
            timeoutMillis);  

    switch (response.getCode()) {  
        case ResponseCode.SUCCESS: {  
            //返回数据结构中 Map结构 offsetTable  
            ConsumeStats consumeStats = ConsumeStats.decode(response.getBody(), ConsumeStats.class);  
            return consumeStats;  
        }  
        default:  
            break;  
    }  

    throw new MQBrokerException(response.getCode(), response.getRemark());  
}

继续跟进
【broker: AdminBrokerProcessor.java】

@Override  
public RemotingCommand processRequest(ChannelHandlerContext ctx,  
    RemotingCommand request) throws RemotingCommandException {  
    switch (request.getCode()) {  
       ... 
        case RequestCode.GET_CONSUME_STATS:  
            //这里获取消费统计  
            return this.getConsumeStats(ctx, request);  
        ....
}

继续跟进

【broker: AdminBrokerProcessor.java】

private RemotingCommand getConsumeStats(ChannelHandlerContext ctx,  
    RemotingCommand request) throws RemotingCommandException { 

    final RemotingCommand response = RemotingCommand.createResponseCommand(null);  
    final GetConsumeStatsRequestHeader requestHeader =  
        (GetConsumeStatsRequestHeader) request.decodeCommandCustomHeader(GetConsumeStatsRequestHeader.class);  

    ConsumeStats consumeStats = new ConsumeStats();  

    Set<String> topics = new HashSet<String>();  
    if (UtilAll.isBlank(requestHeader.getTopic())) {  
        //当前消费者订阅的 topic列表   这里下文继续分析一下
        topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(requestHeader.getConsumerGroup());  
    } else {  
        topics.add(requestHeader.getTopic());  
    }  
    //在广播模式下 topics 是空的 导致 ConsumeStats 也是没有数据的  
    for (String topic : topics) {  
        ...
            //这是我们要的数据  
            offsetWrapper.setBrokerOffset(brokerOffset);  
        ...
            consumeStats.getOffsetTable().put(mq, offsetWrapper);  
        }  

        double consumeTps = this.brokerController.getBrokerStatsManager().tpsGroupGetNums(requestHeader.getConsumerGroup(), topic);  

        consumeTps += consumeStats.getConsumeTps();  
        consumeStats.setConsumeTps(consumeTps);  
    }  

    byte[] body = consumeStats.encode();  
    response.setBody(body);  
    response.setCode(ResponseCode.SUCCESS);  
    response.setRemark(null);  
    return response;  
}

继续跟进
【ConsumerOffsetManager.java】

/**
 * 当前消费者组 能匹配到的topic 列表
 * 简而言之:当前消费者订阅的 topic列表
 * @param group
 * @return
 */  
public Set<String> whichTopicByConsumer(final String group) {  
    Set<String> topics = new HashSet<String>();  
    //offsetTable 在这里呢,但是通过具体审查后 发现这里面都是集群模式的offsetTable 数据  
    //也就是说 groupName 咱们是广播模式,参数进来后 无法匹配到  
    Iterator<Entry<String, ConcurrentMap<Integer, Long>>> it = this.offsetTable.entrySet().iterator();  
    while (it.hasNext()) {  
        Entry<String, ConcurrentMap<Integer, Long>> next = it.next();  
        //格式 topic@consumerGroupName  
        String topicAtGroup = next.getKey();  
        String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR);  
        if (arrays.length == 2) {  
            //当前广播模式 消费者组名 equals  集群模式的消费者组名 ,无一例外 全部匹配不成功  
            if (group.equals(arrays[1])) {  
                topics.add(arrays[0]);  
            }  
        }  
    }  

    return topics;  
}

[========]
看到此处其实已经能够发现 其实ONT_ONLINE 真的是设计如此,不是我们消息未消费

但是我们是有上进心的孩子,得看看offsetTabel 数据是怎么加载进来的呀
继续搞

broker启动类 加载集群模式下offsetTable

【BrokerStartup.java】

public static void main(String[] args) {
    //探索 broker启动时 加载offsetTable
    start(createBrokerController(args));
}

追踪

/**
 * 探索 broker启动时 加载offsetTable
 * @param args
 * @return
 */
public static BrokerController createBrokerController(String\[\] args) {  
       ...
        //fixme 探索 broker启动时 加载offsetTable  
        boolean initResult = controller.initialize();  
        ...
    return null;  
}

追踪

public boolean initialize() throws CloneNotSupportedException {  
boolean result = this.topicConfigManager.load();  
//fixme 这里加载了 offsetTable数据 注意这是broker项目 一定是加载的集群模式的offsetTable哈  
result = result && this.consumerOffsetManager.load();

追踪
load()方法 流程

1、configFilePath() 获取配置文件路径
ConsumerOffsetManager.java

/**
 * fixme 启动时 加载的offset配置文件 地址
 * storePathRootDir 系统存储路径 System.getProperty("user.home") /  "store";
 * getConsumerOffsetPath 拼接 系统存储路径 / "config"/ "consumerOffset.json";
 * @return
 */
@Override  
public String configFilePath() {  
    return BrokerPathConfigHelper.getConsumerOffsetPath(this.brokerController.getMessageStoreConfig().getStorePathRootDir());  
}

2、decode()

/**
 * fixme 解析出来的数据 添加到offsetTable
 * @param jsonString
 */
@Override  
public void decode(String jsonString) {  
    if (jsonString != null) {  
        ConsumerOffsetManager obj = RemotingSerializable.fromJson(jsonString, ConsumerOffsetManager.class);  
        if (obj != null) {  
            //这里哈
            this.offsetTable = obj.offsetTable;  
        }  
    }  
}

这不就出来了嘛,offsetTable 数据是存储在broker服务器上的
但是怎么确定 广播模式 是存储在消费者本地的呢?
继续探索呗

广播模式下加载offsetTable

clinet 启动类
【DefaultMQPushConsumer.java】

 /**
 * This method gets internal infrastructure readily to serve. Instances must call this method after configuration.
 *  fixme 实例配置后必须调用该方法 ,这个方法熟悉吧,我们在编写代码后 一定会 启动DefaultMQPushConsumer.start();
 * @throws MQClientException if there is any client error.
 */
public void start() throws MQClientException {  
    ... 
    //这里启动实现  
    this.defaultMQPushConsumerImpl.start();  
    ...
}

继续追踪

public synchronized void start() throws MQClientException {  
    ...
    if (this.defaultMQPushConsumer.getOffsetStore() != null) {  
        this.offsetStore = this.defaultMQPushConsumer.getOffsetStore();  
    } else {  
        switch (this.defaultMQPushConsumer.getMessageModel()) {  
        case BROADCASTING:  
        //fixme 广播模式 初始化 offset存储路径  
        this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());  
        break;                    case CLUSTERING:  
        //fixme 集群模式  
        this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());  
        break;                    default:  
        break;  
    }  
        this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);  
    }  
    //fixme 加载offset  注意集群模式的load 是空实现,因为它是broker 加载的  
    this.offsetStore.load();  
    ...
}

分两步追踪 1、 offset存储路径 怎么得到的 2、怎么加载offset

1、offset存储路径
【LocalFileOffsetStore.java】

public LocalFileOffsetStore(MQClientInstance mQClientFactory, String groupName) {
    this.mQClientFactory = mQClientFactory;
    this.groupName = groupName;
    //初始化 存储路径 user.home / .rocketmq_offsets / getClientId = ip@instanceName / groupName / offsets.json
    this.storePath = LOCAL_OFFSET_STORE_DIR + File.separator +
        this.mQClientFactory.getClientId() + File.separator +
        this.groupName + File.separator +
        "offsets.json";
}

2、如何加载offset
【LocalFileOffsetStore.java】

@Override
public void load() throws MQClientException {
    // 读取本地 offset
    OffsetSerializeWrapper offsetSerializeWrapper = this.readLocalOffset();
    if (offsetSerializeWrapper != null && offsetSerializeWrapper.getOffsetTable() != null) {
        //添加到offsetTable
        offsetTable.putAll(offsetSerializeWrapper.getOffsetTable());

        for (MessageQueue mq : offsetSerializeWrapper.getOffsetTable().keySet()) {
            AtomicLong offset = offsetSerializeWrapper.getOffsetTable().get(mq);
            log.info("load consumer's offset, {} {} {}",
                this.groupName,
                mq,
                offset.get());
        }
    }
}
posted @ 2022-09-27 17:19  smile008  阅读(87)  评论(0编辑  收藏  举报