一文对最新版本 Flink 反压机制全景深度解析-附源码
1. 反压形成的根本原因
1.1 反压的本质
数据生产速度 > 数据消费速度
↓
缓冲区填满
↓
生产者被阻塞
↓
反压形成并向上游传播
1.2 反压产生的典型场景
场景1: 算子处理能力不足
Source (1000 records/s) → Map (1000 r/s) → Window (100 r/s) ← 瓶颈!
↓
反压产生
场景2: 外部系统慢
Kafka Source → Transform → **Sink to DB** ← DB响应慢
↓
反压传播到Source
场景3: 数据倾斜
Key "A": 90% 数据 ← 过载!
Key "B": 5% 数据
Key "C": 5% 数据
场景4: GC 压力
大状态 + 频繁GC → Task线程暂停 → 反压
2. Buffer 管理体系(反压的物理基础)
2.1 三层 Buffer 架构
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: NetworkBufferPool │
│ (全局共享 Buffer 池) │
├─────────────────────────────────────────────────────────────┤
│ • 固定大小的 MemorySegment 集合 │
│ • 由 TaskManager 启动时分配 │
│ • 默认 32KB per segment │
│ • 动态在 LocalBufferPool 之间重分配 │
└─────────────────────────────────────────────────────────────┘
↓ 分配给 ↓ 分配给
┌──────────────────────────┐ ┌──────────────────────────┐
│ Layer 2: LocalBufferPool │ │ LocalBufferPool │
│ (InputGate 专属) │ │ (ResultPartition 专属) │
├──────────────────────────┤ ├──────────────────────────┤
│ • 每个 InputGate 一个 │ │ • 每个 ResultPartition │
│ • 可动态调整大小 │ │ 一个 │
│ • 支持 overdr专用
│ • Exclusive Buffers │ │ │
│ (专用于某个 channel) │ │ │
│ • Floating Buffers │ │ │
│ (共享于所有 channels) │ │ │
└──────────────────────────┘ └──────────────────────────┘
2.2 NetworkBufferPool 核心源码
// flink-runtime/.../network/buffer/NetworkBufferPool.java
public class NetworkBufferPool implements BufferPoolFactory {
// 全局 Buffer 总数
private final int totalNumberOfMemorySegments;
// 单个 Segment 大小(默认 32KB)
private final int memorySegmentSize;
// 可用的内存段队列
private final ArrayDeque<MemorySegment> availableMemorySegments;
// 所有 LocalBufferPool
private final Set<LocalBufferPool> allBufferPools = new HashSet<>();
/**
* 请求 Buffer(阻塞式)
* 关键:如果没有可用 Buffer,线程会在这里阻塞!
*/
private List<MemorySegment> internalRequestMemorySegments(
int numberOfSegmentsToRequest) throws IOException {
final List<MemorySegment> segments = new ArrayList<>(numberOfSegmentsToRequest);
final Deadline deadline = Deadline.fromNow(requestSegmentsTimeout);
while (true) {
if (isDestroyed) {
throw new IllegalStateException("Buffer pool is destroyed.");
}
MemorySegment segment;
synchronized (availableMemorySegments) {
// 🔥 尝试获取 Segment
segment = internalRequestMemorySegment();
if (segment == null) {
// ⏳ 没有可用 Buffer,等待 2 秒
availableMemorySegments.wait(2000);
}
}
if (segment != null) {
segments.add(segment);
}
if (segments.size() >= numberOfSegmentsToRequest) {
break; // 请求满足
}
if (!deadline.hasTimeLeft()) {
// ❌ 超时抛异常
throw new IOException("Timeout requesting buffers");
}
}
return segments;
}
}
2.3 LocalBufferPool - 动态内存管理
// flink-runtime/.../network/buffer/LocalBufferPool.java
public class LocalBufferPool implements BufferPool {
// 当前池子可用的 Buffer
private final ArrayDeque<MemorySegment> availableMemorySegments;
// 每个 subpartition 的 Buffer 计数
private final int[] subpartitionBuffersCount;
// 最大 Buffer 数(上限)
private int maxUsedBuffers;
/**
* 🔥 阻塞式请求 Buffer
* Task 线程会在这里被阻塞,形成反压!
*/
private MemorySegment requestMemorySegmentBlocking(int targetChannel)
throws InterruptedException {
MemorySegment segment;
// 循环等待直到有 Buffer 可用
while ((segment = requestMemorySegment(targetChannel)) == null) {
try {
// ⏳ 等待 Buffer 可用的 Future
getAvailableFuture().get(); // 阻塞点!
} catch (ExecutionException e) {
LOG.error("The available future is completed exceptionally.", e);
ExceptionUtils.rethrow(e);
}
}
return segment;
}
/**
* 非阻塞式请求
*/
@Nullable
private MemorySegment requestMemorySegment(int targetChannel) {
MemorySegment segment = null;
synchronized (availableMemorySegments) {
checkDestroyed();
// 1. 优先从本地池子取
if (!availableMemorySegments.isEmpty()) {
segment = availableMemorySegments.poll();
}
// 2. 如果达到上限,尝试获取 Overdraft Buffer
else if (isRequestedSizeReached()) {
segment = requestOverdraftMemorySegmentFromGlobal();
}
if (segment == null) {
return null; // 🚫 没有可用 Buffer
}
// 更新 per-channel 计数
if (targetChannel != UNKNOWN_CHANNEL) {
subpartitionBuffersCount[targetChannel]++;
// 达到per-channel上限
if (subpartitionBuffersCount[targetChannel] == maxBuffersPerChannel) {
unavailableSubpartitionsCount++;
}
}
checkAndUpdateAvailability();
}
return segment;
}
}
3. Credit-Based Flow Control(信用流量控制)
3.1 核心思想
Downstream 告诉 Upstream: "我有 N 个空闲 Buffer,你可以发 N 个"
Upstream 只能发送 Credit 允许的数据量
Credit 用完 → Upstream 停止发送 → 自然反压
3.2 Credit 传递流程
┌────────────────────────────────────────────────────────────┐
│ Step 1: Downstream 初始化 │
├────────────────────────────────────────────────────────────┤
RemoteInputChannel 启动
↓
分配 Exclusive Buffers (initialCredit)
↓
向 Upstream 发送初始 Credit
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Step 2: Upstream 接收 Credit │
├────────────────────────────────────────────────────────────┤
PartitionRequestServerHandler 收到 AddCredit 消息
↓
NetworkSequenceViewReader 更新 Credit 计数
↓
开始发送数据(每发一个 Buffer,Credit -1)
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Step 3: Downstream 消费数据 │
├────────────────────────────────────────────────────────────┤
Task 线程处理数据
↓
释放 Buffer(回收到 BufferPool)
↓
🔄 计算新的可用 Credit
↓
向 Upstream 发送 AddCredit 消息(补充 Credit)
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Step 4: Credit 耗尽 = 反压 │
├────────────────────────────────────────────────────────────┤
Upstream: Credit = 0
↓
停止发送数据
↓
数据积压在 Upstream 的 ResultPartition
↓
ResultPartition Buffer 满
↓
🔴 反压!Task 线程阻塞在 requestBufferBuilderBlocking()
└────────────────────────────────────────────────────────────┘
3.3 RemoteInputChannel - Downstream Credit 管理
// flink-runtime/.../partition/consumer/RemoteInputChannel.java
public class RemoteInputChannel extends InputChannel {
// 初始分配的专用 Buffer 数量
private final int initialCredit;
// 还未通知给 Producer 的 Credit
private final AtomicInteger unannouncedCredit = new AtomicInteger(0);
// Buffer 管理器
private final BufferManager bufferManager;
/**
* 🚀 启动时分配初始 Credit
*/
@Override
void setup() throws IOException {
// 请求 exclusive buffers
bufferManager.requestExclusiveBuffers(initialCredit);
}
/**
* 🔄 Buffer 回收时增加 Credit
*/
@Override
public void notifyBufferAvailable(Buffer buffer) {
// Buffer 被消费后释放
unannouncedCredit.incrementAndGet();
// 触发发送 AddCredit 消息
if (unannouncedCredit.get() >= creditAnnouncementThreshold) {
notifyCreditsAvailable();
}
}
/**
* 📤 向 Upstream 发送 Credit
*/
private void notifyCreditsAvailable() {
if (partitionRequestClient != null) {
// 获取并重置未通告的 Credit
int credits = getAndResetUnannouncedCredit();
if (credits > 0) {
// 🔥 发送 AddCredit 网络消息
partitionRequestClient.notifyCreditAvailable(this, credits);
}
}
}
/**
* 🎯 请求 Buffer(可能阻塞!)
*/
@Override
public Buffer requestBuffer() {
return bufferManager.requestBuffer();
}
}
3.4 PartitionRequestServerHandler - Upstream Credit 处理
// flink-runtime/.../netty/PartitionRequestServerHandler.java
@Override
protected void channelRead0(ChannelHandlerContext ctx, NettyMessage msg) {
if (msgClazz == AddCredit.class) {
AddCredit request = (AddCredit) msg;
// 🔥 关键:增加 Credit 到对应的 Reader
outboundQueue.addCreditOrResumeConsumption(
request.receiverId,
reader -> reader.addCredit(request.credit) // ✅ 补充 Credit
);
// Credit 补充后,可能触发继续发送数据
}
}
3.5 CreditBasedSequenceNumberingViewReader - 数据发送控制
// flink-runtime/.../netty/CreditBasedSequenceNumberingViewReader.java
public class CreditBasedSequenceNumberingViewReader implements NetworkSequenceViewReader {
// 当前可用的 Credit
private int numCreditsAvailable;
/**
* 🚀 添加 Credit
*/
@Override
public void addCredit(int creditDeltas) {
numCreditsAvailable += creditDeltas;
// Credit 补充后,尝试继续发送数据
}
/**
* 📤 发送数据(受 Credit 限制)
*/
@Nullable
public BufferAndAvailability getNextBuffer() throws IOException {
// 🔴 没有 Credit,不能发送!
if (numCreditsAvailable == 0) {
return null;
}
// 从 ResultSubpartitionView 获取数据
BufferAndBacklog next = subpartitionView.getNextBuffer();
if (next != null) {
// ✅ 消耗一个 Credit
numCreditsAvailable--;
return new BufferAndAvailability(
next.buffer(),
next.getNextDataType(),
next.buffersInBacklog(),
numCreditsAvailable
);
}
return null;
}
}
4. 反压传播链路(多层次分析)
4.1 完整的反压传播路径
┌─────────────────────────────────────────────────────────────┐
│ Level 1: Task 线程层(最直接的阻塞) │
├─────────────────────────────────────────────────────────────┤
StreamTask.processInput()
↓
读取数据、处理、发送到下游
↓
emit(record) → RecordWriter.emit()
↓
requestBufferBuilderBlocking() // 🔴 阻塞点1
↓
LocalBufferPool.requestMemorySegmentBlocking() // ⏳ 等待 Buffer
↓
getAvailableFuture().get() // 阻塞直到有 Buffer
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Level 2: 网络层(Credit 控制) │
├─────────────────────────────────────────────────────────────┤
Upstream ResultPartition 尝试发送数据
↓
检查 Credit: numCreditsAvailable > 0?
↓ NO
停止发送,数据积压在 ResultSubpartition
↓
ResultSubpartition.add(buffer)
↓
Buffer 满 → 新数据无法写入
↓
🔴 Upstream Task 阻塞在 requestBufferBuilderBlocking()
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Level 3: Buffer Pool 层(资源竞争) │
├─────────────────────────────────────────────────────────────┤
多个 Task 竞争同一个 NetworkBufferPool
↓
可用 Buffer 不足
↓
Task1, Task2, Task3... 都阻塞在 requestMemorySegmentBlocking()
↓
等待其他 Task 释放 Buffer
↓
🔴 全局反压
└─────────────────────────────────────────────────────────────┘
4.2 代码层面的阻塞点
阻塞点 1: RecordWriter 写入数据
// flink-runtime/.../io/RecordWriter.java
public abstract class RecordWriter<T extends IOReadableWritable> {
// 🔥 发送数据(核心方法)
public void emit(T record, int targetChannel) throws IOException {
// 1. 尝试获取 BufferBuilder
BufferBuilder bufferBuilder = getBufferBuilder(targetChannel);
// 2. 序列化数据到 Buffer
serializer.serializeRecord(record, bufferBuilder);
// 3. 如果 Buffer 满了,flush
if (bufferBuilder.isFull()) {
targetPartition.flush(targetChannel);
}
}
/**
* 🔴 阻塞点:获取 BufferBuilder
*/
private BufferBuilder getBufferBuilder(int targetChannel) throws IOException {
if (bufferBuilder != null && !bufferBuilder.isFull()) {
return bufferBuilder;
}
// ⏳ 阻塞式请求新的 Buffer
bufferBuilder = targetPartition.requestBufferBuilderBlocking(targetChannel);
return bufferBuilder;
}
}
阻塞点 2: ResultPartition 请求 Buffer
// flink-runtime/.../partition/BufferWritingResultPartition.java
public abstract class BufferWritingResultPartition extends ResultPartition {
/**
* 🔴 阻塞式请求 BufferBuilder
*/
@Override
public BufferBuilder requestBufferBuilderBlocking(int targetChannel)
throws IOException, InterruptedException {
// 从 BufferPool 请求
BufferBuilder builder = bufferPool.requestBufferBuilderBlocking(targetChannel);
if (builder == null) {
throw new IOException("No buffer available");
}
return builder;
}
}
阻塞点 3: LocalBufferPool 等待 Buffer
// flink-runtime/.../buffer/LocalBufferPool.java
@Override
public BufferBuilder requestBufferBuilderBlocking(int targetChannel)
throws InterruptedException {
// 🔥 调用阻塞式请求
return toBufferBuilder(
requestMemorySegmentBlocking(targetChannel), // ⏳ 这里会阻塞!
targetChannel
);
}
private MemorySegment requestMemorySegmentBlocking(int targetChannel)
throws InterruptedException {
MemorySegment segment;
// 🔄 循环等待
while ((segment = requestMemorySegment(targetChannel)) == null) {
try {
// ⏳⏳⏳ 阻塞等待 Buffer 可用
getAvailableFuture().get(); // CompletableFuture.get() 阻塞
} catch (ExecutionException e) {
LOG.error("The available future is completed exceptionally.", e);
ExceptionUtils.rethrow(e);
}
}
return segment;
}
4.3 反压传播的时序图
Time →
TaskManager 1 (Upstream) TaskManager 2 (Downstream)
───────────────────────────── ───────────────────────────
Source Task Map Task (慢)
│ │
│ emit(record1) │ processing...
│ emit(record2) │
│ emit(record3) │
│ ... │
│ emit(record100) │ (处理慢,Buffer未释放)
│ │
│ <── Credit = 0 ── │
│ │
│ 🔴 无Credit,停止发送 │
│ │
│ ResultPartition 积压数据 │
│ Buffer Pool 满 │
│ │
│ requestBufferBuilder() │
│ ⏳ 阻塞等待... │
│ │
│ │ finishProcessing(record50)
│ │ recycleBuffer()
│ │
│ <── AddCredit(5) ── │
│ │
│ ✅ 恢复发送 │
│ emit(record101) │
│ emit(record102) │
│ ... │
5. 上下游感知反压的机制
5.1 Downstream 如何感知压力?
// flink-runtime/.../partition/consumer/SingleInputGate.java
public class SingleInputGate extends InputGate {
/**
* 🔍 检测输入队列大小
*/
public int getNumberOfQueuedBuffers() {
int totalBuffers = 0;
for (InputChannel channel : inputChannels.values()) {
totalBuffers += channel.unsynchronizedGetNumberOfQueuedBuffers();
}
return totalBuffers;
}
/**
* 📊 队列大小指标
*/
public long getSizeOfQueuedBuffers() {
long totalBytes = 0;
for (InputChannel channel : inputChannels.values()) {
totalBytes += channel.unsynchronizedGetSizeOfQueuedBuffers();
}
return totalBytes;
}
}
Downstream 感知指标:
1. 输入队列长度 (getNumberOfQueuedBuffers)
└── 队列空 → Idle
└── 队列满 → Backpressured
2. Buffer 可用性 (bufferPool.isAvailable())
└── 有空闲 Buffer → 正常
└── 无 Buffer → Backpressured
3. Credit 发送频率
└── 频繁补充 Credit → 消费快
└── 很少补充 → 消费慢
5.2 Upstream 如何感知反压?
// flink-runtime/.../netty/CreditBasedSequenceNumberingViewReader.java
public class CreditBasedSequenceNumberingViewReader {
// 可用 Credit 数量
private int numCreditsAvailable;
/**
* 🔍 检测是否被反压
*/
public boolean isBackpressured() {
// Credit = 0 → 被下游反压
return numCreditsAvailable == 0;
}
/**
* 📊 获取积压的数据量
*/
public int getBacklog() {
return subpartitionView.getBuffersInBacklog();
}
}
Upstream 感知指标:
1. Credit 余额 (numCreditsAvailable)
└── Credit > 0 → 可以继续发送
└── Credit = 0 → 被反压
2. ResultSubpartition 积压 (buffersInBacklog)
└── 积压少 → Downstream 消费快
└── 积压多 → Downstream 消费慢
3. Buffer 请求延迟
└── 立即获取 → 正常
└── 阻塞很久 → 反压严重
5.3 反压监控指标(Task Metrics)
// flink-runtime/.../metrics/TaskIOMetricGroup.java
public class TaskIOMetricGroup {
/**
* 🔴 反压时间(每秒被反压的毫秒数)
*/
private final Gauge<Long> backPressuredTimeMsPerSecond;
/**
* ⏸ 空闲时间
*/
private final Gauge<Long> idleTimeMsPerSecond;
/**
* ⚡ 忙碌时间
*/
private final Gauge<Long> busyTimeMsPerSecond;
// 三者之和 ≈ 1000ms
}
指标计算:
每2秒采样一次:
backPressuredTime: Task 因等待 Buffer 而阻塞的时间
idleTime: Task 因等待输入数据而空闲的时间
busyTime: Task 实际处理数据的时间
示例:
─────────────────────────────────────────
| 2000ms 采样窗口 |
├─────────────────────────────────────────
| Busy: 500ms (25%) |
| Backpressured: 1200ms (60%) ← 反压! |
| Idle: 300ms (15%) |
─────────────────────────────────────────
6. 反压处理策略(源码级优化)
6.1 Buffer Debloating(动态缓冲区调整)
Flink 1.14+ 引入的自适应机制
// flink-runtime/.../network/partition/BufferDebloater.java
public class BufferDebloater {
// 目标延迟(默认 1 秒)
private final Duration targetThroughput;
// 当前缓冲区大小
private int currentBufferSize;
/**
* 🎯 根据吞吐量动态调整 Buffer 大小
*/
public void onBufferConsumed(long consumedBytes, long consumedTimeNanos) {
// 计算当前吞吐量
double throughput = (double) consumedBytes / consumedTimeNanos * 1_000_000_000;
// 计算理想 Buffer 大小
int targetBufferSize = (int) (throughput * targetThroughput.toMillis() / 1000);
// 平滑调整
currentBufferSize = (int) (0.9 * currentBufferSize + 0.1 * targetBufferSize);
// 限制在 min~max 范围内
currentBufferSize = Math.max(minBufferSize,
Math.min(maxBufferSize, currentBufferSize));
}
}
优势:
原来: 大缓冲区 → 大延迟
Source ─[1MB Buffer]→ Map ─[1MB Buffer]→ Sink
大量数据积压,Checkpoint 对齐时间长
现在: 自适应小缓冲区 → 低延迟
Source ─[64KB]→ Map ─[64KB]→ Sink
减少积压,加速 Checkpoint
6.2 Overdraft Buffers(透支缓冲)
// flink-runtime/.../buffer/LocalBufferPool.java
/**
* 🚀 透支机制:超过上限也能申请(有限制)
*/
@Nullable
private MemorySegment requestOverdraftMemorySegmentFromGlobal() {
if (overdraftBuffersCount.get() >= maxOverdraftBuffers) {
return null; // 达到透支上限
}
// 从全局池请求
MemorySegment segment = networkBufferPool.requestPooledMemorySegment();
if (segment != null) {
overdraftBuffersCount.incrementAndGet();
}
return segment;
}
场景:
正常情况: Task 使用 exclusive + floating buffers
突发情况:
├── 大记录序列化需要多个 Buffer
├── Window 触发,瞬间输出大量结果
└── Unaligned Checkpoint,需要额外 Buffer 存储飞行数据
透支机制: 允许临时超限,避免死锁
6.3 Credit 累积与批量通知
// flink-runtime/.../partition/consumer/RemoteInputChannel.java
/**
* 📬 批量发送 Credit(减少网络消息)
*/
private void notifyCreditsAvailable() {
int credits = unannouncedCredit.get();
// 🔥 只有累积到阈值才发送
if (credits >= MIN_CREDIT_ANNOUNCEMENT) {
int announced = getAndResetUnannouncedCredit();
partitionRequestClient.notifyCreditAvailable(this, announced);
}
}
优化效果:
未优化: 每消费1个Buffer → 发1次AddCredit → 大量小消息
优化后: 消费10个Buffer → 发1次AddCredit(10) → 减少网络开销
7. 反压的级联效应(系统视角)
7.1 反压如何影响 Checkpoint
┌────────────────────────────────────────────────────────────┐
│ 正常情况(无反压) │
├────────────────────────────────────────────────────────────┤
Checkpoint Barrier 快速传播
Source ─[Barrier]→ Map ─[Barrier]→ Sink
50ms 100ms 150ms
Total: 150ms ✅
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ 反压情况(Aligned Checkpoint) │
├────────────────────────────────────────────────────────────┤
Barrier 被积压的数据阻塞
Source ─[Barrier...大量数据...]→ Map (等待对齐) ─→ Sink
50ms ⏳ 10分钟
Total: 10分钟 ❌ Checkpoint 超时!
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ 反压 + Unaligned Checkpoint │
├────────────────────────────────────────────────────────────┤
Barrier 直接穿过,飞行数据包含在 Checkpoint
Source ─[Barrier]→ Map ─[Barrier]→ Sink
50ms 100ms 150ms
Total: 150ms ✅ 但 Checkpoint 更大
└────────────────────────────────────────────────────────────┘
7.2 反压与 Watermark 的交互
/**
* 🌊 Watermark 也会被反压影响
*/
Source (Event Time) → Watermark(t) → 被反压阻塞 → Window 无法触发
问题:
├── Watermark 传播慢
├── Window 触发延迟
└── 结果延迟增加
解决:
└── Idle Source Detection(空闲源检测)
如果某个分区空闲,不阻塞 Watermark 进度
8. 完整示例:反压从产生到解决
8.1 场景:Kafka Source → Slow Sink
// 问题代码
env.addSource(new FlinkKafkaConsumer<>(...))
.map(record -> parseRecord(record))
.keyBy(r -> r.getKey())
.process(new ExpensiveProcessFunction()) // 慢!
.addSink(new SlowDBSink()); // 更慢!
8.2 反压形成过程(时间线)
T=0s: 系统启动
Source: 1000 records/s
Process: 可处理 500 records/s
Sink: 可处理 100 records/s ← 瓶颈
T=1s: Sink 成为瓶颈
Sink Buffer 满
↓
Process 无法发送数据
↓
Process Buffer 开始积压
T=2s: Process 被反压
Process 阻塞在 requestBufferBuilderBlocking()
↓
Process 输入队列满
↓
Source Credit 耗尽
T=3s: Source 被反压
Source 无法发送数据到 Process
↓
Source 阻塞在 emit()
↓
Kafka 消费暂停
T=5s: 稳态反压
整个 Pipeline 速度降到 100 records/s(Sink 速度)
✅ 系统稳定,但吞吐量低
8.3 监控指标表现
flink-监控
Source Task:
backPressuredTimeMsPerSecond: 800ms (80% 反压)
idleTimeMsPerSecond: 50ms
busyTimeMsPerSecond: 150ms
Process Task:
backPressuredTimeMsPerSecond: 850ms (85% 反压)
idleTimeMsPerSecond: 20ms
busyTimeMsPerSecond: 130ms
Sink Task:
backPressuredTimeMsPerSecond: 100ms (10% 反压)
idleTimeMsPerSecond: 50ms
busyTimeMsPerSecond: 850ms (85% 忙碌) ← 瓶颈!
8.4 解决方案
方案 1: 增加并行度
env.addSource(new FlinkKafkaConsumer<>(...))
.setParallelism(4)
.map(record -> parseRecord(record))
.setParallelism(4)
.keyBy(r -> r.getKey())
.process(new ExpensiveProcessFunction())
.setParallelism(8) // 增加处理能力
.addSink(new SlowDBSink())
.setParallelism(16); // 显著增加 Sink 并行度
方案 2: 优化算子逻辑
// 异步 Sink(减少阻塞)
.addSink(new AsyncDatabaseSink<>(
new AsyncDatabaseRequestor(),
100, // 最大并发请求
TimeUnit.SECONDS,
60 // 超时
));
方案 3: 启用 Unaligned Checkpoint
env.getCheckpointConfig().enableUnalignedCheckpoints();
env.getCheckpointConfig().setAlignedCheckpointTimeout(Duration.ofSeconds(10));
方案 4: Buffer Debloating
# flink-conf.yaml
taskmanager.network.memory.buffer-debloat.enabled: true
taskmanager.network.memory.buffer-debloat.target: 1s
9. 核心源码路径总结
反压相关核心类:
1. Buffer 管理
├── NetworkBufferPool.java
│ org.apache.flink.runtime.io.network.buffer.NetworkBufferPool
├── LocalBufferPool.java
│ org.apache.flink.runtime.io.network.buffer.LocalBufferPool
└── BufferManager.java
org.apache.flink.runtime.io.network.partition.consumer.BufferManager
2. Credit 流控
├── RemoteInputChannel.java
│ org.apache.flink.runtime.io.network.partition.consumer.RemoteInputChannel
├── CreditBasedSequenceNumberingViewReader.java
│ org.apache.flink.runtime.io.network.netty.CreditBasedSequenceNumberingViewReader
└── PartitionRequestServerHandler.java
org.apache.flink.runtime.io.network.netty.PartitionRequestServerHandler
3. 网络消息
└── NettyMessage.java
org.apache.flink.runtime.io.network.netty.NettyMessage
├── AddCredit (Credit 通知)
├── BufferResponse (数据传输)
└── PartitionRequest (请求分区)
4. 反压监控
└── TaskIOMetricGroup.java
org.apache.flink.runtime.metrics.TaskIOMetricGroup
├── backPressuredTimeMsPerSecond
├── idleTimeMsPerSecond
└── busyTimeMsPerSecond
5. 数据分区
├── ResultPartition.java
│ org.apache.flink.runtime.io.network.partition.ResultPartition
└── ResultSubpartition.java
org.apache.flink.runtime.io.network.partition.ResultSubpartition
10. 总结
反压的本质
反压 = 自然的流量控制机制
= 系统的自我保护
≠ 系统故障
关键要点
- 反压是分布式系统的正常现象,表示系统在自动调节速度
- Credit-based Flow Control 是 Flink 反压的核心机制
- Buffer 是反压传播的物理载体
- 阻塞是反压的表现形式(Task 线程阻塞在 Buffer 请求)
- 监控指标 可以准确反映反压状态
- Unaligned Checkpoint 和 Buffer Debloating 是应对反压的现代方案
最佳实践
✅ 监控反压指标
✅ 识别瓶颈算子
✅ 合理设置并行度
✅ 启用 Buffer Debloating
✅ 使用 Unaligned Checkpoint(反压下)
✅ 优化慢算子逻辑
✅ 使用异步 Sink
✅ 调优 Buffer 配置

被折叠的 条评论
为什么被折叠?



