有序消费模式和并发消费模式有一些关键地方不同,我这里只讲相对于并发模式下的不同点,以及对这些不同点做一些总结,说明为什么如此,所以看这篇博客之前希望你对并发消费模式的原理有一定的了解。
有序消费一般需要和有序发送一起配合使用,有序发送通过MessageQueueSelector来指定消息发送到的MessageQueue,尽量让有序消息发送到同一个MessageQueue,这是有序消费的基础,很简单,这里就不讲了,可自行百度。
1. 和并发模式一样,每一个订阅的MessageQueue的第一个消息拉取请求是由负载均衡后发起的,在负载均衡后,订阅了新的MessageQueue,此时不同于并发模式,有序模式需要向Broker请求MessageQueue的分布式锁,成功了才会发起第一个消息拉取请求。
public abstract class RebalanceImpl {
private boolean updateProcessQueueTableInRebalance(final String topic, final Set mqSet, final boolean isOrder) {
......
// 增加 不在processQueueTable && 存在于mqSet 里的消息队列。
List pullRequestList = new ArrayList<>();
for (MessageQueue mq : mqSet) {
if (!this.processQueueTable.containsKey(mq)) {
if (isOrder && !this.lock(mq)) { // 顺序消息锁定消息队列,锁定失败则等待下次负载均衡再试
log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
continue;
}
this.removeDirtyOffset(mq);
ProcessQueue pq = new ProcessQueue();
long nextOffset = this.computePullFromWhere(mq); //从Broker读取MessageQueue的消费OffSet
if (nextOffset >= 0) {
ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
if (pre != null) {
log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
} else {
log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
PullRequest pullRequest = new PullRequest();
pullRequest.setConsumerGroup(consumerGroup);
pullRequest.setNextOffset(nextOffset); //设置下次拉取时的起始位置
pullRequest.setMessageQueue(mq);
pullRequest.setProcessQueue(pq);
pullRequestList.add(pullRequest);
changed = true;
}
} else {
log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
}
}
}
// 发起消息拉取请求
this.dispatchPullRequest(pullRequestList);
return changed;
}
/**
* 向Broker请求获得指定消息队列的分布式锁
*
* @param mq 队列
* @return 是否成功
*/
public boolean lock(final MessageQueue mq) {
FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true);
if (findBrokerResult != null) {
LockBatchRequestBody requestBody = new LockBatchRequestBody();
requestBody.setConsumerGroup(this.consumerGroup);
requestBody.setClientId(this.mQClientFactory.getClientId());
requestBody.getMqSet().add(mq);
try {
// 请求Broker获得指定消息队列的分布式锁
Set lockedMq = this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000);
// 设置消息处理队列锁定成功。锁定消息队列成功,可能本地没有消息处理队列,设置锁定成功会在lockAll()方法。
for (MessageQueue mmqq : lockedMq) {
ProcessQueue processQueue = this.processQueueTable.get(mmqq);
if (processQueue != null) {
processQueue.setLocked(true);
processQueue.setLastLockTimestamp(System.currentTimeMillis());
}
}
boolean lockOK = lockedMq.contains(mq);
log.info("the message queue lock {}, {} {}",
lockOK ? "OK" : "Failed",
this.consumerGroup,
mq);
return lockOK;
} catch (Exception e) {
log.error("lockBatchMQ exception, " + mq, e);
}
}
return false;
}
}
2. 一个MessageQueue同一时间只能被一个消费者锁定,消费者每隔20S向Broker更新最后锁定时间。在请求分布式锁时,如果一个MessageQueue的最后更新时间对比当前超过了60S,则锁过期,此时MessageQueue能被其他消费者锁定。
public class ConsumeMessageOrderlyService implements ConsumeMessageService {
public void start() {
if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) {
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
ConsumeMessageOrderlyService.this.lockMQPeriodically();
}
}, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS);
}
}
/**
* 默认每20S锁定当前持有的所有MessageQueue
*/
public synchronized void lockMQPeriodically() {
if (!this.stopped) {
this.defaultMQPushConsumerImpl.getRebalanceImpl().lockAll();
}
}
}
3. 负载均衡后,在移除不属于自己的MessageQueue时,如果ConsumeMessageOrderlyService正在执行消费方法,消费上批次的消息,ConsumeMessageOrderlyService会给ProcessQueue加上锁,这次负载均衡就得不到此ProcessQueue的锁,也就不会释放相应的MessageQueue,所以只能等待相应MessageQueue在Broker的锁定时间过期后再被其他消费者锁定
public abstract class RebalanceImpl {
/**
* 当负载均衡时,依据从Namesrv获取到的最新的{@link TopicRouteData}更新{@link #processQueueTable}里的 : MessageQueue => ProcessQueue
* 若Broker或者Consumer有变化,新增了或者宕机了:
* - 移除 在processQueueTable && 不存在于 mqSet 里的消息队列
* - 增加 不在processQueueTable && 存在于 mqSet 里的消息队列
* - 发起对新的MessageQueue的消费请求
*
* @param topic Topic
* @param mqSet 负载均衡结果后最新的消息队列数组
* @param isOrder 是否顺序
* @return 是否变更
*/
private boolean updateProcessQueueTableInRebalance(final String topic, final Set mqSet, final boolean isOrder) {
boolean changed = false;
// 移除 在processQueueTable && 不存在于 mqSet 里的消息队列
Iterator> it = this.processQueueTable.entrySet().iterator();
while (it.hasNext()) {
Entry next = it.next();
MessageQueue mq = next.getKey();
ProcessQueue pq = next.getValue();
if (mq.getTopic().equals(topic)) {
if (!mqSet.contains(mq)) { // 不再属于自己的队列
pq.setDropped(true); //设置此队列被丢弃,不会再PullMessage
if (this.removeUnnecessaryMessageQueue(mq, pq)) { //移除不在属于自己的MessageQueue
it.remove();
changed = true;
log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq);
}
} else if (pq.isPullExpired()) { // 队列拉取超时,进行清理
switch (this.consumeType()) {
case CONSUME_ACTIVELY:
break;
case CONSUME_PASSIVELY: //Push形式
pq.setDropped(true);
if (this.removeUnnecessaryMessageQueue(mq, pq)) {
it.remove();
changed = true;
log.error("[BUG]doRebalance, {}, remove unnecessary mq, {}, because pull is pause, so try to fixed it",
consumerGroup, mq);
}
break;
default:
break;
}
}
}
}
......
}
}
public class RebalancePushImpl extends RebalanceImpl {
/**
* 移除不需要的队列相关的信息
* 1. 持久化消费进度,并移除之
* 2. 顺序消费&集群模式,解锁对该队列的锁定
*
* @param mq 消息队列
* @param pq 消息处理队列
* @return 是否移除成功
*/
@Override
public boolean removeUnnecessaryMessageQueue(MessageQueue mq, ProcessQueue pq) {
// 同步队列的消费进度,并移除之。
this.defaultMQPushConsumerImpl.getOffsetStore().persist(mq);
this.defaultMQPushConsumerImpl.getOffsetStore().removeOffset(mq);
// 集群模式下,顺序消费移除时,解锁对队列的锁定
if (this.defaultMQPushConsumerImpl.isConsumeOrderly() && MessageModel.CLUSTERING.equals(this.defaultMQPushConsumerImpl.messageModel())) {
try {
// 有序消费模式下,ConsumeMessageOrderlyService在执行 MessageListenerOrderly#consumeMessage() 时会锁定ProcessQueue,
// 执行此方法完后才会释放ProcessQueue的Lock,见ConsumeMessageOrderlyService#464 行
// 锁定不成功说明在长达1S的时间内一直都在执行 MessageListenerOrderly#consumeMessage 方法
// 也就是说 consumeMessage() 方法执行时间超过1S,每条消息的消费速度以及方法参数List的大小可能存在问题
// 此时只能等待相应MessageQueue在Broker的锁定时间过期后再被其他消费者锁定
if (pq.getLockConsume().tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
return this.unlockDelay(mq, pq);
} finally {
pq.getLockConsume().unlock();
}
} else {
log.warn("[WRONG]mq is consuming, so can not unlock it, {}. maybe hanged for a while, {}", mq, pq.getTryUnlockTimes());
pq.incTryUnlockTimes();
}
} catch (Exception e) {
log.error("removeUnnecessaryMessageQueue Exception", e);
}
return false;
}
return true;
}
/**
* {@link ProcessQueue#hasTempMessage()}也就是{@link ProcessQueue#msgTreeMap}里有消息
* 因为已经设置了{@link ProcessQueue#dropped = true},所以不会再消费msgTreeMap里的剩余消息
* 但需要等待之前已消费消息的结果处理完,提交最后的消费进度等,最后才释放MessageQueue的分布式锁,延迟20S释放
*
* @param mq 消息队列
* @param pq 消息处理队列
* @return 是否解锁成功
*/
private boolean unlockDelay(final MessageQueue mq, final ProcessQueue pq) {
if (pq.hasTempMessage()) {
log.info("[{}]unlockDelay, begin {} ", mq.hashCode(), mq);
this.defaultMQPushConsumerImpl.getmQClientFactory().getScheduledExecutorService().schedule(new Runnable() {
@Override
public void run() {
log.info("[{}]unlockDelay, execute at once {}", mq.hashCode(), mq);
RebalancePushImpl.this.unlock(mq, true);
}
}, UNLOCK_DELAY_TIME_MILLS, TimeUnit.MILLISECONDS);
} else {
this.unlock(mq, true);
}
return true;
}
}
有序模式下,负载均衡移除不在属于自己的MessageQueue时,有时候可能不会马上释放相应的分布式锁,极端情况下要等锁时间失效,很多情况下是延迟20S释放锁,当然如果消费压力不大的时候也会马上释放锁。所以有序消费模式下,新增加或者减少了消费者引起的负载均衡,很可能不会第一时间就能获取到消息。
这种设置我觉得是为了保证消息的严格有序,尽量使一个MessageQueue在同一时间只有一个消费者在消费,否则可能会出现消息乱序问题。 比如上一个消费者拉取了序号为:5,6,7,8,9的消息,但在消费到8的时候触发了负载均衡,因为此时会这是ProcessQueue的droped = true ,所以#9消费不会再消费。但是5,6,7,8的消费进度还没有提交给Broker,下一个消费者可能有重新拉取了5,6,7,8,9号消息,从5开始消费。一个消费者消费#8消息,一个消费#5消息,消息乱序了。
4. 并发模式和有序模式在拉取到消息后的处理也不同。在并发模式下,拉取到消息后,将消息合并入ProcessQueue的TreeMap,然后立即提交消费请求到消费线程池;而有序模式不同,有序模式在将拉取到的消息合并入ProcessQueue的TreeMap,此步骤会返回一个boolean变量dispathToConsume,此变量揭示TreeMap中是否还保留上次拉取的消费未消费完。并发模式忽略此值,直接提交消费请求;而有序模式只有当此值为true,也就是上一批次的消息消费完了才会提交消费请求。
public class DefaultMQPushConsumerImpl implements MQConsumerInner {
/**
* 拉取消息
*
* @param pullRequest 拉取消息请求
*/
public void pullMessage(final PullRequest pullRequest) {
......
// 提交拉取到的消息到ProcessQueue的TreeMap中
// 返回 true : 上一批次的消息已经消费完了
// 返回 false: 上一批次的消息还没消费完
boolean dispathToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
......
}
}
/**
* Queue consumption snapshot
* MessageQueue消费量快照
*/
public class ProcessQueue {
/**
* 添加消息,并返回是否提交给消费者
* 返回true,表示上一批次的Message已经消费完了,
* ${@link #consuming}的值在${@link #takeMessags(int)}里修改,也就是有序消费时会重置${@link #consuming = false}
*
* @param msgs 消息
* @return 是否提交给消费者
*/
public boolean putMessage(final List msgs) {
boolean dispatchToConsume = false;
try {
this.lockTreeMap.writeLock().lockInterruptibly();
try {
// 添加消息
int validMsgCnt = 0;
for (MessageExt msg : msgs) {
MessageExt old = msgTreeMap.put(msg.getQueueOffset(), msg);
if (null == old) {
validMsgCnt++;
this.queueOffsetMax = msg.getQueueOffset();
}
}
msgCount.addAndGet(validMsgCnt);
// 计算是否正在消费, 上一批次的消息是否消费完 consuming=true:还没完; consuming=false: 消费完了
if (!msgTreeMap.isEmpty() && !this.consuming) {
dispatchToConsume = true;
this.consuming = true;
}
// Broker累计消息数量
if (!msgs.isEmpty()) {
MessageExt messageExt = msgs.get(msgs.size() - 1);
String property = messageExt.getProperty(MessageConst.PROPERTY_MAX_OFFSET);
if (property != null) {
long accTotal = Long.parseLong(property) - messageExt.getQueueOffset();
if (accTotal > 0) {
this.msgAccCnt = accTotal;
}
}
}
} finally {
this.lockTreeMap.writeLock().unlock();
}
} catch (InterruptedException e) {
log.error("putMessage exception", e);
}
return dispatchToConsume;
}
}
“consuming” 这个变量在有序模式下,从TreeMap提取消息时,如果TreeMap为空时,会重置其为false
public class ProcessQueue {
/**
* 按顺序依次弹出TreeMap的第一条消息,直到凑满batchSize
* 弹出一条TreeMap就删除这条,然后重构,在弹出过程中会持有TreeMap的写锁,阻止TreeMap的写入
* 将得到的消息集合放入{@link #msgTreeMapTemp}
* 当msgTreeMap里面没有消息时,重置{@link #consuming = false},也就是说现有的消息都消费完了
*
* @param batchSize 条数
* @return 消息
*/
public List takeMessags(final int batchSize) {
List result = new ArrayList<>(batchSize);
final long now = System.currentTimeMillis();
try {
this.lockTreeMap.writeLock().lockInterruptibly();
this.lastConsumeTimestamp = now;
try {
if (!this.msgTreeMap.isEmpty()) {
for (int i = 0; i < batchSize; i++) {
Map.Entry entry = this.msgTreeMap.pollFirstEntry();
if (entry != null) {
result.add(entry.getValue());
msgTreeMapTemp.put(entry.getKey(), entry.getValue());
} else {
break;
}
}
}
//标识ProcessQueue里的msgTreeMap已经没有消息了,上一次拉取的消息已经消费完了
if (result.isEmpty()) {
consuming = false;
}
} finally {
this.lockTreeMap.writeLock().unlock();
}
} catch (InterruptedException e) {
log.error("take Messages exception", e);
}
return result;
}
}
有序模式会根据dispathToConsume 的值来决定是否提起消费请求,如果dispathToConsume=false,也就是说上一批次的消费还没有消费完,也就说有一个消费线程正在消费上批次的消息,就不起另一个线程来消费了,这样能保证消息的有序性。
public class DefaultMQPushConsumerImpl implements MQConsumerInner {
/**
* 拉取消息
*
* @param pullRequest 拉取消息请求
*/
public void pullMessage(final PullRequest pullRequest) {
......
boolean dispathToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
// 在有序消费模式下,仅当 dispathToConsume=true 时提交消费请求,也就是上一批次的消息消费完了才提交消费请求
// 在并发消费模式下,dispathToConsume不起作用,直接提交消费请求
DefaultMQPushConsumerImpl.this.consumeMessageService
.submitConsumeRequest(pullResult.getMsgFoundList(), processQueue, pullRequest.getMessageQueue(), dispathToConsume);
......
}
}
public class ConsumeMessageOrderlyService implements ConsumeMessageService {
/**
* 当 dispathToConsume=true 时提交消费请求,不指定拉取的消息,仅指明MessageQueue,ProcessQueue
* 因此,这个消费请求会一直消费,直到{@link ProcessQueue#msgTreeMap}里没有消息
* 可能出现这种情况,消息消费的速度慢于拉取的速度,那么一个消费请求会一直持续消费
* 也就是一个线程一直维持着消费消息,不释放MessageQueue的{@link MessageQueueLock}锁
* 其他线程干瞪眼等待
*
* @param msgs
* @param processQueue
* @param messageQueue
* @param dispathToConsume
*/
@Override
public void submitConsumeRequest(final List msgs,
final ProcessQueue processQueue,
final MessageQueue messageQueue,
final boolean dispathToConsume) {
if (dispathToConsume) {
ConsumeRequest consumeRequest = new ConsumeRequest(processQueue, messageQueue);
this.consumeExecutor.submit(consumeRequest);
}
}
}
并发模式的请求对象ConsumeRequest和有序模式下的请求对象有明显不同:
public class ConsumeMessageOrderlyService implements ConsumeMessageService {
class ConsumeRequest implements Runnable {
/**
* 消息处理队列
*/
private final ProcessQueue processQueue;
/**
* 消息队列
*/
private final MessageQueue messageQueue;
}
}
public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
class ConsumeRequest implements Runnable {
/**
* 消费消息列表
*/
private final List msgs;
/**
* 消息处理队列
*/
private final ProcessQueue processQueue;
/**
* 消息队列
*/
private final MessageQueue messageQueue;
}
}
并发模式的消费请求持有需要被消费的消息,提交消费请求会根据 “consumeMessageBatchMaxSize”的大小来拆分成多个ConsumeRequest 对象,然后一起提交给消费线程池。
有序模式不持有被消费的消息,所以消费请求是直接到ProcessQueue里的TreeMap提取消息,就是上面的
public List takeMessags(final int batchSize)
方法。当拉取到一批消息时,先合并入ProcessQueue的TreeMap,然后根据上批次的消息是否消费完来决定是否提交消费请求,如果没有消费完,那么继续去拉取下批次的消息(拉取前会有多个判断)。当前消费的线程会根据“consumeMessageBatchMaxSize”的大小,按顺序向TreeMap提取消息,循环消费,直到TreeMap没有剩余消息。所以可能出现一种情况,消息消费的速度 < 消息拉取速度,注意是拉取速度,不是生产速度,这种情况在有消息堆积的情况下是很正常的。此时这个线程会一直消费,每次拉取的消息合并入TreeMap中,以维持TreeMap不为空,不会有其他的消费请求被提交,也就是不会有其他的消费者线程。
5. 下面看有序模式的消费请求的处理:
class ConsumeRequest implements Runnable {
@Override
public void run() {
if (this.processQueue.isDropped()) {
log.warn("run, the message queue not be able to consume, because it's dropped. {}", this.messageQueue);
return;
}
// 获得 Consumer 消息队列锁,确保同一时间只有一个线程消费指定的MessageQueue
final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue);
synchronized (objLock) {
// (广播模式) 或者 (集群模式 && Broker消息队列锁有效)
if (MessageModel.BROADCASTING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
|| (this.processQueue.isLocked() && !this.processQueue.isLockExpired())) {
final long beginTime = System.currentTimeMillis();
// 循环
for (boolean continueConsume = true; continueConsume; ) {
//负载均衡后,当前MessageQueue不在属于自己,马上设置其ProcessQueue的dropped = true ,终止剩余消息的消费
if (this.processQueue.isDropped()) {
log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue);
break;
}
// ProcessQueue未被锁定,向Broker请求锁定相应的MessageQueue,成功了锁定ProcessQueue,然后再次消费
if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
&& !this.processQueue.isLocked()) {
log.warn("the message queue not locked, so consume later, {}", this.messageQueue);
ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 10);
break;
}
// ProcessQueue锁过期(30S),向Broker请求锁定相应的MessageQueue,成功了锁定ProcessQueue,然后再次消费
if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
&& this.processQueue.isLockExpired()) {
log.warn("the message queue lock expired, so consume later, {}", this.messageQueue);
ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 10);
break;
}
// 当前轮次消费时间超过连续时长,默认:60s,提交延迟消费请求。默认情况下,每消费1分钟休息10ms。
long interval = System.currentTimeMillis() - beginTime;
if (interval > MAX_TIME_CONSUME_CONTINUOUSLY) {
ConsumeMessageOrderlyService.this.submitConsumeRequestLater(processQueue, messageQueue, 10);
break;
}
// 获取消费消息。此处和并发消息请求不同,并发消息请求已经带了消费哪些消息。
final int consumeBatchSize = ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();
//按顺序提取msgTreeMap里的相应数量消息至msgTreeMapTemp临时集合中,此举会删除msgTreeMap里的那些消息,也就是这些消息目前只在临时集合msgTreeMapTemp中,之前有说明
List msgs = this.processQueue.takeMessags(consumeBatchSize);
if (!msgs.isEmpty()) {
final ConsumeOrderlyContext context = new ConsumeOrderlyContext(this.messageQueue);
ConsumeOrderlyStatus status = null;
// Hook:before
ConsumeMessageContext consumeMessageContext = null;
if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
consumeMessageContext = new ConsumeMessageContext();
consumeMessageContext
.setConsumerGroup(ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumerGroup());
consumeMessageContext.setMq(messageQueue);
consumeMessageContext.setMsgList(msgs);
consumeMessageContext.setSuccess(false);
// init the consume context type
consumeMessageContext.setProps(new HashMap());
ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);
}
// 执行消费
long beginTimestamp = System.currentTimeMillis();
ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
boolean hasException = false;
try {
this.processQueue.getLockConsume().lock(); // 获取队列消费锁
if (this.processQueue.isDropped()) {
log.warn("consumeMessage, the message queue not be able to consume, because it's dropped. {}",
this.messageQueue);
break;
}
status = messageListener.consumeMessage(Collections.unmodifiableList(msgs), context);
} catch (Throwable e) {
log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}", //
RemotingHelper.exceptionSimpleDesc(e), //
ConsumeMessageOrderlyService.this.consumerGroup, //
msgs, //
messageQueue);
hasException = true;
} finally {
this.processQueue.getLockConsume().unlock(); // 释放队列消费锁
}
if (null == status || ConsumeOrderlyStatus.ROLLBACK == status || ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT == status) {
log.warn("consumeMessage Orderly return not OK, Group: {} Msgs: {} MQ: {}",
ConsumeMessageOrderlyService.this.consumerGroup, msgs, messageQueue);
}
// 解析消费结果状态
long consumeRT = System.currentTimeMillis() - beginTimestamp;
if (null == status) {
if (hasException) {
returnType = ConsumeReturnType.EXCEPTION;
} else {
returnType = ConsumeReturnType.RETURNNULL;
}
} else if (consumeRT >= defaultMQPushConsumer.getConsumeTimeout() * 60 * 1000) {
returnType = ConsumeReturnType.TIME_OUT;
} else if (ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT == status) {
returnType = ConsumeReturnType.FAILED;
} else if (ConsumeOrderlyStatus.SUCCESS == status) {
returnType = ConsumeReturnType.SUCCESS;
}
if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name());
}
if (null == status) {
status = ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
// Hook:after
if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
consumeMessageContext.setStatus(status.toString());
consumeMessageContext
.setSuccess(ConsumeOrderlyStatus.SUCCESS == status || ConsumeOrderlyStatus.COMMIT == status);
ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext);
}
ConsumeMessageOrderlyService.this.getConsumerStatsManager()
.incConsumeRT(ConsumeMessageOrderlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT);
// 处理消费结果
continueConsume = ConsumeMessageOrderlyService.this.processConsumeResult(msgs, status, context, this);
} else {
continueConsume = false;
}
}
} else {
if (this.processQueue.isDropped()) {
log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue);
return;
}
ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 100);
}
}
}
}
final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue); :确保同一时间只有一个线程消费指定的MessageQueue
this.processQueue.getLockConsume().lock() ,在执行消费方法时时锁定ProcessQueue的,所以负载均衡后,若此ProcessQueue不在属于自己,释放MessageQueu的锁前需要先获取到ProcessQueue的锁,尝试时间为1S,如果消费方法的执行时间超过1S,则可能获取其锁失败,那么MessageQueue只能等待锁时间失效了,而非主动释放。
6. 处理消费结果:
public class ConsumeMessageOrderlyService implements ConsumeMessageService {
public boolean processConsumeResult(final List msgs,
final ConsumeOrderlyStatus status,
final ConsumeOrderlyContext context,
final ConsumeRequest consumeRequest
) {
boolean continueConsume = true;
long commitOffset = -1L;
if (context.isAutoCommit()) { //默认 AutoCommit = true
switch (status) {
case COMMIT:
case ROLLBACK:
log.warn("the message queue consume result is illegal, we think you want to ack these message {}", consumeRequest.getMessageQueue());
case SUCCESS:
// 提交消息已消费成功到消息处理队列
commitOffset = consumeRequest.getProcessQueue().commit();
// 统计
this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size());
break;
case SUSPEND_CURRENT_QUEUE_A_MOMENT:
// 统计
this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size());
// 统计是否对这些消息重消费,只要重消费次数未达到最大值,都会返回true
if (checkReconsumeTimes(msgs)) {
// 设置消息重新消费,将{@link #msgTreeMapTemp}里的消息重新放回{@link #msgTreeMap},这样重新消费时就能再次消费这些消息
consumeRequest.getProcessQueue().makeMessageToCosumeAgain(msgs);
// 提交延迟消费请求,在1S后重新开启消费
this.submitConsumeRequestLater(consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue(),
context.getSuspendCurrentQueueTimeMillis());
continueConsume = false;
} else {
commitOffset = consumeRequest.getProcessQueue().commit();
}
break;
default:
break;
}
}
......
// 消息处理队列未dropped,提交有效消费进度
if (commitOffset >= 0 && !consumeRequest.getProcessQueue().isDropped()) {
this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), commitOffset, false);
}
return continueConsume;
}
}
7. 计算消费进度,清空消费过的消息:
public class ProcessQueue {
/**
* 计算消费进度
*
* @return 消费进度
*/
public long commit() {
try {
this.lockTreeMap.writeLock().lockInterruptibly();
try {
// 消费进度
Long offset = this.msgTreeMapTemp.lastKey();
msgCount.addAndGet(this.msgTreeMapTemp.size() * (-1));
//清空临时消息
this.msgTreeMapTemp.clear();
// 返回消费进度
if (offset != null) {
return offset + 1;
}
} finally {
this.lockTreeMap.writeLock().unlock();
}
} catch (InterruptedException e) {
log.error("commit exception", e);
}
return -1;
}
}
8.处理消费失败的消息:
/**
* 计算是否要暂停消费
* 不暂停条件:存在消息都超过最大消费次数并且都发回broker成功
*
* @param msgs 消息
* @return 是否要暂停
*/
private boolean checkReconsumeTimes(List msgs) {
boolean suspend = false;
if (msgs != null && !msgs.isEmpty()) {
for (MessageExt msg : msgs) {
if (msg.getReconsumeTimes() >= getMaxReconsumeTimes()) {
MessageAccessor.setReconsumeTime(msg, String.valueOf(msg.getReconsumeTimes()));
if (!sendMessageBack(msg)) {
suspend = true;
msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
}
} else {
suspend = true;
msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
}
}
}
return suspend;
}
在默认情况下, checkReconsumeTimes(msgs) == true ,也就是将消费失败的消息添加会TreeMap,在1S后重新消费这些消息
总结: