上文说到dledger模式有bug,于是还是踏实考察master-slave或HA的架构。最近看源码发现一个严重的bug,可能导致消息丢失。
具体分析过程有机会再详写,直接看MappedFile.java的flush方法代码
public int flush(final int flushLeastPages) {
if (this.isAbleToFlush(flushLeastPages)) {
if (this.hold()) {
int value = getReadPosition();
try {
//We only append data to fileChannel or mappedByteBuffer, never both.
if (writeBuffer != null || this.fileChannel.position() != 0) {
this.fileChannel.force(false);
} else {
this.mappedByteBuffer.force();
}
} catch (Throwable e) {
log.error("Error occurred when force data to disk.", e);
}
this.flushedPosition.set(value);
this.release();
} else {
log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get());
this.flushedPosition.set(getReadPosition());
}
}
return this.getFlushedPosition();
}
能看到try catch了错误之后,有log.error("Error occurred when force data to disk.", e);这行代码报错输出。
然后,就没有然后了。。。
反馈给上一级的还是正确,所以就算刷盘报了错,客户端最后依然收到的是SEND_OK。
然后问题来了,磁盘或存储故障(比如很常见的文件系统read only),然后客户端依然认为发送正常了。直到我们发现磁盘故障要重启机器,因为消息没写成功,然后消息就丢了。
修改起来也简单,加个返回码就行,已提交了PR。问题修改之前,HA架构也别想了。
另外大家源码分析的时候注意,网上说的实现刷盘的入口类是handleflushdisk,4.8.0实际应该是submitFlushRequest了。
还有就是RocketMQ的异常处理机制还有几个bug,后续有时间接着分析。
4.9.1 测试问题还是存在。