4、单线程场景下的Batch策略:串行处理与批量处理的权衡
说实话,单线程下的Batch策略,是很多新手容易忽略的点。
大家一提到批量,脑子里全是多线程、并发、高吞吐。但现实是,很多业务场景就是单线程的——比如消息队列的单个消费者、定时任务、或者某些I/O密集型的串行处理流程。这时候,你该怎么玩批量?
我个人的习惯是,先搞清楚一个核心问题:单线程下,批量到底能带来什么好处?
4.1 串行处理:简单但低效
先看最朴素的方案。来一条数据,处理一条。处理完,再等下一条。
// 串行处理伪代码
while (true) {
Record record = fetchOne(); // 从队列或DB拉取一条
process(record); // 处理它
}
这种模式,我称之为「来一个打一个」。优点是啥?延迟极低。数据一到,立刻处理。缺点也很明显——吞吐量上不去。
为什么?因为每次fetchOne()和process()之间,可能有大量的固定开销。比如网络往返、磁盘寻道、上下文切换。你想想看,处理一条数据花10ms,但拉取一条数据也花10ms,那有一半的时间都浪费在「等」上了。
4.2 批量处理:用延迟换吞吐
那批量处理呢?说白了,就是把多条数据攒到一起,一次性处理。
// 批量处理伪代码
List<Record> buffer = new ArrayList<>();
while (true) {
Record record = fetchOne();
buffer.add(record);
if (buffer.size() >= BATCH_SIZE) {
processBatch(buffer); // 批量处理
buffer.clear();
}
}
这里的关键变化是:一次fetch,处理多条。原来拉取10次才能处理10条,现在拉取10次,可能攒够了100条才处理一次。固定开销被摊薄了。
我在项目中遇到过这样一个场景:一个日志采集服务,单线程从Kafka拉取日志。串行处理时,每条日志的入库时间大约是15ms(包含网络开销)。改成批量后,每次攒够500条再批量写入数据库,单条日志的入库时间降到了2ms左右。吞吐量提升了7倍多。
4.3 权衡的艺术:延迟 vs 吞吐
你可能会问:那是不是无脑上批量就行了?
当然不是。批量处理有一个天然的代价——延迟。
串行处理时,一条数据从到达到处理完,延迟就是处理时间本身。但批量处理时,数据要先在缓冲区里等着,等到攒够了才处理。这个等待时间,就是额外增加的延迟。
| 策略 | 延迟 | 吞吐量 | 适用场景 |
|---|---|---|---|
| 串行处理 | 低(毫秒级) | 低 | 实时性要求高,数据量小 |
| 批量处理 | 高(秒级甚至更高) | 高 | 吞吐量优先,可容忍一定延迟 |
说白了,这就是一个用延迟换吞吐的买卖。你愿意等多久,决定了你能省多少固定开销。
4.4 动态Batch:我常用的折中方案
那有没有办法既保持低延迟,又享受高吞吐?
有。我管它叫「动态Batch」。
核心思路是:不固定攒够多少条才处理,而是加一个时间阈值。比如:
- 如果数据来得快,攒够100条就处理(高吞吐模式)
- 如果数据来得慢,超过100ms还没攒够,也处理(保证延迟上限)
// 动态Batch伪代码
List<Record> buffer = new ArrayList<>();
long lastProcessTime = System.currentTimeMillis();
while (true) {
Record record = fetchOne();
buffer.add(record);
long now = System.currentTimeMillis();
// 两个条件:攒够了 或 超时了
if (buffer.size() >= BATCH_SIZE || (now - lastProcessTime) > MAX_WAIT_MS) {
processBatch(buffer);
buffer.clear();
lastProcessTime = now;
}
}
这个方案,我在一个实时风控系统里用过。数据量波动很大——高峰期每秒几千条,低谷期可能几秒才一条。固定Batch的话,要么高峰期来不及处理,要么低谷期延迟高得离谱。动态Batch完美解决了这个问题。
4.5 核心逻辑图
下面这张图,是我梳理的单线程Batch策略的核心决策逻辑。你可以对照着看自己的场景该选哪种。
4.6 小结
单线程下的Batch策略,说白了就三句话:
- 延迟敏感 → 串行处理,别折腾
- 流量稳定 → 固定Batch,简单高效
- 流量波动 → 动态Batch,兼顾延迟和吞吐
嗯,这里要注意:没有银弹。每种方案都有它的适用边界。我个人的建议是,先跑一段串行处理,拿到延迟和吞吐的基线数据。然后再根据业务容忍度,去选择合适的Batch策略。
毕竟,没有数据支撑的优化,都是耍流氓。