消息消费进度
概述
消费者消费消息过程中,为了避免消息的重复消费,应将消息消费进度保存起来,当其他消费者再对消息进行消费时,读取已消费的消息偏移量,对之后的消息进行消费即可。
消息模式分为两种:
- 集群模式:一条消息只能被一个消费者消费
- 广播模式:一条消息被所有消费者都消费一次
广播模式下,消息被所有消费者消费,因此消息消费的进度可以跟消费端保存在一起,即本地保存。
集群模式下,消息只能被集群内的一个消费者消费,进度不能保存在消费端,否则会导致消息重复消费,因此集群模式下消息进度集中保存在Broker中。
消息进度存储接口
OffsetStore
public interface OffsetStore {
//加载消息消费进度
void load() throws MQClientException;
//更新消费进度并保存在内存中
void updateOffset(final MessageQueue mq, final long offset, final boolean increaseOnly);
//从本地存储中获取消息消费进度
long readOffset(final MessageQueue mq, final ReadOffsetType type);
//保存所有消息消费进度-本地/远程
void persistAll(final Set<MessageQueue> mqs);
void persist(final MessageQueue mq);
//移除偏移量
void removeOffset(MessageQueue mq);
//根据Topic克隆一份消息队列消费进度缓存表
Map<MessageQueue, Long> cloneOffsetTable(String topic);
//更新消费进度到Broker
void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException,
MQBrokerException, InterruptedException, MQClientException;
}
DefaultMQPushConsumerImpl#start
switch (this.defaultMQPushConsumer.getMessageModel()) {
case BROADCASTING:
//广播模式下 将消息消费进度存储到本地
this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
case CLUSTERING:
//集群模式下 将消息消费的进度存储到远端Broker中
this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
default:
break;
}
this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);
如上所示,根据消息消费模式的不同,会创建不同的OffsetStore
对象。
广播模式消费进度存储(LocalFileOffsetStore)
public class LocalFileOffsetStore implements OffsetStore {
//存储目录
//消费者启动时-可以通过"-D rocketmq.client.localOffsetStoreDir=路径"来指定
public final static String LOCAL_OFFSET_STORE_DIR = System.getProperty(
"rocketmq.client.localOffsetStoreDir",
System.getProperty("user.home") + File.separator + ".rocketmq_offsets");
private final static InternalLogger log = ClientLogger.getLog();
//MQ客户端
private final MQClientInstance mQClientFactory;
//消费组名
private final String groupName;
//存储路径
private final String storePath;
//以MessageQueue为键-消费偏移量为值的缓存表
private ConcurrentMap<MessageQueue, AtomicLong> offsetTable =
new ConcurrentHashMap<MessageQueue, AtomicLong>();
}
构造函数
public LocalFileOffsetStore(MQClientInstance mQClientFactory, String groupName) {
this.mQClientFactory = mQClientFactory;
this.groupName = groupName;
//json格式存储
this.storePath = LOCAL_OFFSET_STORE_DIR + File.separator +
this.mQClientFactory.getClientId() + File.separator +
this.groupName + File.separator +
"offsets.json";
}
LocalFileOffsetStore#load
public void load() throws MQClientException {
//从本地磁盘中进行读取json文件-并进行序列化封装转化为map
OffsetSerializeWrapper offsetSerializeWrapper = this.readLocalOffset();
if (offsetSerializeWrapper != null && offsetSerializeWrapper.getOffsetTable() != null) {
//存入缓存表
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());
}
}
}
public class OffsetSerializeWrapper extends RemotingSerializable {
private ConcurrentMap<MessageQueue, AtomicLong> offsetTable =
new ConcurrentHashMap<MessageQueue, AtomicLong>();
}
LocalFileOffsetStore#persistAll
public void persistAll(Set<MessageQueue> mqs) {
if (null == mqs || mqs.isEmpty()) {
return;
}
OffsetSerializeWrapper offsetSerializeWrapper = new OffsetSerializeWrapper();
for (Map.Entry<MessageQueue, AtomicLong> entry : this.offsetTable.entrySet()) {
if (mqs.contains(entry.getKey())) {
AtomicLong offset = entry.getValue();
//填充<消息队列-消费偏移量>缓存表
offsetSerializeWrapper.getOffsetTable().put(entry.getKey(), offset);
}
}
//转为json格式
String jsonString = offsetSerializeWrapper.toJson(true);
if (jsonString != null) {
try {
//jsonString->file->保存到storePath
MixAll.string2File(jsonString, this.storePath);
} catch (IOException e) {
log.error("persistAll consumer offset Exception, " + this.storePath, e);
}
}
}
persistAll()
的入口是MQClientInstance#startScheduledTask
MQClientInstance#startScheduledTask
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.persistAllConsumerOffset();
} catch (Exception e) {
log.error("ScheduledTask persistAllConsumerOffset exception", e);
}
}
}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
消费端启动后延迟10s开启该定时任务,每隔5s进行一次持久化。
MQClientInstance#persistAllConsumerOffset
Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, MQConsumerInner> entry = it.next();
MQConsumerInner impl = entry.getValue();
impl.persistConsumerOffset();
}
DefaultMQPushConsumerImpl#persistConsumerOffset
try {
this.makeSureStateOK();
Set<MessageQueue> mqs = new HashSet<MessageQueue>();
//获取重负载分配好的消息队列
Set<MessageQueue> allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet();
mqs.addAll(allocateMq);
//当前是LocalFileOffsetStore.persistAll
this.offsetStore.persistAll(mqs);
} catch (Exception e) {
log.error("group: " + this.defaultMQPushConsumer.getConsumerGroup() + " persistConsumerOffset exception", e);
}
集群模式消费进度存储(RemoteBrokerOffsetStore)
RemoteBrokerOffsetStore
private final static InternalLogger log = ClientLogger.getLog();
//MQ客户端实例-该实例被同一个JVM下的消费者和生产者共用
private final MQClientInstance mQClientFactory;
//消费组名
private final String groupName;
//以消息队列为键-消费偏移量为值的缓存表
private ConcurrentMap<MessageQueue, AtomicLong> offsetTable =
new ConcurrentHashMap<MessageQueue, AtomicLong>();
RemoteBrokerOffsetStore#persistAll
for (Map.Entry<MessageQueue, AtomicLong> entry : this.offsetTable.entrySet()) {
MessageQueue mq = entry.getKey();
AtomicLong offset = entry.getValue();
if (offset != null) {
if (mqs.contains(mq)) {
try {
//更新消费偏移量到Broker
this.updateConsumeOffsetToBroker(mq, offset.get());
log.info("[persistAll] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}",
this.groupName,
this.mQClientFactory.getClientId(),
mq,
offset.get());
} catch (Exception e) {
log.error("updateConsumeOffsetToBroker exception, " + mq.toString(), e);
}
}
}
}
RemoteBrokerOffsetStore#updateConsumeOffsetToBroker
同步更新消息消费偏移量,如Master关闭,则更新到Slave。
//从MQ客户端中根据BrokerName获取消息队列对应的Broker
FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
if (null == findBrokerResult) {
//根据Topic从NameServer更新路由信息
this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
//重新查找
findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
}
if (findBrokerResult != null) {
//封装消息消费队列更新请求头
UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader();
requestHeader.setTopic(mq.getTopic()); //主题信息
requestHeader.setConsumerGroup(this.groupName); //消费者组
requestHeader.setQueueId(mq.getQueueId()); //队列ID
requestHeader.setCommitOffset(offset); //消费偏移量
if (isOneway) {
//Oneway->根据Broker地址->发送请求将消费偏移量保存到Broker-超时时间默认为5s
this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffsetOneway(
findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5);
} else {
this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffset(
findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5);
}
} else {
throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}
RemoteBrokerOffsetStore#updateOffset
if (mq != null) {
//从缓存中获取消息队列对应的偏移量
AtomicLong offsetOld = this.offsetTable.get(mq);
//为空
if (null == offsetOld) {
//将存入的offset存入内存中
offsetOld = this.offsetTable.putIfAbsent(mq, new AtomicLong(offset));
}
//不为空->根据increaseOnly更新原先的offsetOld
if (null != offsetOld) {
if (increaseOnly) {
MixAll.compareAndIncreaseOnly(offsetOld, offset);
} else {
offsetOld.set(offset);
}
}
}
RemoteBrokerOffsetStore#readOffset
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;
}
}
//从Broker中读取
case READ_FROM_STORE: {
try {
//从Broker中获取消费偏移量
long brokerOffset = this.fetchConsumeOffsetFromBroker(mq);
AtomicLong offset = new AtomicLong(brokerOffset);
//更新至内存中(map)
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;
}
}
RemoteBrokerOffsetStore#fetchConsumeOffsetFromBroker
//从MQ客户端实例中获取Boker信息
FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
if (null == findBrokerResult) {
//从NameServer中更新Topic的路由信息
this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
//重新获取Broker
findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
}
if (findBrokerResult != null) {
//封装查询消费进度请求头
QueryConsumerOffsetRequestHeader requestHeader = new QueryConsumerOffsetRequestHeader();
requestHeader.setTopic(mq.getTopic());
requestHeader.setConsumerGroup(this.groupName);
requestHeader.setQueueId(mq.getQueueId());
//带上请求头调用MQClientAPI到Broker中获取
return this.mQClientFactory.getMQClientAPIImpl().queryConsumerOffset(
findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5);
} else {
throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}
本文仅作为个人学习使用,如有不足或错误请指正!