前两天踩了个大坑。分享记录一下爬坑过程。
现象: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、然后可以开心的去呼呼了,有时候猿跑去做这种费力不讨好的事,就是为了追求内心的平静。【哈哈】