17.1 准备阶段
官方示例
public class PullConsumerTest {
public static void main(String[] args) throws MQClientException {
// 初始化consumer,并设置consumer group name
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("please_rename_unique_group_name_5");
// 设置NameServer地址
consumer.setNamesrvAddr("127.0.0.1:9876");
// 启动Consumer
consumer.start();
try {
// 设置要主动拉取的 mq
MessageQueue mq = new MessageQueue();
mq.setQueueId(0);
mq.setTopic("TopicTest");
mq.setBrokerName("jinrongtong-MacBook-Pro.local");
long offset = 26;// 指定从哪个偏移值开始拉取
// 拉取消息
PullResult pullResult = consumer.pull(mq, "*", offset, 32);
if (pullResult.getPullStatus().equals(PullStatus.FOUND)) {// 拉取成功
System.out.printf("%s%n", pullResult.getMsgFoundList());
// 自己更新拉取偏移
consumer.updateConsumeOffset(mq, pullResult.getNextBeginOffset());
}
} catch (Exception e) {
e.printStackTrace();
}
consumer.shutdown();
}
}
话不多说,更多的介绍,可以直接看官网,这直接上源码,那还是从 consumer.start() 这一行代码开始。
DefaultMQPullConsumer.start
@Override
public void start() throws MQClientException {
// 通过命名空间处理一下消费者组
this.setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
// pull消费的实现类启动
this.defaultMQPullConsumerImpl.start();
}
DefaultMQPullConsumerImpl.start
public synchronized void start() throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST: // 启动时,看这块
// 先设置成失败的状态
this.serviceState = ServiceState.START_FAILED;
// 检查配置项
this.checkConfig();
// 复制订阅信息,这里所做的事就是将订阅信息从 DefaultMQPushConsumerImpl 类的map 复制到 RebalanceImpl 类的map(rebalanceImpl.getSubscriptionInner())中,因为最终做消息处理的是 RebalanceImpl去与服务端打交道
this.copySubscription();
// 集群模式,实例名改成对应的进程Id
if (this.defaultMQPullConsumer.getMessageModel() == MessageModel.CLUSTERING) {
this.defaultMQPullConsumer.changeInstanceNameToPID();
}
// 获取或创建MQClientInstance实例,这个类很重要,同时涵盖了生产者和消费者的操作,具体使用时会讲
this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQPullConsumer, this.rpcHook);
//设置消费者组名
this.rebalanceImpl.setConsumerGroup(this.defaultMQPullConsumer.getConsumerGroup());
//设置消费模式
this.rebalanceImpl.setMessageModel(this.defaultMQPullConsumer.getMessageModel());
//设置分配mq策略(决定消费哪个mq的内容)
this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPullConsumer.getAllocateMessageQueueStrategy());
//设置mq客户端实例对象
this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);
//创建实际拉取消息的对象PullAPIWrapper
this.pullAPIWrapper = new PullAPIWrapper(
mQClientFactory,
this.defaultMQPullConsumer.getConsumerGroup(), isUnitMode());
//注册消息过滤处理器列表
this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);
//先在本地缓存中获取offsetstore,如果没有就去远程broker获取,offsetstore其实就是存放消费者消费mq的偏移量,防止重复消费
if (this.defaultMQPullConsumer.getOffsetStore() != null) {
this.offsetStore = this.defaultMQPullConsumer.getOffsetStore();
} else {
switch (this.defaultMQPullConsumer.getMessageModel()) {
case BROADCASTING:// 广播模式,不做讲解
this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPullConsumer.getConsumerGroup());
break;
case CLUSTERING://集群模式
// 集群模式下,用RemoteBrokerOffsetStore对象来处理消费偏移
this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPullConsumer.getConsumerGroup());
break;
default:
break;
}
this.defaultMQPullConsumer.setOffsetStore(this.offsetStore);
}
// 加载本机偏移信息,集群模式用的是 RemoteBrokerOffsetStore 类对象,默认load没有实现内容,这是一个钩子函数,子类可自行实现
this.offsetStore.load();
//将消费实例对象注册到本地内存consumerTable(ConcurrentMap)中,一个消费者组在一个客户端内,只会有一个消费实例对象
boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPullConsumer.getConsumerGroup(), this);
if (!registerOK) {
//注册失败,恢复状态为创建,并向上抛出异常
this.serviceState = ServiceState.CREATE_JUST;
throw new MQClientException("The consumer group[" + this.defaultMQPullConsumer.getConsumerGroup()
+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
null);
}
//上面都是一些检查和数据准备工作,这一步才是真正启动服务的工作,启动内部实现,这个在`章节16.1`的 MQClientInstance.start 方法中有讲,可以回头去看看
mQClientFactory.start();
log.info("the consumer [{}] start OK", this.defaultMQPullConsumer.getConsumerGroup());
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The PullConsumer service state not OK, maybe started once, "
+ this.serviceState
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
null);
default:
break;
}
}
17.2 触发拉取消息动作
前面一顿操作后,对几个主要的服务(消息拉取、负载均衡等)做了启动,现在先顺着启动后的服务往下走,由于这一章主要讲PULL方式,所以要贯穿PULL消息从获取到完成的整条链路,也得先了解RebalanceService和PullMessageService两个服务。
PullMessageService.start()
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
// pullRequestQueue 是一个BlockingQueue,阻塞队列,这个任务就是从这个队列中取任务来执行,这里就存在一个问题,谁给队列中放入的任务。现在一头雾水,不妨再看看 RebalanceService
PullRequest pullRequest = this.pullRequestQueue.take();
// 执行消息拉取的任务,看`章节17.3`
this.pullMessage(pullRequest);
} catch (InterruptedException ignored) {
} catch (Exception e) {
log.error("Pull Message Service Run Method exception", e);
}
}
log.info(this.getServiceName() + " service end");
}
RebalanceService.start()
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
// 等候 waitInterval 时间后运行
this.waitForRunning(waitInterval);
// 负载均衡处理,继续往下看
this.mqClientFactory.doRebalance();
}
log.info(this.getServiceName() + " service end");
}
MQClientInstance.doRebalance
public void doRebalance() {
// consumerTable 存放的是消费者组对应的 MQClientInstance 实例,一个消费者组在一个客户端内,只会有一个消费实例对象
for (Map.Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
MQConsumerInner impl = entry.getValue();
if (impl != null) {
try {
// 这里直接看 DefaultMQPullConsumerImpl 的实现
impl.doRebalance();
} catch (Throwable e) {
log.error("doRebalance exception", e);
}
}
}
}
DefaultMQPullConsumerImpl.doRebalance
public void doRebalance() {
if (this.rebalanceImpl != null) {
// 这里又跳转到 RebalanceImpl 类实现了
this.rebalanceImpl.doRebalance(false);
}
}
RebalanceImpl.doRebalance
public void doRebalance(final boolean isOrder) {
//这是订阅数据,在前面示例中有一行代码:consumer.subscribe("TopicTest", "*"),订阅一个或多个topic,并指定tag过滤条件,这里指定*表示接收所有tag的消息
Map<String, SubscriptionData> subTable = this.getSubscriptionInner();
if (subTable != null) {
for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
final String topic = entry.getKey();
try {
// 按topic负载,其实就是决定当前客户端消费哪些队列
this.rebalanceByTopic(topic, isOrder);
} catch (Throwable e) {
if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
log.warn("rebalanceByTopic Exception", e);
}
}
}
}
this.truncateMessageQueueNotMyTopic();
}
RebalanceImpl.rebalanceByTopic
private void rebalanceByTopic(final String topic, final boolean isOrder) {
switch (messageModel) {
case BROADCASTING: { // 广播模式,忽略
Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
if (mqSet != null) {
boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);
if (changed) {
this.messageQueueChanged(topic, mqSet, mqSet);
log.info("messageQueueChanged {} {} {} {}",
consumerGroup,
topic,
mqSet,
mqSet);
}
} else {
log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
}
break;
}
case CLUSTERING: { // 集群模式
// topicSubscribeInfoTable 存放的是topic对应的消息队列集合,从Namesrv中获取
Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
// cidAll 存放的是topic对应的消息者集合,从Namesrv中获取
List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
if (null == mqSet) { // 消息队列肯定不能为空
if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
}
}
if (null == cidAll) { // 消费者也不能为空
log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic);
}
// 两者都不为空
if (mqSet != null && cidAll != null) {
// 复制一份,不破坏原值
List<MessageQueue> mqAll = new ArrayList<MessageQueue>();
mqAll.addAll(mqSet);
// 排个序
Collections.sort(mqAll);
Collections.sort(cidAll);
// 队列分配策略,默认是平均分配
AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
List<MessageQueue> allocateResult = null;
try {
// 分配后,得到该消费者客户端可以消费的队列集合
allocateResult = strategy.allocate(
this.consumerGroup,
this.mQClientFactory.getClientId(),
mqAll,
cidAll);
} catch (Throwable e) {
log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(),
e);
return;
}
// 去重
Set<MessageQueue> allocateResultSet = new HashSet<MessageQueue>();
if (allocateResult != null) {
allocateResultSet.addAll(allocateResult);
}
/*
这个方法主要干了以下几件事:
1.创建ProcessQueue,并将其与MessageQueue关联起来形成mq-pq的k->v对存储在内存中
2.剔除无效的mq
3.剔除过时的mq
以上三种情况,任何一种成立,change都会为true
*/
boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
if (changed) {
log.info(
"rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),
allocateResultSet.size(), allocateResultSet);
// mq改变后,要更新本地缓存,同时发送心跳告知broker
this.messageQueueChanged(topic, mqSet, allocateResultSet);
}
}
break;
}
default:
break;
}
}
RebalanceImpl.updateProcessQueueTableInRebalance
private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet,
final boolean isOrder) {
boolean changed = false;
// 先遍历 processQueueTable 主要看看里面的mq是否在要用的mqSet里面,不在就从 processQueueTable 移除
Iterator<Entry<MessageQueue, ProcessQueue>> it = this.processQueueTable.entrySet().iterator();
while (it.hasNext()) {
Entry<MessageQueue, ProcessQueue> next = it.next();
MessageQueue mq = next.getKey();
ProcessQueue pq = next.getValue();
if (mq.getTopic().equals(topic)) {
if (!mqSet.contains(mq)) { // 不在
pq.setDropped(true);
// 移除
if (this.removeUnnecessaryMessageQueue(mq, pq)) {
it.remove();
changed = true;
log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq);
}
} else if (pq.isPullExpired()) { // 拉取过时,即:与上一次拉取时间超过2分钟
switch (this.consumeType()) {
case CONSUME_ACTIVELY: // PULL方式
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;
}
}
}
}
// 默认情况下 processQueueTable 是没有数据的,也就是说上面的那段不会执行。下面的逻辑其实就是新增
List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
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;
}
// 因为是创建,所以这里要先重置 mq 对应的消费偏移值
this.removeDirtyOffset(mq);
// 创建 pq 对象
ProcessQueue pq = new ProcessQueue();
// 计算从哪个偏移开始拉取,默认都是0
long nextOffset = this.computePullFromWhere(mq);
if (nextOffset >= 0) {
// 把 mq->pq的对应关系放到 processQueueTable map 中
ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
if (pre != null) { // map 中原来就有,证明拉取请求已经有了,不重复
log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
} else {
log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
// map 中没有,则新建一个拉取请求,这里到关键时候了,这里创建的就是拉取消息的请求,然后由拉取消息的服务去处理,由谁处理呢,继续看下面的 dispatchPullRequest 方法
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);
}
}
}
// 分发请求到push或pull,这里看pull方式
this.dispatchPullRequest(pullRequestList);
return changed;
}
RebalancePullImpl.dispatchPullRequest
从源码看,pull方式的dispatchPullRequest实现为空,这就证明走不下去了,那这条路就不通了,接下来怎么办呢?还是得从pull的概念来推导,pull意味着主动拉取,那既然是主动,肯定就不能靠几个服务启动后就解决了,必须得有一个用户发起的主动操作,回想一下,示例中有一行代码:
PullResult pullResult = consumer.pull(mq, “*”, offset, 32);
一看就明白,这个就是用户主动拉取的操作,接下来就是讲解这个动作的源码了。
public void dispatchPullRequest(List<PullRequest> pullRequestList) {
}
17.3 消息拉取
DefaultMQPullConsumer 有8个pull方法,供用户使用。
/**
* mq 指定要从哪个cq拉取
* subExpression 指定消息过滤tags
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
*/
public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums);
}
/**
* mq 指定要从哪个cq拉取
* subExpression 指定消息过滤tags
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
* timeout 拉取超时
*/
@Override
public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums, long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, timeout);
}
/**
* mq 指定要从哪个cq拉取
* messageSelector 指定消息选择器:tags或sql
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
*/
@Override
public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums);
}
/**
* mq 指定要从哪个cq拉取
* messageSelector 指定消息选择器:tags或sql
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
* timeout 拉取超时
*/
@Override
public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, timeout);
}
/**
* mq 指定要从哪个cq拉取
* messageSelector 指定消息选择器:tags或sql
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
* pullCallback 拉取回调
*/
@Override
public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback)
throws MQClientException, RemotingException, InterruptedException {
this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, pullCallback);
}
/**
* mq 指定要从哪个cq拉取
* subExpression 指定消息过滤tags
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
* pullCallback 拉取回调
* timeout 拉取超时
*/
@Override
public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback,
long timeout)
throws MQClientException, RemotingException, InterruptedException {
this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, pullCallback, timeout);
}
/**
* mq 指定要从哪个cq拉取
* messageSelector 指定消息选择器:tags或sql
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
* pullCallback 拉取回调
*/
@Override
public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums,
PullCallback pullCallback)
throws MQClientException, RemotingException, InterruptedException {
this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, pullCallback);
}
/**
* mq 指定要从哪个cq拉取
* messageSelector 指定消息选择器:tags或sql
* offset 拉取起始偏移
* maxNums 拉取的最大消息数
* pullCallback 拉取回调
* timeout 拉取超时
*/
@Override
public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums,
PullCallback pullCallback, long timeout)
throws MQClientException, RemotingException, InterruptedException {
this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, pullCallback, timeout);
}
这么多pull方法,就不一一介绍了,选择一个最简单的,也就是第一个pull吧,继续往里探。
DefaultMQPullConsumerImpl.pull
public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
// 增加一个默认10秒的拉取超时参数
return pull(mq, subExpression, offset, maxNums, this.defaultMQPullConsumer.getConsumerPullTimeoutMillis());
}
public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums, long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
// 将topic、tags等封装成subscriptionData对象,供后续环节使用
SubscriptionData subscriptionData = getSubscriptionData(mq, subExpression);
// 继续调用 pullSyncImpl
return this.pullSyncImpl(mq, subscriptionData, offset, maxNums, false, timeout);
}
DefaultMQPullConsumerImpl.pullSyncImpl
private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block,
long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
this.makeSureStateOK();
if (null == mq) { // mq 不能为空
throw new MQClientException("mq is null", null);
}
if (offset < 0) { // offset肯定要大于等于0
throw new MQClientException("offset < 0", null);
}
if (maxNums <= 0) {// maxNums肯定要大于0
throw new MQClientException("maxNums <= 0", null);
}
// 自动订阅当前topic消息,此时tags是*,表示不做过滤
this.subscriptionAutomatically(mq.getTopic());
int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);
long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;
boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
// 真正拉取的核心方法,这个方法在讲PUSH方式的时候已经写过了,大家可以直接回到`章节16.4`
PullResult pullResult = this.pullAPIWrapper.pullKernelImpl(
mq,
subscriptionData.getSubString(),// tags值
subscriptionData.getExpressionType(), // 对应类型,默认TAG,这里也只讲TAG
isTagType ? 0L : subscriptionData.getSubVersion(),// 判断是否TAG类型
offset, // 拉取起始偏移
maxNums, // 拉取最大数
sysFlag,
0,
// broker无响应最大超时,默认20s
this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
timeoutMillis, // 拉取超时
CommunicationMode.SYNC, // 同步拉取
null
);
// 拉取完成,处理拉取结果,这个在讲PUSH的时候也讲过,直接回头看`章节16.5`
this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData);
// 重置 topic 为无 namespace
this.resetTopic(pullResult.getMsgFoundList());
// 执行自定义hook
if (!this.consumeMessageHookList.isEmpty()) {
ConsumeMessageContext consumeMessageContext = null;
consumeMessageContext = new ConsumeMessageContext();
consumeMessageContext.setNamespace(defaultMQPullConsumer.getNamespace());
consumeMessageContext.setConsumerGroup(this.groupName());
consumeMessageContext.setMq(mq);
consumeMessageContext.setMsgList(pullResult.getMsgFoundList());
consumeMessageContext.setSuccess(false);
this.executeHookBefore(consumeMessageContext);
consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString());
consumeMessageContext.setSuccess(true);
this.executeHookAfter(consumeMessageContext);
}
return pullResult;
}
整个拉取操作与PUSH方式的章节16.4
中大量重叠,但是在方法 MQClientAPIImpl.pullMessage 中分道扬镳。
MQClientAPIImpl.pullMessage
public PullResult pullMessage(
final String addr,
final PullMessageRequestHeader requestHeader,
final long timeoutMillis,
final CommunicationMode communicationMode,
final PullCallback pullCallback
) throws RemotingException, MQBrokerException, InterruptedException {
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);
switch (communicationMode) {
case ONEWAY: // 单向发送
assert false;
return null;
case ASYNC: // 异步拉取,主要用在PUSH方式,之前已经讲过,此处忽略
this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
return null;
case SYNC: // 同步拉取,现在需要讲的
return this.pullMessageSync(addr, request, timeoutMillis);
default:
assert false;
break;
}
return null;
}
MQClientAPIImpl.pullMessageSync
private PullResult pullMessageSync(
final String addr,
final RemotingCommand request,
final long timeoutMillis
) throws RemotingException, InterruptedException, MQBrokerException {
// 调用同步拉取方法
RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
assert response != null;
// 处理拉取结果,这个方法内部很简单,就不单独拎出来写了
return this.processPullResponse(response);
}
NettyRemotingClient.invokeSync
public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
// 记录开始时间
long beginStartTime = System.currentTimeMillis();
// netty Channel
final Channel channel = this.getAndCreateChannel(addr);
if (channel != null && channel.isActive()) {
try {
// 调用前钩子,想像成拦截器,这是可以用户自定义扩展的
doBeforeRpcHooks(addr, request);
// 记录截止当前操作,已消耗时间
long costTime = System.currentTimeMillis() - beginStartTime;
// 当前已产生超时,抛出调用超时异常
if (timeoutMillis < costTime) {
throw new RemotingTimeoutException("invokeSync call timeout");
}
// 进一步调用同步方法
RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);
// 调用前钩子,想像成拦截器,这是可以用户自定义扩展的
doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
return response; // 返回结果
} catch (RemotingSendRequestException e) {
log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
this.closeChannel(addr, channel); // 产生异常,关闭channel资源
throw e;
} catch (RemotingTimeoutException e) {
if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
this.closeChannel(addr, channel);// 产生异常,关闭channel资源
log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
}
log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
throw e;
}
} else {
// channel状态非激活,关闭channel资源,并往上抛出异常
this.closeChannel(addr, channel);
throw new RemotingConnectException(addr);
}
}
NettyRemotingClient.invokeSyncImpl
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
final long timeoutMillis)
throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
// 标记唯一请求id
final int opaque = request.getOpaque();
try {
// 将响应处理封装成Future,并存放到 responseTable map 中,等待消息拉取成功后再处理
final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
this.responseTable.put(opaque, responseFuture);
final SocketAddress addr = channel.remoteAddress();
// 客户端channel(这里就是消费端),发送请求到服务端broker,并注册拉取后的监听
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
// operationComplete 方法由谁调用呢,先猜测一下,应该是由channel收到拉取的消息后,再调用,后续再源码验证
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {// 拉取成功,设置发送状态成功标志
responseFuture.setSendRequestOK(true);
return;
} else {
// 拉取成功,设置发送状态失败标志
responseFuture.setSendRequestOK(false);
}
// 因为是同步操作,所以处理完后,可以立马移除,这点跟push方式中的异步不一样
responseTable.remove(opaque);
// 设置失败原因
responseFuture.setCause(f.cause());
// 设置响应为null
responseFuture.putResponse(null);
log.warn("send a request command to channel <" + addr + "> failed.");
}
});
// 等待拉取任务的结果
RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
if (null == responseCommand) {// 超时/异常返回null
// 这里为啥要判断 isSendRequestOK,才能断定是超时呢,因为默认 isSendRequestOK 就是 true
if (responseFuture.isSendRequestOK()) {
throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
responseFuture.getCause());
} else {
throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
}
}
// 返回响应结果
return responseCommand;
} finally {
// 请求完成,从map中移除该请求
this.responseTable.remove(opaque);
}
}
至此,PULL方式,就讲完了,是不是很简单,实际上在讲PUSH时,已经把一些公共方法都讲完了。