源码版本号:版本号:4.9.4
以并发消费ConsumeMessageConcurrentlyService
为例
需要实现MessageListenerConcurrently
的consumeMessage
方法,该方法返回的是枚举值ConsumeConcurrentlyStatus
public enum ConsumeConcurrentlyStatus {
/**
* 消费成功
*/
CONSUME_SUCCESS,
/**
* 消费失败, 延迟一段时间后会继续消费
*/
RECONSUME_LATER;
}
查看ConsumeMessageConcurrentlyService
中调用
MessageListenerConcurrently#consumeMessage
方法
public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
class ConsumeRequest implements Runnable {
@Override
public void run() {
try {
// 省略......
/**
* 402行
* 调用MessageListenerConcurrently#consumeMessage方法
*/
status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);
} catch (Throwable e) {
// 省略......
}
// 省略
/**
* 430行
* 如果消费时出现异常或者消费后本来就是返回了null
* 则将状态修改为ConsumeConcurrentlyStatus.RECONSUME_LATER
* 代表稍后还会再消费
*/
if (null == status) {
status = ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
// 省略......
if (!processQueue.isDropped()) {
// 448行 最后会根据消费状态status做一些处理
ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this);
} else {
log.warn("processQueue is dropped without process consume result. messageQueue={}, msgs={}", messageQueue, msgs);
}
}
}
}
接着看ConsumeMessageConcurrentlyService#processConsumeResult
方法
public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
// 241行
public void processConsumeResult(
final ConsumeConcurrentlyStatus status,
final ConsumeConcurrentlyContext context,
final ConsumeRequest consumeRequest
) {
// 这里会返回 Integer.MAX_VALUE
int ackIndex = context.getAckIndex();
switch (status) {
case CONSUME_SUCCESS:
if (ackIndex >= consumeRequest.getMsgs().size()) {
ackIndex = consumeRequest.getMsgs().size() - 1;
}
// 省略
break;
case RECONSUME_LATER:
ackIndex = -1;
// 省略
break;
default:
break;
}
// 省略......
// 270行
switch (this.defaultMQPushConsumer.getMessageModel()) {
// 广播模式
case BROADCASTING:
// 只有消费失败即ackIndex为-1才会运行下面的for循环
for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) {
MessageExt msg = consumeRequest.getMsgs().get(i);
log.warn("BROADCASTING, the message consume failed, drop it, {}", msg.toString());
}
break;
// 集群模式
case CLUSTERING:
List<MessageExt> msgBackFailed = new ArrayList<MessageExt>(consumeRequest.getMsgs().size());
// 只有消费失败即ackIndex为-1才会运行下面的for循环
for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) {
MessageExt msg = consumeRequest.getMsgs().get(i);
// 这里会将消息投递到重试topic:%RETRY%+consumerGroup
boolean result = this.sendMessageBack(msg, context);
if (!result) {
msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
msgBackFailed.add(msg);
}
}
if (!msgBackFailed.isEmpty()) {
consumeRequest.getMsgs().removeAll(msgBackFailed);
// 如果发送重试消息失败了, 则会在本地延迟一段时间后继续提交消费
this.submitConsumeRequestLater(msgBackFailed, consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue());
}
break;
default:
break;
}
/**
* 298行
* 从未消费消息列表里面拿到消息的最小offset
*/
long offset = consumeRequest.getProcessQueue().removeMessage(consumeRequest.getMsgs());
if (offset >= 0 && !consumeRequest.getProcessQueue().isDropped()) {
// 更新消费进度
this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), offset, true);
}
}
}
如果消费状态为CONSUME_SUCCESS
,那么ackIndex
为消费消息数量减1;
如果消费状态为RECONSUME_LATER
,那么ackIndex
为-1。
如果消费失败,对于广播模式,只是打印warn
日志。
对于集群模式,会先尝试将这个消息投递到重试topic=%RETRY%+consumerGroup,
如果发送重试队列失败,则会在本地延迟一段时间后继续提交消费。
sendMessageBack
方法主要是向broker发送消息,代码逻辑比较简单。
sendMessageBack
会往broker发送RequestCode
为
CONSUMER_SEND_MSG_BACK
的请求,处理的入口在这里:这里是broker源码的逻辑
/**
* 子项目: broker
* 包名: org.apache.rocketmq.broker.processor;
*/
public class SendMessageProcessor extends AbstractSendMessageProcessor implements NettyRequestProcessor {
/**
* 90行
*/
public CompletableFuture<RemotingCommand> asyncProcessRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final SendMessageContext mqtraceContext;
switch (request.getCode()) {
case RequestCode.CONSUMER_SEND_MSG_BACK:
return this.asyncConsumerSendMsgBack(ctx, request);
default:
// 省略
}
}
// 117行
private CompletableFuture<RemotingCommand> asyncConsumerSendMsgBack(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
// 147行, 生成新的topic=%RETRY+consumerGroup
String newTopic = MixAll.getRetryTopic(requestHeader.getGroup());
// 192行
if (msgExt.getReconsumeTimes() >= maxReconsumeTimes
|| delayLevel < 0) {
/**
* 消息的重试最多进行 16 次,如果超过了 16 次就会将其丢进死信队列当中了
*/
newTopic = MixAll.getDLQTopic(requestHeader.getGroup());
} else {
/**
* 208行
* 这里值得注意的是,这个delayLevel就是消费者传过来的参数,并且它一直都是 0。
* 所以,基于上面的代码,delayLevel的值完全取决于当前Message的重试次数,
* 即 ReconsumeTimes。
*/
if (0 == delayLevel) {
delayLevel = 3 + msgExt.getReconsumeTimes();
}
msgExt.setDelayTimeLevel(delayLevel);
}
// 最后将消息寻到延时队列里面
}
}