/**
* Copyright (C) 2010-2013 Alibaba Group Holding Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.rocketmq.broker.processor;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.rocketmq.broker.BrokerController;
import com.alibaba.rocketmq.broker.digestlog.SendbackmsgLiveMoniter;
import com.alibaba.rocketmq.broker.digestlog.SendmsgLiveMoniter;
import com.alibaba.rocketmq.common.MixAll;
import com.alibaba.rocketmq.common.TopicConfig;
import com.alibaba.rocketmq.common.TopicFilterType;
import com.alibaba.rocketmq.common.UtilAll;
import com.alibaba.rocketmq.common.constant.LoggerName;
import com.alibaba.rocketmq.common.constant.PermName;
import com.alibaba.rocketmq.common.help.FAQUrl;
import com.alibaba.rocketmq.common.message.MessageConst;
import com.alibaba.rocketmq.common.message.MessageDecoder;
import com.alibaba.rocketmq.common.message.MessageExt;
import com.alibaba.rocketmq.common.protocol.MQProtos.MQRequestCode;
import com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode;
import com.alibaba.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import com.alibaba.rocketmq.common.protocol.header.SendMessageRequestHeader;
import com.alibaba.rocketmq.common.protocol.header.SendMessageResponseHeader;
import com.alibaba.rocketmq.common.subscription.SubscriptionGroupConfig;
import com.alibaba.rocketmq.common.sysflag.MessageSysFlag;
import com.alibaba.rocketmq.remoting.common.RemotingHelper;
import com.alibaba.rocketmq.remoting.exception.RemotingCommandException;
import com.alibaba.rocketmq.remoting.netty.NettyRequestProcessor;
import com.alibaba.rocketmq.remoting.protocol.RemotingCommand;
import com.alibaba.rocketmq.remoting.protocol.RemotingProtos.ResponseCode;
import com.alibaba.rocketmq.store.MessageExtBrokerInner;
import com.alibaba.rocketmq.store.PutMessageResult;
/**
* 处理客户端发送消息的请求
*
* @author shijia.wxr<vintage.wang@gmail.com>
* @since 2013-7-26
*/
public class SendMessageProcessor implements NettyRequestProcessor {
private static final Logger log = LoggerFactory.getLogger(LoggerName.BrokerLoggerName);
private final static int DLQ_NUMS_PER_GROUP = 1;
private final BrokerController brokerController;
private final Random random = new Random(System.currentTimeMillis());
private final SocketAddress storeHost;
public SendMessageProcessor(final BrokerController brokerController) {
this.brokerController = brokerController;
this.storeHost =
new InetSocketAddress(brokerController.getBrokerConfig().getBrokerIP1(), brokerController
.getNettyServerConfig().getListenPort());
}
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request)
throws RemotingCommandException {
MQRequestCode code = MQRequestCode.valueOf(request.getCode());
switch (code) {
case SEND_MESSAGE:
/*
* chen.si 消息发送事件,包括 普通消息、事务消息prepare/commit/rollback、定时消息
*/
return this.sendMessage(ctx, request);
case CONSUMER_SEND_MSG_BACK:
/*
* chen.si consumer消息处理失败,直接发回给 消息所在的 broker
*
* 参考MQConsumer.sendMessageBack注释:
* Consumer消费失败的消息可以选择重新发回到服务器端,并延时消费
* 会首先尝试将消息发回到消息之前存储的主机,此时只传送消息Offset,消息体不传送,不会占用网络带宽
* 如果发送失败,会自动重试发往其他主机,此时消息体也会传送
* 重传回去的消息只会被当前Consumer Group消费。
*/
return this.consumerSendMsgBack(ctx, request);
default:
break;
}
return null;
}
private RemotingCommand consumerSendMsgBack(final ChannelHandlerContext ctx, final RemotingCommand request)
throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
final ConsumerSendMsgBackRequestHeader requestHeader =
(ConsumerSendMsgBackRequestHeader) request
.decodeCommandCustomHeader(ConsumerSendMsgBackRequestHeader.class);
/*
* chen.si 为每个consumer group建立 重试分区队列,其中topic为%RETRY + consumerGroupName
* 因为一种consumer group处理消息失败, 只能放在自己专属的重试队列里,不能影响 其他的consumer group
*/
// 确保订阅组存在
SubscriptionGroupConfig subscriptionGroupConfig =
this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(
requestHeader.getGroup());
if (null == subscriptionGroupConfig) {
response.setCode(MQResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST_VALUE);
response.setRemark("subscription group not exist, " + requestHeader.getGroup() + " "
+ FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST));
return response;
}
/*
* chen.si 此消息不需要重试的意思
*/
// 如果重试队列数目为0,则直接丢弃消息
if (subscriptionGroupConfig.getRetryQueueNums() <= 0) {
response.setCode(ResponseCode.SUCCESS_VALUE);
response.setRemark(null);
return response;
}
/*
* chen.si 重试的消息,作为特殊的topic:%RETRY% + consumerGroup
*/
String newTopic = MixAll.getRetryTopic(requestHeader.getGroup());
/*
* chen.si 随便扔到一个重试队列里
*/
int queueIdInt =
Math.abs(this.random.nextInt() % 99999999) % subscriptionGroupConfig.getRetryQueueNums();
/*
* chen.si 重试的topic还没有,创建一个
*/
// 检查topic是否存在
TopicConfig topicConfig =
this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(//
newTopic,//
subscriptionGroupConfig.getRetryQueueNums(), //
PermName.PERM_WRITE | PermName.PERM_READ);
if (null == topicConfig) {
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("topic[" + newTopic + "] not exist");
return response;
}
// 检查topic权限
if (!PermName.isWriteable(topicConfig.getPerm())) {
response.setCode(MQResponseCode.NO_PERMISSION_VALUE);
response.setRemark("the topic[" + newTopic + "] sending message is forbidden");
return response;
}
// 查询消息,这里如果堆积消息过多,会访问磁盘
// 另外如果频繁调用,是否会引起gc问题,需要关注 TODO
/*
* chen.si 发回的消息里,只有commit offset,根据commit offset寻找消息
*/
MessageExt msgExt =
this.brokerController.getMessageStore().lookMessageByOffset(requestHeader.getOffset());
if (null == msgExt) {
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("look message by offset failed, " + requestHeader.getOffset());
return response;
}
// 构造消息
final String retryTopic = msgExt.getProperty(MessageConst.PROPERTY_RETRY_TOPIC);
if (null == retryTopic) {
/*
* chen.si 保留此消息的原始topic
*/
msgExt.putProperty(MessageConst.PROPERTY_RETRY_TOPIC, msgExt.getTopic());
}
/*
* chen.si 不用等了
*/
msgExt.setWaitStoreMsgOK(false);
// 客户端自动决定定时级别
int delayLevel = requestHeader.getDelayLevel();
// 死信消息处理
if (msgExt.getReconsumeTimes() >= subscriptionGroupConfig.getRetryMaxTimes()//
|| delayLevel < 0) {
/*
* chen.si 为每个consumer group,创建死信队列,名称为 %DLQ% + consumerGroupName
* 这里的死信消息无需处理了,直接扔到 死信topic和队列中 %DLQ%
*/
newTopic = MixAll.getDLQTopic(requestHeader.getGroup());
/*
* chen.si 只有1个队列
*/
queueIdInt = Math.abs(this.random.nextInt() % 99999999) % DLQ_NUMS_PER_GROUP;
/*
* chen.si 死信topic还没有,创建1个
*/
topicConfig =
this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(
newTopic, //
DLQ_NUMS_PER_GROUP,//
PermName.PERM_WRITE);
if (null == topicConfig) {
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("topic[" + newTopic + "] not exist");
return response;
}
}
// 继续重试
else {
/*
* chen.si 说明还可以重试,用定时消息 的方式 来重试
*/
if (0 == delayLevel) {
delayLevel = 3 + msgExt.getReconsumeTimes();
}
/*
* chen.si 消息的延迟级别是通过 properties 的 键值 来存储的,直接设置这个
*/
msgExt.setDelayTimeLevel(delayLevel);
}
/*
* chen.si 重新存储消息,commit log中 以及 定时队列中
*/
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(newTopic);
msgInner.setBody(msgExt.getBody());
msgInner.setFlag(msgExt.getFlag());
msgInner.setProperties(msgExt.getProperties());
msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties()));
msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(null, msgExt.getTags()));
msgInner.setQueueId(queueIdInt);
msgInner.setSysFlag(msgExt.getSysFlag());
msgInner.setBornTimestamp(msgExt.getBornTimestamp());
msgInner.setBornHost(msgExt.getBornHost());
msgInner.setStoreHost(this.getStoreHost());
msgInner.setReconsumeTimes(msgExt.getReconsumeTimes() + 1);
PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
SendbackmsgLiveMoniter.printProcessSendmsgRequestLive(ctx.channel(), request, putMessageResult,
delayLevel, msgExt.getReconsumeTimes());
if (putMessageResult != null) {
switch (putMessageResult.getPutMessageStatus()) {
case PUT_OK:
response.setCode(ResponseCode.SUCCESS_VALUE);
response.setRemark(null);
return response;
default:
break;
}
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark(putMessageResult.getPutMessageStatus().name());
return response;
}
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("putMessageResult is null");
return response;
}
private String diskUtil() {
String storePathPhysic = this.brokerController.getMessageStoreConfig().getStorePathCommitLog();
double physicRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic);
String storePathLogis = this.brokerController.getMessageStoreConfig().getStorePathConsumeQueue();
double logisRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathLogis);
String storePathIndex = this.brokerController.getMessageStoreConfig().getStorePathIndex();
double indexRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathIndex);
return String.format("CL: %5.2f CQ: %5.2f INDEX: %5.2f", physicRatio, logisRatio, indexRatio);
}
private RemotingCommand sendMessage(final ChannelHandlerContext ctx, final RemotingCommand request)
throws RemotingCommandException {
final RemotingCommand response =
RemotingCommand.createResponseCommand(SendMessageResponseHeader.class);
final SendMessageResponseHeader responseHeader =
(SendMessageResponseHeader) response.getCustomHeader();
final SendMessageRequestHeader requestHeader =
(SendMessageRequestHeader) request.decodeCommandCustomHeader(SendMessageRequestHeader.class);
// 由于有直接返回的逻辑,所以必须要设置
response.setOpaque(request.getOpaque());
if (log.isDebugEnabled()) {
log.debug("receive SendMessage request command, " + request);
}
/**
* chen.si 判断当前broker是否有 写 权限
*/
// 检查Broker权限
if (!PermName.isWriteable(this.brokerController.getBrokerConfig().getBrokerPermission())) {
response.setCode(MQResponseCode.NO_PERMISSION_VALUE);
response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
+ "] sending message is forbidden");
return response;
}
final byte[] body = request.getBody();
/**
* chen.si 保留字包括2个: TBW102 和 broker-cluster-name
*/
// Topic名字是否与保留字段冲突
if (!this.brokerController.getTopicConfigManager().isTopicCanSendMessage(requestHeader.getTopic())) {
String errorMsg =
"the topic[" + requestHeader.getTopic() + "] is conflict with system reserved words.";
log.warn(errorMsg);
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark(errorMsg);
return response;
}
// 检查topic是否存在
TopicConfig topicConfig =
this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
if (null == topicConfig) {
log.warn("the topic " + requestHeader.getTopic() + " not exist, producer: "
+ ctx.channel().remoteAddress());
/**
* chen.si 可以自动创建topic
*/
topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageMethod(//
requestHeader.getTopic(), //
requestHeader.getDefaultTopic(), //
RemotingHelper.parseChannelRemoteAddr(ctx.channel()), //
requestHeader.getDefaultTopicQueueNums());
// 尝试看下是否是失败消息发回
if (null == topicConfig) {
/**
* chen.si 如果有失败需要重试的消息,正常情况下会发回给 之前消息发出的broker。 但是如果原有的broker失败,则会自动调整到其他的可用broker。
* 因为是重试的消息,所以消息的topic为 %RETRY%xxTopic 的模式。但是是以send发方式发的,而不是sendback
*
* TODO 需要看下这里的RETRY具体工作流程,先贴一个topics.json:
*
* "%RETRY%benchmark_consumer":{
"perm":6,
"readQueueNums":1,
"topicFilterType":"SINGLE_TAG",
"topicName":"%RETRY%benchmark_consumer",
"writeQueueNums":1
},
*/
if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
/**
* chen.si 同是创建topic,但是这里是因为 消息消费失败而引起的sendback,只有1个read/write队列
*/
topicConfig =
this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(
requestHeader.getTopic(), 1, PermName.PERM_WRITE | PermName.PERM_READ);
}
}
if (null == topicConfig) {
response.setCode(MQResponseCode.TOPIC_NOT_EXIST_VALUE);
response.setRemark("topic[" + requestHeader.getTopic() + "] not exist, apply first please!"
+ FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
return response;
}
}
// 检查topic权限
/**
* chen.si 比如:
* "TBW102":{
"perm":7,
"readQueueNums":8,
"topicFilterType":"SINGLE_TAG",
"topicName":"TBW102",
"writeQueueNums":8
其中perm 对于 READ=4 WRITE=2 INHERIT=1
*/
if (!PermName.isWriteable(topicConfig.getPerm())) {
response.setCode(MQResponseCode.NO_PERMISSION_VALUE);
response.setRemark("the topic[" + requestHeader.getTopic() + "] sending message is forbidden");
return response;
}
// 检查队列有效性
/**
* chen.si 队列,也就是消息的逻辑分区
*/
int queueIdInt = requestHeader.getQueueId();
if (queueIdInt >= topicConfig.getWriteQueueNums()) {
String errorInfo =
"queueId[" + queueIdInt + "] is illagal, topicConfig.writeQueueNums: "
+ topicConfig.getWriteQueueNums() + " producer: " + ctx.channel().remoteAddress();
log.warn(errorInfo);
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark(errorInfo);
return response;
}
// 随机指定一个队列
if (queueIdInt < 0) {
/**
* chen.si 设置queueId为 负数,则随机选择 分区 进行存储
*/
queueIdInt = Math.abs(this.random.nextInt() % 99999999) % topicConfig.getWriteQueueNums();
}
/**
* chen.si 对于多tag,增加一个flag
*/
int sysFlag = requestHeader.getSysFlag();
// 多标签过滤需要置位
if (TopicFilterType.MULTI_TAG == topicConfig.getTopicFilterType()) {
sysFlag |= MessageSysFlag.MultiTagsFlag;
}
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(requestHeader.getTopic());
msgInner.setBody(body);
msgInner.setFlag(requestHeader.getFlag());
msgInner.setProperties(MessageDecoder.string2messageProperties(requestHeader.getProperties()));
msgInner.setPropertiesString(requestHeader.getProperties());
msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(topicConfig.getTopicFilterType(),
msgInner.getTags()));
msgInner.setQueueId(queueIdInt);
msgInner.setSysFlag(sysFlag);
msgInner.setBornTimestamp(requestHeader.getBornTimestamp());
msgInner.setBornHost(ctx.channel().remoteAddress());
msgInner.setStoreHost(this.getStoreHost());
/**
* chen.si TODO 了解下流程
*/
msgInner.setReconsumeTimes(0);
/**
* chen.si 配置是否支持 事务消息,默认是支持
*/
// 检查事务消息
if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
String traFlag = msgInner.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (traFlag != null) {
response.setCode(MQResponseCode.NO_PERMISSION_VALUE);
response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
+ "] sending transaction message is forbidden");
return response;
}
}
PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
SendmsgLiveMoniter.printProcessSendmsgRequestLive(ctx.channel(), request, putMessageResult);
if (putMessageResult != null) {
boolean sendOK = false;
switch (putMessageResult.getPutMessageStatus()) {
// Success
case PUT_OK:
sendOK = true;
response.setCode(ResponseCode.SUCCESS_VALUE);
break;
case FLUSH_DISK_TIMEOUT:
response.setCode(MQResponseCode.FLUSH_DISK_TIMEOUT_VALUE);
sendOK = true;
break;
case FLUSH_SLAVE_TIMEOUT:
response.setCode(MQResponseCode.FLUSH_SLAVE_TIMEOUT_VALUE);
sendOK = true;
break;
case SLAVE_NOT_AVAILABLE:
response.setCode(MQResponseCode.SLAVE_NOT_AVAILABLE_VALUE);
sendOK = true;
break;
// Failed
case CREATE_MAPEDFILE_FAILED:
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("create maped file failed.");
break;
case MESSAGE_ILLEGAL:
response.setCode(MQResponseCode.MESSAGE_ILLEGAL_VALUE);
response.setRemark("the message is illegal, maybe length not matched.");
break;
case SERVICE_NOT_AVAILABLE:
response.setCode(MQResponseCode.SERVICE_NOT_AVAILABLE_VALUE);
response.setRemark("service not available now, maybe disk full, " + diskUtil());
break;
case UNKNOWN_ERROR:
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("UNKNOWN_ERROR");
break;
default:
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("UNKNOWN_ERROR DEFAULT");
break;
}
if (sendOK) {
response.setRemark(null);
responseHeader.setMsgId(putMessageResult.getAppendMessageResult().getMsgId());
responseHeader.setQueueId(queueIdInt);
responseHeader.setQueueOffset(putMessageResult.getAppendMessageResult().getLogicsOffset());
/**
* chen.si oneway模式,不需要返回响应;request-response模式,需要
*/
// 直接返回
if (!request.isOnewayRPC()) {
try {
ctx.writeAndFlush(response).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
log.error("SendMessageProcessor response to "
+ future.channel().remoteAddress() + " failed", future.cause());
log.error(request.toString());
log.error(response.toString());
}
}
});
}
catch (Throwable e) {
log.error("SendMessageProcessor process request over, but response failed", e);
log.error(request.toString());
log.error(response.toString());
}
}
/**
* chen.si 可能存在 被hang的 pull操作, 这里有新的消息,所以需要通知 pull操作
*/
this.brokerController.getPullRequestHoldService().notifyMessageArriving(
requestHeader.getTopic(), queueIdInt,
putMessageResult.getAppendMessageResult().getLogicsOffset());
return null;
}
}
else {
response.setCode(ResponseCode.SYSTEM_ERROR_VALUE);
response.setRemark("store putMessage return null");
}
return response;
}
public SocketAddress getStoreHost() {
return storeHost;
}
}