Flink源码分析系列文档目录
请点击:Flink 源码分析系列文档目录
什么是credit反压
在数据发送端(CreditBasedSequenceNumberingViewReader
)维护了对应的数据接收端(RemoteInputChannel
)的credit信息,表示下游还可以接收credit个buffer的数据。每一次向下游发送buffer数据的时候(getNextBuffer
),credit减去buffer的数量。当credit值为0的时候,停止向下游发送数据。下游在有新的空闲内存的时候会通知上游有新的credit可用(notifyCreditAvailable
)。上游接收到新增的credit数量之后,更新对应channel的credit数量,重新开始向下游发送数据。
下游AddCredit请求的发送过程
RemoteInputChannel
有新credit可用的时候需要告知上游去增加自己的credit数值。以上通过调用notifyCreditAvailable
方法完成。此通知方法在回收内存(recycle
),监听器发现有缓存可用的回调函数(notifyBufferAvailable
)和分配积压任务所需内存(onSenderBacklog
)时,新增加的buffer数大于0且unannouncedCredit
从0变为非0的时候调用(unannouncedCredit
为RemoteInputChannel
暂时还没有向上游reader报告的可用credit数量,下文有此变量的分析)。
通过跟踪我们发现最底层调用的是CreditBasedPartitionRequestClientHandler
的notifyCreditAvailable
。代码如下所示:
@Override
public void notifyCreditAvailable(final RemoteInputChannel inputChannel) {
ctx.executor().execute(() -> ctx.pipeline().fireUserEventTriggered(inputChannel));
}
这段代码发送了一个userEvent。
接下来
CreditBasedPartitionRequestClientHandler
的userEventTriggered
会得到响应,调用处理notifyCreditAvailable
逻辑。代码如下:
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof RemoteInputChannel) {
boolean triggerWrite = inputChannelsWithCredit.isEmpty();
// 加入inputChannelsWithCredit队列
inputChannelsWithCredit.add((RemoteInputChannel) msg);
// 如果入队之前队列为空,调用writeAndFlushNextMessageIfPossible方法
// 发送AddCredit请求到server端
if (triggerWrite) {
writeAndFlushNextMessageIfPossible(ctx.channel());
}
} else {
ctx.fireUserEventTriggered(msg);
}
}
我们跟踪到writeAndFlushNextMessageIfPossible
方法。发现如下内容:
private void writeAndFlushNextMessageIfPossible(Channel channel) {
if (channelError.get() != null || !channel.isWritable()) {
return;
}
while (true) {
// 从inputChannelsWithCredit获取一个InputChannel
RemoteInputChannel inputChannel = inputChannelsWithCredit.poll();
// The input channel may be null because of the write callbacks
// that are executed after each write.
if (inputChannel == null) {
return;
}
//It is no need to notify credit for the released channel.
// 如果channel没有被释放,构造一个AddCredit请求并发送
if (!inputChannel.isReleased()) {
AddCredit msg = new AddCredit(
inputChannel.getPartitionId(),
// 此处获取并清零RemoteInputChannel的unannouncedCredit
// unannouncedCredit为尚未向reader上报的可用credit数量
// 下文有详细分析
inputChannel.getAndResetUnannouncedCredit(),
inputChannel.getInputChannelId());
// Write and flush and wait until this is done before
// trying to continue with the next input channel.
channel.writeAndFlush(msg).addListener(writeListener);
return;
}
}
}
上游接收并处理AddCredit请求的过程
CreditBasedSequenceNumberingViewReader
在Netty的 Server端运行,负责维护下游和subpartitionView
的对应关系。每一次调用requestSubpartitionView
都会创建出一个CreditBasedSequenceNumberingViewReader
。
CreditBasedSequenceNumberingViewReader
增加credit的逻辑在PartitionRequestServerHandler
的channelRead0
方法中。消费端发送AddCredit消息之后会被此handler读取到,进行如下操作(无关代码已省略):
// ...
// 如果接收到的消息是AddCredit(增加credit)
else if (msgClazz == AddCredit.class) {
AddCredit request = (AddCredit) msg;
// 调用addCredit方法
outboundQueue.addCredit(request.receiverId, request.credit);
}
// ...
这里outboundQueue
为PartitionRequestQueue
。我们分析下它的addCredit
方法。代码如下:
void addCredit(InputChannelID receiverId, int credit) throws Exception {
if (fatalError) {
return;
}
// 根据receiverId获取reader
NetworkSequenceViewReader reader = allReaders.get(receiverId);
if (reader != null) {
// 调用reader的增加credit方法
reader.addCredit(credit);
// 可用reader入队
enqueueAvailableReader(reader);
} else {
throw new IllegalStateException("No reader for receiverId = " + receiverId + " exists.");
}
}
接下来跟踪到reader.addCredit(credit)
调用。我们看下CreditBasedSequenceNumberingViewReader
的addCredit
方法:
@Override
public void addCredit(int creditDeltas) {
numCreditsAvailable += creditDeltas;
}
CreditBasedSequenceNumberingViewReader
有一个numCreditsAvailable
变量,用于记录该reader可用的credit数量。
回到enqueueAvailableReader
方法。该方法将reader加入到可用reader队列中并发送此reader对应subpartitionView的buffer到下游。它的代码如下:
private void enqueueAvailableReader(final NetworkSequenceViewReader reader) throws Exception {
// 如果reader已注册为可用(调用过enqueueAvailableReader)或者reader本身不可用,直接返回。这两个方法后续分析
if (reader.isRegisteredAsAvailable() || !reader.isAvailable()) {
return;
}
// Queue an available reader for consumption. If the queue is empty,
// we try trigger the actual write. Otherwise this will be handled by
// the writeAndFlushNextMessageIfPossible calls.
// 如果availableReader队列在添加reader之前为空,需要触发数据发送操作
boolean triggerWrite = availableReaders.isEmpty();
// 注册此reader为可用reader
registerAvailableReader(reader);
if (triggerWrite) {
// 写入reader对应的subpartitionView到下游task
writeAndFlushNextMessageIfPossible(ctx.channel());
}
}
enqueueAvailableReader
的isAvailable
方法返回该reader的数据是否可以发送到下游。代码如下:
@Override
public boolean isAvailable() {
// BEWARE: this must be in sync with #isAvailable(BufferAndBacklog)!
// 如果可用credit大于0,并且subpartitionView也可用的时候返回true
if (numCreditsAvailable > 0) {
return subpartitionView.isAvailable();
}
else {
// 或者说subpartitionView将要读取的下一个buffer是event类型
// 意思是event类型的数据无视是否有可用credit,无条件发送给下游task
return subpartitionView.nextBufferIsEvent();
}
}
我们在看看registerAvailableReader
方法。
private void registerAvailableReader(NetworkSequenceViewReader reader) {
// 添加reader到availableReaders队列
availableReaders.add(reader);
// 设置reader已注册为可用的标记为true
reader.setRegisteredAsAvailable(true);
}
getNextBuffer
方法,numCreditsAvailable 减一之后判断是否还有可用的credit,如果没有则抛出IllegalStateException。代码如下:
@Override
public BufferAndAvailability getNextBuffer() throws IOException, InterruptedException {
BufferAndBacklog next = subpartitionView.getNextBuffer();
if (next != null) {
sequenceNumber++;
if (next.buffer().isBuffer() && --numCreditsAvailable < 0) {
throw new IllegalStateException("no credit available");
}
return new BufferAndAvailability(
next.buffer(), isAvailable(next), next.buffersInBacklog());
} else {
return null;
}
}
RemoteInputChannel的unannouncedCredit变量
unannouncedCredit
统计了RemoteInputChannel
暂时还没有向上游reader报告的可用credit数量。该统计字段在如下情况会增加:
- 调用recycle方法:内存缓存片段(MemorySegment)被回收的时候。
- 调用notifyBufferAvailable方法:从BufferPool请求Buffer失败会注册一个BufferListener。当Listener发现有Buffer恢复可用的时候会调用此方法。详细参见Flink 源码之节点间通信
RemoteInputChannel
还有一个获取并清零unannouncedCredit
的方法getAndResetUnannouncedCredit
。该方法的唯一调用在CreditBasedPartitionRequestClientHandler
类的writeAndFlushNextMessageIfPossible
方法。此方法在上面已经分析过,不再赘述。