🐢 当"强一致性"遇上"猪队友",Producer 直接摆烂
一、现象:生产界的"心脏骤停"
表象是什么
时间线:
00:00 TPS = 10000 😊 风生水起
00:05 TPS = 5000 😐 有点不对劲
00:10 TPS = 1000 😱 老板在看我
00:15 TPS = 100 💀 准备写检讨
00:30 TPS = 0 🪦 系统已凉凉
深渊在凝视
# Producer 日志(循环播放的绝望)
[WARN] Expiring 10000 records for topic-0 due to timeout
[WARN] Expiring 10000 records for topic-0 due to timeout
[WARN] Expiring 10000 records for topic-0 due to timeout
...
[ERROR] Failed to send message: TimeoutException after 120000ms
[ERROR] Failed to send message: TimeoutException after 120000ms
[ERROR] Failed to send message: TimeoutException after 120000ms
"我发了 10000 条消息,等回复等到花儿都谢了"
"算了,不等了,直接丢弃"
"又发了 10000 条,又等不到回复"
"又丢了..."
"这班谁爱上谁上吧"
监控指标"集体装死"
| 指标 | 正常值 | 故障值 | 状态 |
|---|---|---|---|
record-send-rate |
10000/s | 0/s | ❌ 归零 |
request-latency-avg |
10ms | 120000ms | ❌ 超时 |
record-error-rate |
0 | 100% | ❌ 全错 |
| Broker CPU | 30% | 30% | ✅ 正常 |
| Broker 网络 | 500MB/s | 500MB/s | ✅ 正常 |
| Broker 磁盘 | 60% | 60% | ✅ 正常 |
现代诡异录
Producer: "我发不出去!"
Broker: "我挺好啊,CPU 网络都正常"
网络: "我也挺好啊,带宽够用"
那到底是谁的问题?🤔
二、根因:从底层协议扒起
2.1 Producer 发送流程"五步走"
┌─────────────────────────────────────────────────────────────┐
│ Producer 发送消息完整流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 消息序列化 │
│ Record → byte[] (快速,微秒级) │
│ │
│ Step 2: 分区选择 │
│ 根据 key/hash 决定发送到哪个 partition (快速,微秒级) │
│ │
│ Step 3: 放入 RecordAccumulator (发送缓冲区) │
│ 等待批处理 (可配置 linger.ms,默认 0) │
│ │
│ Step 4: Sender 线程批量发送 │
│ 通过网络发送到 Broker (毫秒级) │
│ │
│ Step 5: 等待 Broker 确认 (acks 配置决定) ⭐ 问题出在这里! │
│ │
│ acks=0: 不等待确认,发完就跑 │
│ acks=1: 等待 Leader 确认 │
│ acks=all: 等待所有 ISR 副本确认 ⚠️ 本次主角 │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 acks=all 的"死亡陷阱"
┌─────────────────────────────────────────────────────────────┐
│ acks=all 确认机制详解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 配置:acks=all + min.insync.replicas=2 │
│ │
│ 正常情况: │
│ Producer → Leader → Follower1 ✓ → Follower2 ✓ → ACK │
│ (收到 2 个 ISR 确认,返回成功) │
│ │
│ 故障情况: │
│ Producer → Leader → Follower1 ✓ → Follower2 ❌ (同步慢) │
│ ↓ │
│ ISR 收缩 = [Leader, Follower1] (只有 1 个?不对!) │
│ ↓ │
│ 等等... Leader 自己也算 ISR 成员! │
│ 实际 ISR = [Leader] (只有 1 个) │
│ ↓ │
│ min.insync.replicas=2 > ISR=1 ❌ 不满足条件! │
│ ↓ │
│ Leader 拒绝写入!Producer 无限等待! │
│ ↓ │
│ 直到 delivery.timeout.ms 超时,消息被丢弃 │
│ │
└─────────────────────────────────────────────────────────────┘
2.3 ISR(In-Sync Replicas)机制详解
┌─────────────────────────────────────────────────────────────┐
│ ISR 动态收缩机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 初始状态: │
│ Partition: topic-0-0 │
│ Replicas: [Broker1, Broker2, Broker3] (3 个副本) │
│ ISR: [Broker1, Broker2, Broker3] (3 个都在同步) │
│ Leader: Broker1 │
│ │
│ Follower2 同步变慢(网络抖动/磁盘慢): │
│ replica.lag.time.max.ms = 30000 (30 秒) │
│ ↓ │
│ Follower2 超过 30 秒没追上 Leader │
│ ↓ │
│ ISR 收缩:[Broker1, Broker3] (Follower2 被踢出) │
│ │
│ Follower3 也同步变慢: │
│ ↓ │
│ ISR 收缩:[Broker1] (只剩 Leader 自己!) ⚠️ 危险! │
│ │
│ 此时 min.insync.replicas=2: │
│ ISR 数量 (1) < min.insync.replicas (2) │
│ ↓ │
│ Leader 拒绝写入任何消息! │
│ ↓ │
│ Producer 等待 ack 超时 → 消息丢弃 │
│ │
└─────────────────────────────────────────────────────────────┘
2.4 超时配置"三重门"
// Producer 有三个关键超时配置,层层嵌套
┌─────────────────────────────────────────────────────────────┐
│ 超时配置层级关系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第一层:request.timeout.ms (默认 30 秒) │
│ ├─ 含义:单次请求等待 Broker 响应的超时时间 │
│ ├─ 触发:发送一批消息后,等待 ack 的时间 │
│ └─ 超时后:重试(如果 retries > 0) │
│ │
│ 第二层:delivery.timeout.ms (默认 120 秒) ⭐ │
│ ├─ 含义:消息从发送到最终成功/失败的总时间 │
│ ├─ 触发:包含所有重试时间的总和 │
│ └─ 超时后:消息被丢弃,返回 TimeoutException │
│ │
│ 第三层:max.block.ms (默认 60 秒) │
│ ├─ 含义:send() 方法阻塞等待缓冲区的最大时间 │
│ ├─ 触发:RecordAccumulator 满了,等待空间释放 │
│ └─ 超时后:抛出 TimeoutException │
│ │
│ 关系:delivery.timeout.ms >= request.timeout.ms │
│ │
└─────────────────────────────────────────────────────────────┘
2.5 故障时间线"死亡螺旋"
┌──────────────────────────────────────────────────────────────┐
│ 故障时间线完整还原 │
├──────────────────────────────────────────────────────────────┤
│ │
│ T0 Follower2 磁盘变慢(可能是 GC/其他租户干扰) │
│ T0+5s Follower2 开始 lag,落后 Leader 5 秒 │
│ T0+30s Follower2 lag > replica.lag.time.max.ms (30 秒) │
│ T0+31s ISR 收缩,Follower2 被踢出 ISR │
│ T0+35s Follower3 也开始 lag(同一块磁盘?) │
│ T0+65s ISR 收缩,只剩 [Leader] (1 个副本) │
│ │
│ ⚠️ 此时 min.insync.replicas=2 > ISR=1,Leader 拒绝写入! │
│ │
│ T1 Producer 发送消息 batch-1 │
│ T1+30s request.timeout.ms 超时,触发重试 │
│ T1+60s 重试再次超时 │
│ T1+90s 重试再次超时 │
│ T1+120s delivery.timeout.ms 超时,消息被丢弃 │
│ Producer 日志:Expiring 10000 records │
│ │
│ T2 Producer 发送消息 batch-2 │
│ T2+120s 同样被丢弃 │
│ ... │
│ │
│ 💀 结果:TPS 从 10000 跌到 0,消息大量丢失 │
│ │
└──────────────────────────────────────────────────────────────┘
2.6 为什么 Broker 监控"一切正常"?
┌─────────────────────────────────────────────────────────────┐
│ Broker 监控"假正常"之谜 │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU 正常 ✅ → 因为 Leader 根本没在处理写入请求 │
│ 请求在"等待 ISR 满足条件"阶段就被阻塞了 │
│ 不是"忙不过来",是"不让写" │
│ │
│ 网络正常 ✅ → 因为网络带宽确实够用 │
│ 问题是协议层面的拒绝,不是网络层面的阻塞 │
│ │
│ 磁盘正常 ✅ → 因为磁盘确实没写满 │
│ 问题是 ISR 数量不够,不是磁盘空间不够 │
│ │
│ 类比:你去银行办业务 │
│ - 银行大厅很空(CPU 正常) │
│ - 网络信号很好(网络正常) │
│ - 但窗口说"需要 2 个柜员签字,但只有 1 个在岗" │
│ - 你就一直等,等到超时 │
│ - 最后业务没办成,还被赶出去了 │
│ │
└─────────────────────────────────────────────────────────────┘
三、急救方案(从"止血"到"治本")
3.1 方案一:降级 acks 配置(最快止血)
// 这是"断臂求生"方案,能恢复 TPS 但牺牲一致性
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "1"); // 从 all 降级为 1
// 优点:
// 1. 只等待 Leader 确认,不等待 Follower
// 2. TPS 立刻恢复
// 3. 不需要重启 Broker
// 风险:
// 1. Leader 宕机可能丢失数据(Follower 可能没同步)
// 2. 数据一致性从"强"降级为"弱"
// 3. 金融/订单场景慎用!
// 💡 适用场景:
// - 日志类数据,允许少量丢失
// - 紧急恢复业务,后续再修复
// - 非核心业务
3.2 方案二:紧急修复 ISR(治本方案)
# 第一步:查看 ISR 状态
bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--describe --topic hot-topic
# 输出示例:
# Topic: hot-topic Partition: 0 Leader: 1 Replicas: 1,2,3 ISR: 1
# ↑
# 只有 Leader 自己!
# 第二步:查看 Follower lag 情况
bin/kafka-log-dirs.sh --bootstrap-server localhost:9092 \
--describe --topic hot-topic
# 第三步:检查 Follower Broker 状态
# 登录 Broker2 和 Broker3,检查:
# 1. 磁盘 IO:iostat -x 1
# 2. 网络:iftop
# 3. GC 日志:grep "Full GC" broker.log
# 4. 磁盘空间:df -h
# 第四步:修复 Follower
# 如果是磁盘满:扩容或清理
# 如果是网络问题:修复网络
# 如果是 Broker 宕机:重启 Broker
# 第五步:等待 ISR 恢复
# 监控 ISR 数量,等待恢复到 2 个以上
watch -n 1 'kafka-topics.sh --bootstrap-server localhost:9092 \
--describe --topic hot-topic | grep ISR'
3.3 方案三:临时调整 min.insync.replicas(折中方案)
# 临时降低 min.insync.replicas 要求
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type topics \
--entity-name hot-topic \
--alter \
--add-config min.insync.replicas=1
# ✅ 优点:
# 1. 不需要改 Producer 配置
# 2. 恢复写入能力
# 3. 可动态调整,无需重启
# ❌ 风险:
# 1. 数据可靠性降低
# 2. 只有一个副本确认,可能丢失
# 💡 建议:
# 1. 作为临时应急方案
# 2. ISR 恢复后立刻调回 2
# 3. 配合监控告警使用
3.4 方案四:增加超时时间(延缓问题)
// 这是"止痛药"方案,不能治本但能争取时间
Properties props = new Properties();
props.put("delivery.timeout.ms", "300000"); // 从 120 秒 调到 300 秒
props.put("request.timeout.ms", "60000"); // 从 30 秒 调到 60 秒
props.put("retries", "5"); // 增加重试次数
// 优点:
// 1. 给 ISR 恢复争取更多时间
// 2. 减少消息丢弃
// 3. 不需要改 Broker 配置
// 风险:
// 1. Producer 阻塞时间变长
// 2. 内存占用增加(缓冲区积压)
// 3. 如果 ISR 一直不恢复,最终还是会超时
// 💡 建议:
// 1. 配合 ISR 修复一起使用
// 2. 监控 Producer 内存使用
// 3. 设置上限,避免无限等待
3.5 方案五:死信队列兜底(最后一道防线)
// 这是"保险"方案,确保消息不丢失
public class ReliableProducer {
private final KafkaProducer<String, String> producer;
private final DeadLetterQueue dlq;
public void sendWithFallback(ProducerRecord<String, String> record) {
try {
producer.send(record, (metadata, exception) -> {
if (exception != null) {
// 发送失败,写入死信队列
log.error("发送失败,写入死信队列", exception);
dlq.send(record, exception);
}
}).get(120, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 超时,也写入死信队列
log.error("发送超时,写入死信队列", e);
dlq.send(record, e);
} catch (Exception e) {
log.error("发送异常", e);
dlq.send(record, e);
}
}
}
// 死信队列实现(可以用另一个 Kafka Topic)
public class DeadLetterQueue {
private final KafkaProducer<String, String> dlqProducer;
private static final String DLQ_TOPIC = "dead-letter-queue";
public void send(ProducerRecord<String, String> original, Exception e) {
ProducerRecord<String, String> dlqRecord = new ProducerRecord<>(
DLQ_TOPIC,
original.key(),
buildDlqMessage(original, e)
);
dlqProducer.send(dlqRecord);
}
private String buildDlqMessage(ProducerRecord<String, String> original, Exception e) {
return String.format(
"{\"originalTopic\":\"%s\",\"originalKey\":\"%s\",\"originalValue\":\"%s\",\"error\":\"%s\",\"timestamp\":%d}",
original.topic(),
original.key(),
original.value(),
e.getMessage(),
System.currentTimeMillis()
);
}
}
四、预防方案(监控 + 配置 + 架构)
4.1 关键配置"黄金组合"
# Producer 推荐配置
# ========== 确认机制 ==========
acks=all # 强一致性(核心业务)
# acks=1 # 高性能(日志类业务)
# ========== 超时配置 ==========
delivery.timeout.ms=120000 # 消息总超时 2 分钟
request.timeout.ms=30000 # 单次请求超时 30 秒
max.block.ms=60000 # send() 阻塞最多 60 秒
# ========== 重试配置 ==========
retries=3 # 重试 3 次
retry.backoff.ms=1000 # 重试间隔 1 秒
# ========== 批量配置 ==========
linger.ms=5 # 等待 5ms 凑批
batch.size=16384 # 每批最大 16KB
compression.type=lz4 # 压缩减少网络传输
# ========== 缓冲配置 ==========
buffer.memory=33554432 # 32MB 发送缓冲区
max.in.flight.requests.per.connection=5 # 最多 5 个未确认请求
4.2 Broker 关键配置
# Broker 推荐配置
# ========== ISR 相关 ==========
min.insync.replicas=2 # 最小同步副本数
replica.lag.time.max.ms=30000 # Follower lag 超过 30 秒踢出 ISR
unclean.leader.election.enable=false # 禁止非 ISR 副本成为 Leader
# ========== 副本同步 ==========
num.replica.fetchers=2 # 副本拉取线程数
replica.fetch.wait.max.ms=500 # 副本拉取等待时间
replica.fetch.min.bytes=1 # 有数据就拉取
replica.fetch.max.bytes=10485760 # 单次拉取最大 10MB
4.3 监控指标"必备清单"
# Prometheus 监控配置
groups:
- name: kafka-producer-alerts
rules:
# Producer 发送失败率
- alert: KafkaProducerErrorRateHigh
expr: rate(kafka_producer_record_error_total[5m]) > 0.01
for: 5m
labels:
severity: critical
annotations:
summary: "Producer 发送错误率超过 1%"
# Producer 发送延迟
- alert: KafkaProducerRequestLatencyHigh
expr: kafka_producer_request_latency_avg > 10000
for: 5m
labels:
severity: warning
annotations:
summary: "Producer 请求延迟超过 10 秒"
# ISR 收缩告警
- alert: KafkaISRShrunk
expr: kafka_server_replicamanager_isrshrinkspersec > 0
for: 1m
labels:
severity: warning
annotations:
summary: "ISR 发生收缩,检查 Follower 状态"
# UnderReplicated 分区
- alert: KafkaUnderReplicatedPartitions
expr: kafka_server_underreplicatedpartitions > 0
for: 5m
labels:
severity: critical
annotations:
summary: "存在未同步的分区"
4.4 关键 JMX 指标解读
// Producer 关键 JMX 指标
ObjectName objectName = new ObjectName(
"kafka.producer:type=producer-metrics,client-id=*"
);
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
// 1. 消息发送速率
Attribute sendRate = mBeanServer.getAttribute(
objectName,
"record-send-rate"
);
// 2. 消息错误率
Attribute errorRate = mBeanServer.getAttribute(
objectName,
"record-error-rate"
);
// 3. 请求延迟
Attribute requestLatency = mBeanServer.getAttribute(
objectName,
"request-latency-avg"
);
// 4. 缓冲区使用情况
Attribute bufferAvailable = mBeanServer.getAttribute(
objectName,
"bufferpool-wait-time-total"
);
// 5. 压缩率
Attribute compressionRate = mBeanServer.getAttribute(
objectName,
"compression-rate-avg"
);
4.5 健康检查脚本
#!/bin/bash
# kafka-producer-health-check.sh
BOOTSTRAP_SERVER="localhost:9092"
TOPIC="hot-topic"
echo "=== 检查 Topic ISR 状态 ==="
kafka-topics.sh --bootstrap-server $BOOTSTRAP_SERVER \
--describe --topic $TOPIC | grep -E "ISR|Replicas"
echo ""
echo "=== 检查 UnderReplicated 分区 ==="
UNDER_REP=$(kafka-topics.sh --bootstrap-server $BOOTSTRAP_SERVER \
--describe --under-replicated-partitions | wc -l)
if [ $UNDER_REP -gt 1 ]; then
echo "发现 $UNDER_REP 个未同步分区"
kafka-topics.sh --bootstrap-server $BOOTSTRAP_SERVER \
--describe --under-replicated-partitions
else
echo " 所有分区同步正常"
fi
echo ""
echo "=== 检查 Broker 状态 ==="
for broker in 1 2 3; do
echo "Broker $broker:"
# 检查 Broker 是否在线
nc -z localhost 909$broker && echo " 在线" || echo " 离线"
done
echo ""
echo "=== 检查 Producer 指标 ==="
# 通过 JMX 或 Prometheus 查询 Producer 指标
# 这里简化处理
curl -s http://prometheus:9090/api/v1/query \
--data-urlencode "query=rate(kafka_producer_record_error_total[5m])" \
| jq '.data.result[0].value[1]' | awk '{if($1>0.01) print " 错误率高"; else print " 错误率正常"}'
4.6 容量规划建议
┌─────────────────────────────────────────────────────────────┐
│ Producer 容量规划公式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 所需缓冲区 = (TPS × 平均消息大小 × delivery.timeout.ms) │
│ ÷ batch.size × 安全系数 │
│ │
│ 示例计算: │
│ - TPS: 10000/s │
│ - 平均消息大小: 1KB │
│ - delivery.timeout.ms: 120000ms (2 分钟) │
│ - batch.size: 16KB │
│ - 安全系数: 1.5 │
│ │
│ 所需缓冲区 = (10000 × 1KB × 120) ÷ 16KB × 1.5 │
│ = 1,200,000 KB ÷ 16KB × 1.5 │
│ = 75,000 batches × 1.5 │
│ = 112,500 batches │
│ = 112,500 × 16KB ≈ 1.8 GB │
│ │
│ 建议配置:buffer.memory = 2GB │
│ │
│ ⚠️ 注意:如果缓冲区满了,send() 会阻塞或抛出异常 │
│ │
└─────────────────────────────────────────────────────────────┘
五、故障排查"速查表"
┌─────────────────────────────────────────────────────────────┐
│ Producer 发送慢故障排查速查表 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 症状 1:TPS 骤降,Producer 日志报 Timeout │
│ ├─ 检查 ISR 数量:kafka-topics.sh --describe │
│ ├─ 检查 min.insync.replicas 配置 │
│ ├─ 检查 Follower lag:kafka-log-dirs.sh --describe │
│ └─ 检查 Broker 状态:是否有 Broker 宕机 │
│ │
│ 症状 2:消息大量被丢弃 (Expiring records) │
│ ├─ 检查 delivery.timeout.ms 配置 │
│ ├─ 检查 request.timeout.ms 配置 │
│ ├─ 检查重试次数:retries 配置 │
│ └─ 检查死信队列是否有积压 │
│ │
│ 症状 3:Producer 内存飙升 │
│ ├─ 检查 buffer.memory 配置 │
│ ├─ 检查 RecordAccumulator 积压情况 │
│ ├─ 检查发送速率是否超过 Broker 处理能力 │
│ └─ 检查是否有消息阻塞导致缓冲区无法释放 │
│ │
│ 症状 4:部分 Topic 正常,部分 Topic 异常 │
│ ├─ 检查异常 Topic 的 ISR 状态 │
│ ├─ 检查异常 Topic 的 Leader 分布 │
│ ├─ 检查是否有 Broker 负载不均衡 │
│ └─ 检查异常 Topic 的分区是否集中在同一 Broker │
│ │
└─────────────────────────────────────────────────────────────┘
六、终极建议(血泪总结)
┌─────────────────────────────────────────────────────────────┐
│ acks 配置选择决策树 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 你的业务能容忍数据丢失吗? │
│ │
│ ├─ 能容忍(日志、监控数据) │
│ │ └─→ acks=1 或 acks=0 │
│ │ 优点:TPS 高,延迟低 │
│ │ 风险:Leader 宕机可能丢数据 │
│ │ │
│ └─ 不能容忍(订单、支付、用户数据) │
│ └─→ acks=all + min.insync.replicas=2 │
│ 优点:数据强一致,不丢数据 │
│ 风险:ISR 不足时会阻塞 │
│ 对策:监控 ISR + 死信队列兜底 │
│ │
└─────────────────────────────────────────────────────────────┘
6.2 架构建议
推荐架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 业务服务 │ → │ Producer │ → │ Kafka │
│ │ │ + 重试 │ │ Cluster │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
│ 失败
▼
┌─────────────┐
│ 死信队列 │
│ (DLQ) │
└──────┬──────┘
│
│ 后续处理
▼
┌─────────────┐
│ 补偿任务 │
│ (Retry) │
└─────────────┘
acks=all 是"强一致性"的承诺,但前提是 ISR 要给力 。否则 Producer 就会陷入"等也不是,不等也不是"的两难境地。解决方案不是"无限等待",而是"有限等待 + 死信兜底"。
情景再现
┌─────────────────────────────────────────────────────────────┐
│ Kafka Producer 故障模板 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【故障概述】 │
│ - 故障时间:2024-XX-XX XX:XX │
│ - 影响范围:XX 业务线,TPS 从 10000 降至 0 │
│ - 持续时间:XX 分钟 │
│ │
│ 【根因分析】 │
│ - 直接原因:ISR 收缩至 1 个,小于 min.insync.replicas=2 │
│ - 根本原因:Follower2 磁盘 IO 瓶颈导致同步 lag │
│ - 触发条件:大促流量峰值,Follower 跟不上 Leader │
│ │
│ 【处理过程】 │
│ - T+0: 发现告警,TPS 骤降 │
│ - T+5: 定位到 ISR 收缩问题 │
│ - T+10: 临时降级 acks=1,恢复 TPS │
│ - T+30: 修复 Follower2 磁盘问题 │
│ - T+60: ISR 恢复,acks 调回 all │
│ │
│ 【改进措施】 │
│ - [x] 增加 ISR 收缩告警(已完成) │
│ - [x] 配置死信队列(已完成) │
│ - [ ] 优化 Follower 磁盘 IO(进行中) │
│ - [ ] 容量规划评审(计划中) │
│ │
│ 【经验教训】 │
│ - acks=all 必须配合 ISR 监控 │
│ - 超时配置必须设置,避免无限等待 │
│ - 核心业务必须有死信队列兜底 │
│ │
└─────────────────────────────────────────────────────────────┘
Again :强一致性是有代价的,这个代价就是 ISR 必须给力。否则,Producer 就会用 TPS 告诉你什么叫"等待的煎熬"。