/** * 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.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.FileRegion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.rocketmq.broker.BrokerController; import com.alibaba.rocketmq.broker.client.ConsumerGroupInfo; import com.alibaba.rocketmq.broker.digestlog.PullmsgLiveMoniter; import com.alibaba.rocketmq.broker.longpolling.PullRequest; import com.alibaba.rocketmq.broker.pagecache.ManyMessageTransfer; import com.alibaba.rocketmq.common.TopicConfig; import com.alibaba.rocketmq.common.constant.LoggerName; import com.alibaba.rocketmq.common.constant.PermName; import com.alibaba.rocketmq.common.filter.FilterAPI; import com.alibaba.rocketmq.common.help.FAQUrl; import com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode; import com.alibaba.rocketmq.common.protocol.header.PullMessageRequestHeader; import com.alibaba.rocketmq.common.protocol.header.PullMessageResponseHeader; import com.alibaba.rocketmq.common.protocol.heartbeat.MessageModel; import com.alibaba.rocketmq.common.protocol.heartbeat.SubscriptionData; import com.alibaba.rocketmq.common.subscription.SubscriptionGroupConfig; import com.alibaba.rocketmq.common.sysflag.PullSysFlag; 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.GetMessageResult; import com.alibaba.rocketmq.store.config.BrokerRole; /** * 拉消息请求处理 * * @author shijia.wxr<vintage.wang@gmail.com> * @since 2013-7-26 */ public class PullMessageProcessor implements NettyRequestProcessor { private static final Logger log = LoggerFactory.getLogger(LoggerName.BrokerLoggerName); private final BrokerController brokerController; public PullMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; } @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { return this.processRequest(ctx.channel(), request, true); } public void excuteRequestWhenWakeup(final Channel channel, final RemotingCommand request) throws RemotingCommandException { Runnable run = new Runnable() { @Override public void run() { try { /* * chen.si 此次不会再挂起了,一定会返回 */ final RemotingCommand response = PullMessageProcessor.this.processRequest(channel, request, false); if (response != null) { response.setOpaque(request.getOpaque()); response.markResponseType(); try { channel.writeAndFlush(response).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { log.error("processRequestWrapper response to " + future.channel().remoteAddress() + " failed", future.cause()); log.error(request.toString()); log.error(response.toString()); } } }); } catch (Throwable e) { log.error("processRequestWrapper process request over, but response failed", e); log.error(request.toString()); log.error(response.toString()); } } } catch (RemotingCommandException e1) { log.error("excuteRequestWhenWakeup run", e1); } } }; /* * chen.si 避免阻塞 接收producer请求而存储消息 的线程。 扔到线程池中处理 */ this.brokerController.getPullMessageExecutor().submit(run); } private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException { /** * chen.si 收到消息拉取请求 */ RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class); final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.getCustomHeader(); final PullMessageRequestHeader requestHeader = (PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class); /** * chen.si TODO 需要看一下,这里是什么意思,哪里使用了sendfile system call */ // 由于使用sendfile,所以必须要设置 response.setOpaque(request.getOpaque()); if (log.isDebugEnabled()) { log.debug("receive PullMessage request command, " + request); } // 检查Broker权限 if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { response.setCode(MQResponseCode.NO_PERMISSION_VALUE); response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1() + "] pulling message is forbidden"); return response; } /** * chen.si consumer group相关的信息判断 * * 1. consumer group是否属于 配置的group列表 * 2. 如果consumer group不在配置列表中,则根据broker的配置autoCreateSubscriptionGroup=true是否自动创建 */ // 确保订阅组存在 SubscriptionGroupConfig subscriptionGroupConfig = this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig( requestHeader.getConsumerGroup()); if (null == subscriptionGroupConfig) { response.setCode(MQResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST_VALUE); response.setRemark("subscription group not exist, " + requestHeader.getConsumerGroup() + " " + FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST)); return response; } /** * chen.si consumer group是否允许消费 */ // 这个订阅组是否可以消费消息 if (!subscriptionGroupConfig.isConsumeEnable()) { response.setCode(MQResponseCode.NO_PERMISSION_VALUE); response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); return response; } final boolean hasSuspendFlag = PullSysFlag.hasSuspendFlag(requestHeader.getSysFlag()); final boolean hasCommitOffsetFlag = PullSysFlag.hasCommitOffsetFlag(requestHeader.getSysFlag()); final boolean hasSubscriptionFlag = PullSysFlag.hasSubscriptionFlag(requestHeader.getSysFlag()); /** * chen.si 获取无消息的hang时长 */ final long suspendTimeoutMillisLong = hasSuspendFlag ? requestHeader.getSuspendTimeoutMillis() : 0; // 检查topic是否存在 TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); if (null == topicConfig) { log.error("the topic " + requestHeader.getTopic() + " not exist, consumer: " + RemotingHelper.parseChannelRemoteAddr(channel)); 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权限 if (!PermName.isReadable(topicConfig.getPerm())) { response.setCode(MQResponseCode.NO_PERMISSION_VALUE); response.setRemark("the topic[" + requestHeader.getTopic() + "] pulling message is forbidden"); return response; } // 检查队列有效性 if (requestHeader.getQueueId() < 0 || requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) { String errorInfo = "queueId[" + requestHeader.getQueueId() + "] is illagal,Topic :" + requestHeader.getTopic() + " topicConfig.readQueueNums: " + topicConfig.getReadQueueNums() + " consumer: " + channel.remoteAddress(); log.warn(errorInfo); response.setCode(ResponseCode.SYSTEM_ERROR_VALUE); response.setRemark(errorInfo); return response; } // 订阅关系处理 SubscriptionData subscriptionData = null; /** * chen.si 需要处理消息tag过滤 */ if (hasSubscriptionFlag) { try { /** * chen.si consumer拉取消息,可以指定: * 1. topic 只获取指定topic的消息 * 2. tag list 只获取具有指定tag的消息 */ subscriptionData = FilterAPI.buildSubscriptionData(requestHeader.getTopic(), requestHeader.getSubscription()); } catch (Exception e) { log.warn("parse the consumer's subscription[{}] failed, group: {}", requestHeader.getSubscription(),// requestHeader.getConsumerGroup()); response.setCode(MQResponseCode.SUBSCRIPTION_PARSE_FAILED_VALUE); response.setRemark("parse the consumer's subscription failed"); return response; } } else { ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo( requestHeader.getConsumerGroup()); if (null == consumerGroupInfo) { log.warn("the consumer's group info not exist, group: {}", requestHeader.getConsumerGroup()); response.setCode(MQResponseCode.SUBSCRIPTION_NOT_EXIST_VALUE); response.setRemark("the consumer's group info not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC)); return response; } if (!subscriptionGroupConfig.isConsumeBroadcastEnable() // && consumerGroupInfo.getMessageModel() == MessageModel.BROADCASTING) { response.setCode(MQResponseCode.NO_PERMISSION_VALUE); response.setRemark("the consumer group[" + requestHeader.getConsumerGroup() + "] can not consume by broadcast way"); return response; } subscriptionData = consumerGroupInfo.findSubscriptionData(requestHeader.getTopic()); if (null == subscriptionData) { log.warn("the consumer's subscription not exist, group: {}", requestHeader.getConsumerGroup()); response.setCode(MQResponseCode.SUBSCRIPTION_NOT_EXIST_VALUE); response.setRemark("the consumer's subscription not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC)); return response; } // 判断Broker的订阅关系版本是否最新 if (subscriptionData.getSubVersion() < requestHeader.getSubVersion()) { log.warn("the broker's subscription is not latest, group: {} {}", requestHeader.getConsumerGroup(), subscriptionData.getSubString()); response.setCode(MQResponseCode.SUBSCRIPTION_NOT_LATEST_VALUE); response.setRemark("the consumer's subscription not latest"); return response; } } final GetMessageResult getMessageResult = this.brokerController.getMessageStore().getMessage(requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), subscriptionData); if (getMessageResult != null) { if (getMessageResult.getBufferTotalSize() > 0) { PullmsgLiveMoniter.printProcessRequestLive(channel, request, getMessageResult); } response.setRemark(getMessageResult.getStatus().name()); responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset()); responseHeader.setMinOffset(getMessageResult.getMinOffset()); responseHeader.setMaxOffset(getMessageResult.getMaxOffset()); // 消费较慢,重定向到另外一台机器 if (getMessageResult.isSuggestPullingFromSlave()) { responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig .getWhichBrokerWhenConsumeSlowly()); } // 消费正常,按照订阅组配置重定向 else { responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId()); } switch (getMessageResult.getStatus()) { case FOUND: response.setCode(ResponseCode.SUCCESS_VALUE); break; case MESSAGE_WAS_REMOVING: response.setCode(MQResponseCode.PULL_RETRY_IMMEDIATELY_VALUE); break; // 这两个返回值都表示服务器暂时没有这个队列,应该立刻将客户端Offset重置为0 case NO_MATCHED_LOGIC_QUEUE: case NO_MESSAGE_IN_QUEUE: if (0 != requestHeader.getQueueOffset()) { response.setCode(MQResponseCode.PULL_OFFSET_MOVED_VALUE); log.info( "the broker store no queue data, fix the request offset {} to {}, Topic: {} QueueId: {} Consumer Group: {}",// requestHeader.getQueueOffset(), // getMessageResult.getNextBeginOffset(), // requestHeader.getTopic(),// requestHeader.getQueueId(),// requestHeader.getConsumerGroup()// ); } else { response.setCode(MQResponseCode.PULL_NOT_FOUND_VALUE); } break; case NO_MATCHED_MESSAGE: response.setCode(MQResponseCode.PULL_RETRY_IMMEDIATELY_VALUE); break; case OFFSET_FOUND_NULL: response.setCode(MQResponseCode.PULL_NOT_FOUND_VALUE); break; case OFFSET_OVERFLOW_BADLY: response.setCode(MQResponseCode.PULL_OFFSET_MOVED_VALUE); log.info("the request offset: " + requestHeader.getQueueOffset() + " over flow badly, broker max offset: " + getMessageResult.getMaxOffset() + ", consumer: " + channel.remoteAddress()); break; case OFFSET_OVERFLOW_ONE: response.setCode(MQResponseCode.PULL_NOT_FOUND_VALUE); break; case OFFSET_TOO_SMALL: response.setCode(MQResponseCode.PULL_OFFSET_MOVED_VALUE); log.info("the request offset: " + requestHeader.getQueueOffset() + " too small, broker min offset: " + getMessageResult.getMinOffset() + ", consumer: " + channel.remoteAddress()); break; default: assert false; break; } switch (response.getCode()) { case ResponseCode.SUCCESS_VALUE: try { FileRegion fileRegion = new ManyMessageTransfer(response.encodeHeader(getMessageResult .getBufferTotalSize()), getMessageResult); channel.writeAndFlush(fileRegion).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { getMessageResult.release(); if (!future.isSuccess()) { log.error( "transfer many message by pagecache failed, " + channel.remoteAddress(), future.cause()); } } }); } catch (Throwable e) { log.error("", e); getMessageResult.release(); } response = null; break; case MQResponseCode.PULL_NOT_FOUND_VALUE: // 长轮询 if (brokerAllowSuspend && hasSuspendFlag) { /* * chen.si 此处认为是长轮询,可以block一段时间 */ PullRequest pullRequest = new PullRequest(request, channel, suspendTimeoutMillisLong, this.brokerController .getMessageStore().now(), requestHeader.getQueueOffset()); this.brokerController.getPullRequestHoldService().suspendPullRequest( requestHeader.getTopic(), requestHeader.getQueueId(), pullRequest); response = null; break; } // 向Consumer返回应答 case MQResponseCode.PULL_RETRY_IMMEDIATELY_VALUE: case MQResponseCode.PULL_OFFSET_MOVED_VALUE: break; default: assert false; } } else { response.setCode(ResponseCode.SYSTEM_ERROR_VALUE); response.setRemark("store getMessage return null"); } // 存储Consumer消费进度 boolean storeOffsetEnable = brokerAllowSuspend; // 说明是首次调用,相对于长轮询通知 /* * chen.si 可以自动提交offset */ storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag; // 说明Consumer设置了标志位 storeOffsetEnable = storeOffsetEnable // 只有Master支持存储offset && this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE; if (storeOffsetEnable) { this.brokerController.getConsumerOffsetManager().commitOffset(requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset()); } return response; } }