RocketMQ Dead letter topic
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情
什么是 Dead Letter Topic
Dead letter topic
是对 retry topic
的升级, 当重复消费超过最大次数, 就会被发送 dead letter topic
死信. Dead letter topic
的命名构成是: %DLQ%${group_name}, 也是每个group一个dead letter topic.
但是和 retry topic
不一样, dead letter topic
并不会主动被 group 消费, 需要单独启动group
进行订阅.
- Dead Letter Topic 是什么时候创建
- Dead Letter Topic 的 queue 有多少个?
- rocketmq 是有 master-slave 的, slave 上也会有 dead letter topic 么?
DefaultMQPullConsumerImpl#sendMessageBack DefaultMQPushConsumerImpl#sendMessageBack
什么时候创建 dead letter topic
消费次数 > 最大消费次数 或者 delayLevel < 0 的情况下, 消息会被投递到 dead letter
, 如果这个时候 dead letter
并没有被创建, 也会重新创建。
创建逻辑在 CONSUMER_SEND_MSG_BACK
协议中实现。具体调用方在 NettyRemotingClient.java 中。
class NettyClientHandler extends SimpleChannelInboundHandler<RemotingCommand> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
processMessageReceived(ctx, msg);
}
}
调用链路
NettyRemotiongClient#start-> NettyRemotiongClient#NettyClientHandler ->NettyRemotingAbstract#processMessageReceived->NettyRemotingAbstract#processRequestCommand->SendMessageProcessor#asyncProcessRequest->SendMessageProcessor#asyncConsumerSendMsgBack
具体实现逻辑在 SendMessageProcessor#asyncProcessRequest
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:
SendMessageRequestHeader requestHeader = parseRequestHeader(request);
if (requestHeader == null) {
return CompletableFuture.completedFuture(null);
}
mqtraceContext = buildMsgContext(ctx, requestHeader);
this.executeSendMessageHookBefore(ctx, request, mqtraceContext);
if (requestHeader.isBatch()) {
return this.asyncSendBatchMessage(ctx, request, mqtraceContext, requestHeader);
} else {
return this.asyncSendMessage(ctx, request, mqtraceContext, requestHeader);
}
}
}
然后继续查看 SendMessageProcessor#asyncConsumerSendMsgBack
方法
主要关于这段
if (msgExt.getReconsumeTimes() >= maxReconsumeTimes
|| delayLevel < 0) {
newTopic = MixAll.getDLQTopic(requestHeader.getGroup());
queueIdInt = ThreadLocalRandom.current().nextInt(99999999) % DLQ_NUMS_PER_GROUP;
topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic,
DLQ_NUMS_PER_GROUP,
PermName.PERM_WRITE | PermName.PERM_READ, 0);
if (null == topicConfig) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("topic[" + newTopic + "] not exist");
return CompletableFuture.completedFuture(response);
}
msgExt.setDelayTimeLevel(0);
} else {
if (0 == delayLevel) {
delayLevel = 3 + msgExt.getReconsumeTimes();
}
msgExt.setDelayTimeLevel(delayLevel);
}
通过代码可以看到,当消费次数大于最大消费次数,或者 delaylevel < 0
的时候,消息会投递到 dead letter topic
。
通过代码可以看到 创建的 dead letter queue 个数默认为1, 因为 DLQ_NUMS_PER_GROUP
值默认为1 。
topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic,
DLQ_NUMS_PER_GROUP,
PermName.PERM_WRITE | PermName.PERM_READ, 0);
如果这个时候 dead letter 没有被创建,也会重新创建, 实现在createTopicInSendMessageBackMethod
public TopicConfig createTopicInSendMessageBackMethod(
final String topic,
final int clientDefaultTopicQueueNums,
final int perm,
final int topicSysFlag) {
TopicConfig topicConfig = this.topicConfigTable.get(topic);
if (topicConfig != null)
return topicConfig;
boolean createNew = false;
try {
if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
try {
topicConfig = this.topicConfigTable.get(topic);
if (topicConfig != null)
return topicConfig;
topicConfig = new TopicConfig(topic);
topicConfig.setReadQueueNums(clientDefaultTopicQueueNums);
topicConfig.setWriteQueueNums(clientDefaultTopicQueueNums);
topicConfig.setPerm(perm);
topicConfig.setTopicSysFlag(topicSysFlag);
log.info("create new topic {}", topicConfig);
this.topicConfigTable.put(topic, topicConfig);
createNew = true;
this.dataVersion.nextVersion();
this.persist();
} finally {
this.topicConfigTableLock.unlock();
}
}
} catch (InterruptedException e) {
log.error("createTopicInSendMessageBackMethod exception", e);
}
if (createNew) {
this.brokerController.registerBrokerAll(false, true, true);
}
return topicConfig;
}
通过代码可以看到,先判断了 topicConfig
是否为空,如果是空,重新创建。
消费失败场景下,brokerController
创建的 dead letter topic
, 具体查看 this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod
。
消费者客户端,可以消费 master broker
, 也有可能消费 slave broker
,也就是说,在消费失败的时候, dead letter 可能在 master/slave 上都会创建。
consumer 客户端消费有两种消费模式,通过代码也可以看出来:并发模式,有序模式。
processConsumeResult 会 调用checkReconsumeTimes & sendMessageBack
private boolean checkReconsumeTimes(List<MessageExt> 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;
}
总结
- Dead letter topic 是什么时候创建的?
发送死信消息的时候
- Dead letter topic 的queue有多少个?
1个
- rocketmq是有master-slave的,slave上也会有dead letter topic 吗?
Master-slave 都有可能