RocketMQ Remoting模块也是整个代码中比较简单的一个模块,在掌握基本的Netty知识之后就可以尝试对源码进行简单的阅读分析,我也是结合源码分析来进行Netty应用的学习。
该模块主要有两个类 NettyRemotingServer 和 NettyRemotingClient 。分别对应服务端和客户端,其中服务端主要在Broker和NameService中使用。
Push中存在的Pull请求是通过“长轮询”方式达到Push 效果的方法,长轮询方式既有 Pull 的优点,又兼 Push 方式的实时性。“长轮询”方式通过 Client 端和 Server 端的配合,达到既拥有 Pull 的优点,又能达到保证实时性的目的。
Push 方式 Server 端接收到消息后,主动把消息推送给 Client 端,实时性高 对于 个提供队列服务的 Server 来说,用 Push 方式主动推送有很多弊端:首先是加大 Server 端的 作量,进而影响 Server 的性能;其次, Client处理能力各不相同, Client 的状态不受 Server 控制,如果 Client 不能及时处理Server 推送过来的消息,会造成各种潜在问题。
Pull 方式是 Client 端循环地从 Server 端拉取消息,主动权在 Client 手里,自己拉取到一定量消息后,处理妥当了再接着取Pull 方式的问题是循环拉取消息的间隔不好设定,间隔太短就处在一个 忙等”的状态,浪费资源;每个Pull 的时间间隔太长 Server 端有消息到来时 有可能没有被及时处理。
@Override
public void run() {
while (!this.isStopped()) {
if (this.brokerController.getBrokerConfig().isLongPollingEnable()) {
this.waitForRunning(5 * 1000);
} else {
this.waitForRunning(this.brokerController.getBrokerConfig().getShortPollingTimeMills());
}
long beginLockTimestamp = this.systemClock.now();
this.checkHoldRequest();
long costTime = this.systemClock.now() - beginLockTimestamp;
if (costTime > 5 * 1000) {
log.warn("PullRequestHoldService: check hold pull request cost {}ms", costTime);
}
}
}
protected void checkHoldRequest() {
for (String key : this.pullRequestTable.keySet()) {
String[] kArray = key.split(TOPIC_QUEUEID_SEPARATOR);
if (2 == kArray.length) {
String topic = kArray[0];
int queueId = Integer.parseInt(kArray[1]);
final long offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId);
this.notifyMessageArriving(topic, queueId, offset);
}
}
}
从Broker 的源码【PullRequestHoldService#run】中可以看出,服务端接到新消息请求后, 如果队列里没有新消息并不急于返回,通过一个循环不断查看状态,每次 waitForRunning一段时间 (默认是5秒)然后再 Check。默认情况下当 Broker 一直没有新消息, 第三次check 的时候, 等待时间超过 Request 里面的 BrokerSuspen MaxTimeMi11is 就返回空结果。在等待的过程中, Broker 收到了新的消息后会直接调用notifyMessageArriving 函数返回请求结果。“长轮询”的核心是, Broker HOLD 住客户端过来的请求一小段时间,在这个时间内有新消息到达,就利用现有的连接立刻返回消息给 Consumer。 “长轮询”的主动权还是掌握在 Consumer 手中, Broker 即使有大量消息积压,也不会主动推送给Consumer。
长轮询方式的局限性,是在HOLD住Consumer请求的时候需要占用资源,它适合用在消息队列这种客户端连接数可控的场景中。
1.NettyRemotingAbstract
public abstract class NettyRemotingAbstract {
public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) {
if (msg != null) {
switch (msg.getType()) {
case REQUEST_COMMAND:
processRequestCommand(ctx, msg);
break;
case RESPONSE_COMMAND:
processResponseCommand(ctx, msg);
break;
default:
break;
}
}
}
public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
//根据 requestCode 获取与当前请求匹配的 处理器processor
Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
Pair<NettyRequestProcessor, ExecutorService> pair =
null == matched ? this.defaultRequestProcessorPair : matched;
int opaque = cmd.getOpaque();
...
Runnable run = buildProcessRequestHandler(ctx, cmd, pair, opaque);
...
RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd);
pair.getObject2().submit(requestTask);
}
private Runnable buildProcessRequestHandler(ChannelHandlerContext ctx, RemotingCommand
cmd,Pair<NettyRequestProcessor, ExecutorService> pair, int opaque) {
return () -> {
Exception exception = null;
RemotingCommand response;
String remoteAddr = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
...// 执行处理器processor相关处理逻辑
response = pair.getObject1().processRequest(ctx, cmd);
...
writeResponse(ctx.channel(), cmd, response);
};
}
}
1.1.NettyRemotingServer
public class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) {
int localPort = RemotingHelper.parseSocketAddressPort(ctx.channel().localAddress());
NettyRemotingAbstract remotingAbstract = NettyRemotingServer.this.remotingServerTable.get(localPort);
if (localPort != -1 && remotingAbstract != null) {
remotingAbstract.processMessageReceived(ctx, msg);
return;
}
RemotingHelper.closeChannel(ctx.channel());
}
// 只有服务端初始化 requestCode & 处理器processor 之间的对应关系
@Override
public void registerProcessor(int requestCode, NettyRequestProcessor processor, ExecutorService executor) {
ExecutorService executorThis = executor;
if (null == executor) {
executorThis = this.publicExecutor;
}
Pair<NettyRequestProcessor, ExecutorService> pair = new Pair<>(processor, executorThis);
this.processorTable.put(requestCode, pair);
}
}
1.2.NettyRemotingClient
public class NettyRemotingClient extends NettyRemotingAbstract implements RemotingClient {
class NettyClientHandler extends SimpleChannelInboundHandler<RemotingCommand> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
processMessageReceived(ctx, msg);
}
}
}
1、NettyRemotingClient
NettyRemotingClient模块是应用方接触最多的核心通信模块类。该类主要负责客户端与服务端之间数据的通信,即服务端读取的队列的数据都是通过NettyRemotingClient通知到应用方消费监听器。
在RocketMQ RPC通信是通过Netty实现的,在初始化Bootstrap中众多handler中其中NettyClientHandler是最后的一个handler,同时也是Netty框架与应用框架间数据流转的最后一站。
2、NettyClientHandler
每一条待消费的数据都是通过该Handler投递给消费者实例的。对于顺序消息来说发送时已经获悉需要发送的队列,而在broker端也知道该队列对应的消费者实例信息,从而保证broker端精准投递消息至特定的消费者实例。
class NettyClientHandler extends SimpleChannelInboundHandler<RemotingCommand> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
processMessageReceived(ctx, msg);//msg就是服务端响应的数据
}
}
public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
final RemotingCommand cmd = msg;
if (cmd != null) {
switch (cmd.getType()) {
case REQUEST_COMMAND:
processRequestCommand(ctx, cmd);
break;
case RESPONSE_COMMAND:
processResponseCommand(ctx, cmd);
break;
default:
break;
}
}
}
public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cmd) {
final int opaque = cmd.getOpaque();
final ResponseFuture responseFuture = responseTable.get(opaque);//#2
if (responseFuture != null) {
responseFuture.setResponseCommand(cmd);//#3
responseTable.remove(opaque);
if (responseFuture.getInvokeCallback() != null) {
executeInvokeCallback(responseFuture);//#1
}
}
}
- 步骤1调用NettyRemotingClient抽象父类NettyRemotingAbstract#executeInvokeCallback方法。
- 步骤2中的responseTable在生产者发送时会初始化完毕,并且设置key为opaque,value为ResponseFuture。
- 将在broker上获取的待消费数据赋值给ResponseFuture。用于消费实例继续消费。
4、ResponseFuture
public class ResponseFuture {
private final int opaque;
private final Channel processChannel;
private final long timeoutMillis;
private final InvokeCallback invokeCallback;
private final SemaphoreReleaseOnlyOnce once;
private volatile RemotingCommand responseCommand;// 存在消息体body
public ResponseFuture(Channel channel, int opaque, long timeoutMillis, InvokeCallback invokeCallback,
SemaphoreReleaseOnlyOnce once) {
this.opaque = opaque;
this.processChannel = channel;
this.timeoutMillis = timeoutMillis;
this.invokeCallback = invokeCallback;
this.once = once;
}
public void executeInvokeCallback() {
if (invokeCallback != null) {
if (this.executeCallbackOnlyOnce.compareAndSet(false, true)) {
invokeCallback.operationComplete(this);//#1
}
}
}
}
- 步骤1中的invokeCallback在生产者发送消息时赋值给ResponseFuture。
5、MQClientAPIImpl
private void pullMessageAsync(String addr,RemotingCommand request,long timeoutMillis,PullCallback pullCallback) {
this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
@Override
public void operationComplete(ResponseFuture responseFuture) {
RemotingCommand response = responseFuture.getResponseCommand();//#1
if (response != null) {
try {
PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr);
assert pullResult != null;
pullCallback.onSuccess(pullResult);//DefaultMQPushConsumerImpl#PullCallback#onSuccess
}
}
});
}
- 步骤1中从ResponseFuture中获取待消费的数据。
6、DefaultMQPushConsumerImpl#PullCallback
PullCallback pullCallback = new PullCallback() {
@Override
public void onSuccess(PullResult pullResult) {
if (pullResult != null) {
pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,subscriptionData);
switch (pullResult.getPullStatus()) {
case FOUND:
boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList());//#1
DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(//#2
pullResult.getMsgFoundList(),processQueue,pullRequest.getMessageQueue(),dispatchToConsume);
break;
case NO_NEW_MSG:
case NO_MATCHED_MSG:
...
case OFFSET_ILLEGAL:
...
default:
break;
}
}
}
};
- 步骤1:将需要消费的数据存储到processQueue#msgTreeMap中。
- 步骤2:触发消费线程池执行。
7、ConsumeMessageOrderlyService
public class ConsumeMessageOrderlyService implements ConsumeMessageService {
private final ThreadPoolExecutor consumeExecutor;
public ConsumeMessageOrderlyService(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl,
MessageListenerOrderly messageListener) {
this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl;
this.messageListener = messageListener;
this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer();
this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup();
this.consumeRequestQueue = new LinkedBlockingQueue<Runnable>();
this.consumeExecutor = new ThreadPoolExecutor(
this.defaultMQPushConsumer.getConsumeThreadMin(),
this.defaultMQPushConsumer.getConsumeThreadMax(),
1000 * 60,
TimeUnit.MILLISECONDS,
this.consumeRequestQueue,
new ThreadFactoryImpl("ConsumeMessageThread_"));
this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_"));
}
public void submitConsumeRequest(//#3
final List<MessageExt> msgs,
final ProcessQueue processQueue,
final MessageQueue messageQueue,
final boolean dispathToConsume) {
if (dispathToConsume) {
ConsumeRequest consumeRequest = new ConsumeRequest(processQueue, messageQueue);
this.consumeExecutor.submit(consumeRequest);
}
}
class ConsumeRequest implements Runnable {
private final ProcessQueue processQueue;
private final MessageQueue messageQueue;
public ConsumeRequest(ProcessQueue processQueue, MessageQueue messageQueue) {
this.processQueue = processQueue;
this.messageQueue = messageQueue;
}
...
@Override
public void run() {
final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue);//#3
synchronized (objLock) {
if (MessageModel.BROADCASTING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
|| (this.processQueue.isLocked() && !this.processQueue.isLockExpired())) {
...
final beginTime = System.currentTimeMillis();
for (boolean continueConsume = true; continueConsume; ) {
...
List<MessageExt> msgs = this.processQueue.takeMessages(consumeBatchSize);//#1
...
status = messageListener.consumeMessage(Collections.unmodifiableList(msgs), context);//#2
continueConsume = ConsumeMessageOrderlyService.this.processConsumeResult(msgs, status, context,
this);
}
}
}
}
}
}
consumeExecutor:消息消费的线程池。多线程处理不同messageQueue是不需要控制并发,同一个messageQueue多线程消费需要严格控制消费顺序。
processQueue:msgTreeMap(存放拉取到的消息)、queueOffsetMax(消费到的最大offset)、msgSize(ProcessQueue中的总消息长度)、msgCount(ProcessQueue中的总消息数量)、msgAccCnt(表示broker端还有多少消息未被消费)。
TreeMap类型的msgTreeMap集合保证同一个messageQueue中多条消息是按照QueueOffset进行排序。
processQueue中msgTreeMap集合的读写是分开的,即存在不同线程对其进行读写。ReentrantReadWriteLock类型的lockTreeMap保证一个messageQueue某一时刻只能允许一个线程操作msgTreeMap。
for(boolean continueConsume = true;continueConsume)这个循环很有意思,表示当前MessageQueue是否可以持续消费,是否可以继续消费的标准有两个:
- consumeOffset的提交,如果提交成功,就可以继续消费,直到processQueue中msgTreeMap为空。
- consumeOffset没有正常提交.比如其它状态:SUSPEND_CURRENT_QUEUE_A_MOMENT。
如果返回到是SUSPEND_CURRENT_QUEUE_A_MOMENT,这个里面会把本次消费的消息重新放回到ProcessQueue中的msgTreeMap然后由下个线程接着消费,为什么说是下个线程呢? 因为这里会把continueConsume设置为false,返回时,执行当前ConsumeRequest进行消费的线程就会完成消费,下一个获取当前MessageQueue的锁的线程接着处理当前的processQueue(不同MessageQueue对应的processQueue永远是同一个对象)。
- 步骤1:从processQueue获取待消费的数据。
- 步骤2:回调消费者实例监听器消费队列中的数据。
- 步骤3:不同消费线程针对同一个messageQueue中的消息处理是通过锁控制消费顺序的。
7.1、消费状态
public boolean processConsumeResult(
final List<MessageExt> msgs,
final ConsumeOrderlyStatus status,
final ConsumeOrderlyContext context,
final ConsumeRequest consumeRequest
) {
boolean continueConsume = true;
long commitOffset = -1L;
if (context.isAutoCommit()) {
switch (status) {
case COMMIT:
case ROLLBACK:
case SUCCESS:
commitOffset = consumeRequest.getProcessQueue().commit();
this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size());
break;
case SUSPEND_CURRENT_QUEUE_A_MOMENT:
this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup,
consumeRequest.getMessageQueue().getTopic(), msgs.size());
if (checkReconsumeTimes(msgs)) {//重试消费次数判断
consumeRequest.getProcessQueue().makeMessageToConsumeAgain(msgs);
this.submitConsumeRequestLater(consumeRequest.getProcessQueue(),consumeRequest.getMessageQueue(),
context.getSuspendCurrentQueueTimeMillis());
continueConsume = false;
} else {
commitOffset = consumeRequest.getProcessQueue().commit();
}
break;
default:
break;
}
}
...
return continueConsume;
}
如果消息是自动提交:对于SUSPEND_CURRENT_QUEUE_A_MOMENT返回状态,如果没有达到重试次数则重新将当前ConsumeRequest放入到第六章阐述的任务队列中。
makeMessageToConsumeAgain:重新将当前消息放到TreeMap中,根据TreeMap特性保证重试消费也能保证顺序消费。
private void submitConsumeRequestLater(ProcessQueue processQueue,MessageQueue messageQueue,long suspendTimeMillis) {
long timeMillis = suspendTimeMillis;//默认为-1
if (timeMillis == -1) {
timeMillis = this.defaultMQPushConsumer.getSuspendCurrentQueueTimeMillis();//默认为1000
}
if (timeMillis < 10) {
timeMillis = 10;
} else if (timeMillis > 30000) {
timeMillis = 30000;
}
this.scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
ConsumeMessageOrderlyService.this.submitConsumeRequest(null, processQueue, messageQueue, true);
}
}, timeMillis, TimeUnit.MILLISECONDS);
}