【Bug】RocketMQ在Pull模式下无法正确获取消费进度Offset

前两天踩了个大坑。分享记录一下爬坑过程。

现象:RocketMQ的Pull模式在cluster模式集群环境下,无法正确的获取/更新某些队列的消费进度。

为了爬坑顺着DefaultMQPullConsumer往下简单读了读代码,记录一下这里控制消费进度的大致逻辑:

1、Consumer里有一个OffsetStore,在cluster模式下的实现类是RemoteBrokerOffsetStore。

2、OffsetStore里维护着一个Map,这个Map以MessageQueen为Key,以消费进度为Value,在本地记录了这个Consumer在每条队列上的消费进度。

3、在RemoteBrokerOffsetStore里有一个关键的方法引起了我的注意:

public void persist(MessageQueue mq) {
        AtomicLong offset = this.offsetTable.get(mq);
        if (offset != null) {
                try {
                        this.updateConsumeOffsetToBroker(mq, offset.get());
                        log.debug("updateConsumeOffsetToBroker {} {}", mq, offset.get());
                } catch (Exception e) {
                        log.error("updateConsumeOffsetToBroker exception, " + mq.toString(), e);
                }
        }
}

这方法的作用是先保存Map中的Offset,再把本地RemoteBrokerOffsetStore中的Map的数值更新到Broker机上去。也就是在服务端也保存一份每条队列的消费进度。

但这个方法咋一看感觉好像有问题,假如这个步骤失败了,本地Map里的Offset会和Broker机上的Offset不同步。并且由于这个步骤被catch之后只打了一行日志之后就什么也没做,也就是说万一这个步骤失败,它的调用方会完全不知情。

4、读取Offset的值有三种ReadOffsetType的模式:优先从本地再从Broker机、从本地、从Broker机

@Override
public long readOffset(final MessageQueue mq, final ReadOffsetType type) {
        if (mq != null) {
                switch (type) {
                case MEMORY_FIRST_THEN_STORE:
                case READ_FROM_MEMORY: {
                        AtomicLong offset = this.offsetTable.get(mq);
                        if (offset != null) {
                                return offset.get();
                        } else if (ReadOffsetType.READ_FROM_MEMORY == type) {
                                return -1;
                        }
                }
                case READ_FROM_STORE: {
                        try {
                                long brokerOffset = this.fetchConsumeOffsetFromBroker(mq);
                                AtomicLong offset = new AtomicLong(brokerOffset);
                                this.updateOffset(mq, offset.get(), false);
                                return brokerOffset;
                        }
                        // No offset in broker
                        catch (MQBrokerException e) {
                                return -1;
                        }
                        //Other exceptions
                        catch (Exception e) {
                                log.warn("fetchConsumeOffsetFromBroker exception, " + mq, e);
                                return -2;
                        }
                }
                default:
                break;
                }
        }

        return -1;
}

但测试结果表明无论选择哪种模式,读出的结果都不稳定的有误,造成数据重复消费。

5、因此基本确定是因为persist这个方法我可能没有用对。

先用一个并不优雅,但绝对奏效的方案解决问题:不调用这两个方法去Broker机存取消费进度,而是直接把消费进度存数据库,从数据库存取。

6、虽然上面的方案解决了问题,但心里一直感觉很不爽。总感觉自己调用RocketMQ的方法没错,但为啥就是不把消费进度更新到Broker机上去?于是突发奇想爬起来看看下使用的版本3.2.0。

然后跑去官网把3.2.0往后所有的更新日志翻出来一篇一篇的查看。

终于……我发现了这个:

http://rocketmq.apache.org/release_notes/release-notes-4.0.0-incubating/

原来这个Bug,是在4.0.0修复的,也就是说之前的版本都有可能出现没办法把消费进度上传到Broker机上的问题。

7、然后可以开心的去呼呼了,有时候猿跑去做这种费力不讨好的事,就是为了追求内心的平静。【哈哈】

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值