0.信用机制
例如,对于一个 RemoteInputChannel
,其初始信用值由配置参数 taskmanager.network.credit-model.initial-credit
决定(默认值为 2),这意味着该通道在初始化时被分配了 2 个专用缓冲区,也就拥有了 2 的初始信用值。信用值会随着数据的接收和处理而动态变化,当接收端处理完数据并释放缓冲区时,信用值会增加;而当接收端接收数据占用缓冲区时,信用值会减少。

工作原理
- 数据发送端:数据发送端(生产者)在发送数据之前,会先检查目标通道(下游)的信用值。只有当信用值大于 0 时,发送端才会将数据发送到对应的通道上。,发送数据后,会根据发送的数据量相应地减少目标通道的信用值。
- 数据接收端:数据接收端(消费者)在处理完数据并释放缓冲区后,会向发送端(上游)发送信用通知,告知发送端可以增加相应的信用值。这样,发送端就可以根据新的信用值来决定是否继续发送数据以及发送多少数据。
- 实现反压:当接收端处理数据的速度跟不上发送端发送数据的速度时,接收端的缓冲区会逐渐被填满,信用值会逐渐降低。当信用值降为 0 时,发送端就会停止发送数据,从而实现反压,避免接收端缓冲区溢出。
- 资源动态分配:信用机制使得网络资源能够根据实际的数据流量情况进行动态分配。在数据流量不均衡的情况下,信用值可以在不同的通道之间动态调整,使得资源能够优先分配给那些需要处理更多数据的通道,提高了资源的利用率。
案例:假设初始时,接收端有 2 个专用缓冲区,每个专用缓冲区大小为 100M,初始信用值为 2,同时系统还配置了一定数量的浮动缓冲区。
- 第一次发送数据:发送端尝试发送 300M 的数据。由于专用缓冲区只有 2 个,共 200M 空间,所以先占用这 2 个专用缓冲区,发送 200M 数据,此时专用缓冲区的信用值降为 0,发送端暂停发送。
- 接收端处理数据与浮动缓冲区介入:接收端开始处理数据,在处理完 100M 数据后,释放了 1 个专用缓冲区,信用值变为 1。此时,如果系统中存在可用的浮动缓冲区,并且根据系统的分配策略,决定将 1 个大小为 100M 的浮动缓冲区分配给该通道,那么接收端就可以再接收 100M 的数据(一共200M)。发送端收到信用通知后,发送剩余的 100M 数据,这次发送既使用了释放的专用缓冲区,也使用了新分配的浮动缓冲区。
- 持续数据处理与信用值变化:随着数据的不断接收和处理,专用缓冲区和浮动缓冲区会不断地被占用和释放。如果接收端处理速度较快,能够及时释放专用缓冲区和浮动缓冲区,信用值就会不断增加,发送端就可以持续发送数据。例如,接收端又处理完 200M 数据(包括之前占用的专用缓冲区和浮动缓冲区中的数据),释放了 2 个缓冲区(1 个专用缓冲区和 1 个浮动缓冲区),信用值变为 2 + 1 = 3(初始专用缓冲区信用值 2 加上新释放的浮动缓冲区对应的信用值 1)。此时发送端可以根据信用值发送 300M 数据,若发送的数据量超过了专用缓冲区和当前可用浮动缓冲区的总和,就会再次触发反压,等待接收端释放更多缓冲区以增加信用值。
1.监控信用值--CreditBasedInputBuffersUsageGauge
该类是反压监控体系的一部分,其计算结果会被用于:
-
反压触发判断 : 当
usedBuffers / totalBuffers
超过阈值时,触发反压信号。 -
动态信用调整: 下游 TaskManager 根据缓冲区使用率动态计算可用的 Credit值。
-
Metrics暴露 : 通过 JMX/Prometheus 输出
buffers-in-pool-usage
等指标。 -
包括浮动缓冲区 (Floating Buffers) 和专用缓冲区 (Exclusive Buffers)
-
初始化阶段:
- 每个
RemoteInputChannel
分配initial-credit
数量的专用缓冲区。 - 剩余缓冲区放入全局浮动池。
- 每个
-
数据传输阶段:
- 远程通道优先使用专用缓冲区。
- 若专用缓冲区不足,且浮动池有可用缓冲区,则借用浮动缓冲区。
- 当浮动池耗尽时,通道停止接收新数据,触发反压。
-
缓冲区回收:
- 通道处理完数据后,释放占用的缓冲区。
- 专用缓冲区归还给通道,浮动缓冲区返回全局池。
Java
// 计算已使用缓冲区总数 = Floating + Exclusive
public int calculateUsedBuffers(SingleInputGate inputGate) {
return floatingBuffersUsageGauge.calculateUsedBuffers(inputGate)
+ exclusiveBuffersUsageGauge.calculateUsedBuffers(inputGate);
}
// 计算总缓冲区容量 = Floating + Exclusive
public int calculateTotalBuffers(SingleInputGate inputGate) {
return floatingBuffersUsageGauge.calculateTotalBuffers(inputGate)
+ exclusiveBuffersUsageGauge.calculateTotalBuffers(inputGate);
}
(1) FloatingBuffersUsageGauge
浮动缓冲区(Floating Buffers)
- 用途:全局共享的缓冲区池,动态分配给需要更多带宽的通道。
- 管理方式:
- 由
NetworkBufferPool
统一管理,根据通道的需求动态分配 / 回收。 - 使用信用机制(Credit-Based Flow Control)进行分配。
- 适用场景:
- 处理突发流量,平衡各通道的资源使用。
- 当某些通道暂时不需要缓冲区时,可释放给其他通道使用。
📌 Floating Buffers的特殊性
在 Flink Credit-Based反压机制中,浮动缓冲区是共享内存池中的动态分配单元:
- ✨ 共享性 :被所有
RemoteInputChannel
竞争使用(区别于独占缓冲区) - 🚀 动态性:根据网络负载按需分配,支持Unaligned Checkpoint等特殊场景
- 计算原理:[已使用的缓冲] = [总申请量] - [剩余可用量]
在Flink的网络缓冲模型中存在三级状态:
1️⃣ _逻辑申请总量 (requested) • Task线程通过LocalBufferPool
申请的缓冲区总数 • API调用路径:ResultPartitionWriter.requestBuffer()
→ BufferPool.requestBufferBlocking()
• 注意:这部分包含已批准但尚未被Netty线程领取的缓冲
2️⃣ _物理持有量 (physically held) • Netty实际从操作系统获得的堆外内存(DirectByteBuffer) • Metric指标名:network.totalMemorySegments
3️⃣ _可用空闲量 (available) • Netty持有的缓冲区中未被数据占用的部分 • Metric指标名:network.availableMemorySegments
📊 Gauge的作用
通过计算浮动缓冲区的使用率,为以下场景提供决策依据:
- TaskManager是否因缓冲不足触发反压?
- NetworkBufferPool是否需要动态扩容?
- Checkpoint协调器是否因缓冲耗尽而降级?
java
public class FloatingBuffersUsageGauge extends AbstractBuffersUsageGauge {
public FloatingBuffersUsageGauge(SingleInputGate[] inputGates) {
super((SingleInputGate[])Preconditions.checkNotNull(inputGates));
}
public int calculateUsedBuffers(SingleInputGate inputGate){
BufferPool bufferPool=inputGate.getBufferPool(); //获取绑定的缓冲池实例
if(bufferPool!=null){
//阶段1:获取已申请缓冲区总数(可能包含未被物理占用的逻辑申请)
int requested=bufferPool.bestEffortGetNumOfUsedBuffers();
//阶段2:遍历所有RemoteInputChannel统计实际可用缓冲
int available=0;
for(InputChannel ic:inputGate.getInputChannels().values()){
if(ic instanceof RemoteInputChannel){
available+=((RemoteInputChannel)ic).unsynchronizedGetFloatingBuffersAvailable();
}
}
/*
*阶段3:计算逻辑占用数
*公式:used = requested(逻辑申请)-available(物理可用)
*Math.max保证非负值(可能因异步统计出现短暂不一致)
*/
return Math.max(0,requested-available);
}
return 0; //缓冲池未初始化时返回0占用
}
public int calculateTotalBuffers(SingleInputGate inputGate){
BufferPool bufferPool=inputGate.getBufferPool();
return bufferPool!=null?bufferPool.getNumBuffers():0;
}
}
(2) ExclusiveBuffersUsageGauge
该类是 Flink网络层中用于监控独占缓冲区(Exclusive Buffers)使用状态的核心组件。其核心功能包括:
- 精确统计每个RemoteInputChannel独占分配的缓冲区用量
- 为Credit-Based反压机制提供通道级细粒度指标
- 支撑网络流量均衡分析和资源动态调配
java
/**
* 用于监控和计算 Flink 任务中专用缓冲区(Exclusive Buffers)使用情况的度量器。
* 该类提供了专用缓冲区已使用数量和总分配数量的计算功能,
* 仅统计远程输入通道(RemoteInputChannel)的缓冲区,因为本地通道不使用专用缓冲区。
*/
public class ExclusiveBuffersUsageGauge extends AbstractBuffersUsageGauge {
/**
* 构造函数,初始化用于监控的输入门数组。
*
* @param inputGates 包含多个输入门的数组,每个输入门对应一个上游分区
* @throws NullPointerException 如果输入的 inputGates 数组为 null
*/
public ExclusiveBuffersUsageGauge(SingleInputGate[] inputGates) {
// 调用父类构造函数并确保输入的 inputGates 不为 null
super((SingleInputGate[])Preconditions.checkNotNull(inputGates));
}
/**
* 计算指定输入门当前使用的专用缓冲区总数。
* 专用缓冲区是专门分配给远程输入通道的内存块,用于暂存网络传输的数据。
*
* @param inputGate 需要计算缓冲区使用情况的输入门
* @return 该输入门下所有远程通道正在使用的专用缓冲区总数
*/
public int calculateUsedBuffers(SingleInputGate inputGate) {
int usedBuffers = 0;
// 遍历该输入门的所有输入通道
Iterator var3 = inputGate.getInputChannels().values().iterator();
while(var3.hasNext()) {
InputChannel ic = (InputChannel)var3.next();
// 仅统计远程输入通道的专用缓冲区,本地通道不使用专用缓冲区
if (ic instanceof RemoteInputChannel) {
// unsynchronizedGetExclusiveBuffersUsed 方法直接返回当前已使用的专用缓冲区数量
// 该方法不使用同步锁,性能更高,但可能存在极小的数据延迟(适合监控场景)
usedBuffers += ((RemoteInputChannel)ic).unsynchronizedGetExclusiveBuffersUsed();
}
}
return usedBuffers;
}
/**
* 计算指定输入门分配的专用缓冲区总数量。
* 总数量由每个远程输入通道的初始信用值(initial credit)决定。
*
* @param inputGate 需要计算缓冲区总量的输入门
* @return 该输入门下所有远程通道分配的专用缓冲区总数
*/
public int calculateTotalBuffers(SingleInputGate inputGate) {
int totalExclusiveBuffers = 0;
// 遍历该输入门的所有输入通道
Iterator var3 = inputGate.getInputChannels().values().iterator();
while(var3.hasNext()) {
InputChannel ic = (InputChannel)var3.next();
// 仅统计远程输入通道的专用缓冲区
if (ic instanceof RemoteInputChannel) {
// getInitialCredit 返回该通道初始分配的缓冲区数量(信用值)
// 信用值代表通道可以持有的最大缓冲区数量,用于流量控制
totalExclusiveBuffers += ((RemoteInputChannel)ic).getInitialCredit();
}
}
return totalExclusiveBuffers;
}
}
2.数据处理--CreditBasedPartitionRequestClientHandler
这个类是 Flink 网络栈中基于信用模型的分区请求处理器,主要负责:
- 管理输入通道 :维护所有
RemoteInputChannel
的引用,处理通道的添加、移除和状态更新。 - 控制数据发送:根据接收端的信用值决定是否发送数据,实现反压控制。
- 错误处理:捕获网络异常并通知所有相关通道。
- 消息解码:处理来自生产者的各种消息(如缓冲区数据、错误响应等)。
scala
public class CreditBasedPartitionRequestClientHandler extends ChannelInboundHandlerAdapter {
// 处理Credit消息
private void handleCreditMessage(CreditMessage msg) {
RemoteInputChannel inputChannel = getInputChannel(msg.getReceiverId());
if (inputChannel != null) {
// 更新信用并尝试发送数据
inputChannel.updateCredit(msg.getCredit());
inputChannel.tryFinishPendingRequests();
}
}
// 发送数据
private void writeAndFlushNextMessageIfPossible() {
// 检查是否有足够的Credit发送数据
if (inputChannel.getCreditsAvailable() > 0 && inputChannel.hasDataToSend()) {
// 获取要发送的缓冲区
Buffer buffer = inputChannel.getNextBuffer();
// 发送数据
NetworkUtils.sendDataWithHeader(
ctx,
buffer,
inputChannel.getReceiverId());
// 减少可用Credit
inputChannel.decreaseCredit();
}
}
}
3.核心 类\组件\流程
核心组件:
- ResultPartition:上游任务生产的数据分区,用于写
- InputGate:下游任务消费数据的入口,用于读
- LocalBufferPool:管理网络缓冲区的本地缓冲池
- NetworkBufferPool:全局网络缓冲区资源池
Flink 反压机制的核心流程:
- 下游算子根据其处理能力和可用缓冲区数量发送Credit给上游算子
- 上游算子根据收到的Credit决定可以发送多少数据
- 当下游处理速度跟不上时,Credit减少,上游发送速率自动降低
- 反压通过任务链从下游向上游传播,直到Source算子
- 整个过程无需配置,自动适应负载变化
(1) 基于 Credit 的流量控制的类
java
public class EventSerializer {
// 序列化和反序列化Buffer请求事件和Credit报告
public static ByteBuffer toSerializedEvent(AbstractEvent event) {
// ... 序列化逻辑
}
public static AbstractEvent fromSerializedEvent(ByteBuffer buffer, ClassLoader classLoader) {
// ... 反序列化逻辑
}
}
// Credit报告事件类
public class CreditMessage extends AbstractEvent {
private final int targetChannel;
private final int credit;
private final long backlog;
public CreditMessage(int targetChannel, int credit, long backlog) {
this.targetChannel = targetChannel;
this.credit = credit;
this.backlog = backlog;
}
// ...
}
(2) 下游算子请求数据的过程
java
public class RemoteInputChannel extends InputChannel {
// 向上游请求缓冲区
public void requestSubpartition(int subpartitionIndex) {
if (producerAddress == null) {
throw new IllegalStateException("Producer address unknown.");
}
// 构建请求消息
PartitionRequest request = new PartitionRequest(
partitionId,
subpartitionIndex,
inputChannelId,
credit);
// 将请求发送给生产者
try {
connectionManager.createPartitionRequestClient(producerAddress)
.requestSubpartition(request);
} catch (IOException e) {
// 异常处理...
}
}
// 发送Credit
private void sendCredit() {
// 计算剩余可用缓冲区数量作为信用值
int numCreditsAvailable =
Math.min(desiredNumCredits - numCreditsAndBacklog.getFirst(), Integer.MAX_VALUE);
if (numCreditsAvailable > 0) {
// 构建Credit消息
CreditMessage credit = new CreditMessage(
inputChannelId,
numCreditsAvailable,
numCreditsAndBacklog.getSecond());
// 发送Credit给上游
connectionManager.sendCredit(producerAddress, credit);
// 更新本地已发送的Credit
numCreditsAndBacklog.setFirst(numCreditsAndBacklog.getFirst() + numCreditsAvailable);
}
}
}
(3) 上游算子接收请求并发送数据
java
public class PipelinedSubpartition extends ResultSubpartition {
// 添加缓冲区
public void add(BufferConsumer bufferConsumer) {
boolean notifyDataAvailable = false;
synchronized (buffers) {
// 添加到缓冲区队列
buffers.add(bufferConsumer);
// 通知可能等待数据的消费者
if (buffers.size() == 1) {
notifyDataAvailable = true;
}
}
if (notifyDataAvailable) {
notifyDataAvailable();
}
}
// 通知数据可用
private void notifyDataAvailable() {
if (isAvailable && registeredListener != null) {
registeredListener.notifyDataAvailable();
}
}
}
(4) 数据发送的处理
java
public class CreditBasedPartitionRequestClientHandler extends ChannelInboundHandlerAdapter {
// 处理Credit消息
private void handleCreditMessage(CreditMessage msg) {
// 获取当前可用的缓冲区
RemoteInputChannel inputChannel = getInputChannel(msg.getReceiverId());
// 如果有可用的缓冲区,那么更新信用
if (inputChannel != null) {
// 更新信用并尝试发送数据
inputChannel.updateCredit(msg.getCredit());
inputChannel.tryFinishPendingRequests();
}
}
// 发送数据
private void writeAndFlushNextMessageIfPossible() {
// 检查是否有足够的Credit发送数据
if (inputChannel.getCreditsAvailable() > 0 && inputChannel.hasDataToSend()) {
// 获取要发送的缓冲区
Buffer buffer = inputChannel.getNextBuffer();
// 发送数据
NetworkUtils.sendDataWithHeader(
ctx,
buffer,
inputChannel.getReceiverId());
// 减少可用Credit
inputChannel.decreaseCredit();
}
}
}
(5) 反压传播机制
java
public class StreamTask<OUT, OP extends StreamOperator<OUT>> extends AbstractInvokable {
// 处理输入数据
protected void processInput(OneInputStreamOperator<IN, OUT> operator,
StreamTaskInput<IN> input) throws Exception {
while (true) {
// 尝试从input读取记录
DataInputStatus status = input.emitNext(operator.getInput());
switch (status) {
case MORE_AVAILABLE:
// 继续处理
continue;
case NOTHING_AVAILABLE:
// 暂无数据,返回
return;
case END_OF_INPUT:
// 输入结束
endOfInput = true;
return;
default:
// 未知状态
throw new RuntimeException("Unknown input status: " + status);
}
}
}
}
当下游算子的处理能力不足时,它会减少请求上游数据的Credit,从而限制上游向 它发送数据的速率:
java
public class RemoteInputChannel extends InputChannel {
// 处理数据缓冲区
public void onBuffer(Buffer buffer, int sequenceNumber, int backlog) {
// 回收缓冲区到队列
boolean isEmpty = recycle(buffer, sequenceNumber);
// 更新积压量
updateBacklog(backlog);
// 缓冲区空时,请求更多的credit
if (isEmpty) {
sendCredit();
}
}
// 更新积压量
private void updateBacklog(int backlog) {
if (numCreditsAndBacklog.getSecond() != backlog) {
numCreditsAndBacklog.setSecond(backlog);
// 如果积压量减少,可能需要发送更多Credit
if (backlog == 0) {
sendCredit();
}
}
}
}
(6) 缓冲区管理
网络缓冲区管理是反压机制的核心,由NetworkBufferPool和LocalBufferPool 负责:
java
public class LocalBufferPool implements BufferPool {
// 全局缓冲区池
private final NetworkBufferPool networkBufferPool;
// 当前可用缓冲区
private final ArrayDeque<MemorySegment> availableMemorySegments;
// 缓冲区大小
private final int totalNumberOfMemorySegments;
// 最小缓冲区数量
private final int numberOfRequiredMemorySegments;
// 请求缓冲区
public Buffer requestBuffer() {
synchronized (availableMemorySegments) {
if (availableMemorySegments.isEmpty()) {
// 没有可用缓冲区,返回null
return null;
}
// 从池中获取缓冲区
MemorySegment segment = availableMemorySegments.poll();
// 创建缓冲区
return new NetworkBuffer(segment, this);
}
}
// 回收缓冲区
public void recycle(MemorySegment segment) {
synchronized (availableMemorySegments) {
// 回收到本地池
availableMemorySegments.add(segment);
// 通知等待者
availableMemorySegments.notifyAll();
}
}
}
(7) 反压监控
Flink提供了反压监控功能,可以在Web UI中查看任务的反压状态:
java
public class BackPressureStatsTrackerImpl implements BackPressureStatsTracker {
// 收集反压统计信息
public void handleBackPressureStats(Map<JobVertexID, OperatorBackPressureStats> stats) {
synchronized (lock) {
// 更新统计信息
for (Map.Entry<JobVertexID, OperatorBackPressureStats> entry : stats.entrySet()) {
JobVertexID jobVertexId = entry.getKey();
OperatorBackPressureStats operatorStats = entry.getValue();
// 存储反压信息
operatorStatsCache.put(jobVertexId, operatorStats);
}
}
}
// 获取某个任务的反压比率
public double getBackPressureRatio(JobVertexID jobVertexId) {
synchronized (lock) {
OperatorBackPressureStats stats = operatorStatsCache.get(jobVertexId);
if (stats != null) {
return stats.getBackPressureRatio();
} else {
return 0.0;
}
}
}
}
// 反压监控的实现在BackPressureRequestCoordinator类中:
public class BackPressureRequestCoordinator {
// 触发反压采样
public CompletableFuture<BackPressureStats> triggerBackPressureSampling(ExecutionJobVertex jobVertex) {
// 创建采样请求
BackPressureRequest request = new BackPressureRequest(jobVertex);
// 提交请求
return coordinator.submit(request);
}
}