在一条老街的尽头,有一家小小的快递驿站。驿站的老周每天都收到街坊们零散的包裹:张婶的一封信,李叔的一个小盒子,王奶奶的一双布鞋。如果每来一件,老周就骑着小摩托进城送一趟,那一天得跑几十趟——发动摩托、过桥、排队,光是这些“路上的开销”,就比送件本身还费功夫。后来老周聪明了:在门口挂了个大邮袋,把零散包裹攒起来,等邮袋装满,或者等到下午三点这个固定时刻,再骑一趟车全部送出去。跑的次数变少了,一天送出的包裹反而多了好几倍。这个朴素的生活智慧,就是计算机里的批处理优化:把零散的小操作攒成一批,一次性处理,摊薄每一次操作背后的固定开销。这篇我们就跟着老周,把攒包的时机(合并策略)、袋子的大小(批量大小)、装不下的应对(缓冲区管理)、送件失败的处理(失败与部分成功),以及隔壁顺丰小哥(流式)与驿站(微批)的对比,都聊明白。

岗位关联

掌握批处理优化,是后端工程师写出高吞吐服务、做好数据库批量写入与 RPC 调优的必修课;读完本文,你能看懂线上系统的“攒批”代码,也能亲手设计出更优的批量方案。

目录
  1. 可视化演示
  2. 批处理概念
  3. 合并策略
  4. 批量大小
  5. 延迟与吞吐
  6. 缓冲区管理
  7. 失败与部分成功
  8. 流式与微批
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示零散请求逐个处理的高开销、缓冲区合并攒批、批量提交与吞吐对比,支持步骤切换与动态演示。

本节配有交互动效(进入视口后自动加载,约 3KB)。

零散请求逐个处理 每个请求单独提交开销大 频繁IO导致吞吐低下 N requests = N roundtrips 逐个处理每个请求产生大量重复开销

批处理概念

老周算了笔账:送一封信,骑车进城来回要 40 分钟,而路上真正花在“送这一封信”上的时间只有 5 分钟。那 40 分钟里,有 30 分钟是“不管送几封信都要花”的固定开销——发动车子、过桥、排队。只要车能装下,多带一件包裹,额外只多花 5 分钟。所以问题的关键不在“信多难送”,而在“路多难跑”。

计算机里也有类似的账。一次磁盘读写,真正传数据的时间很短,但寻道、旋转延迟、系统调用切换要付出固定的毫秒级代价;一次网络请求,真正传输字节很快,但 TCP 握手、RTT 往返固定在那里。如果业务是“来一个请求就发一个 RPC”,那每个请求都要付一份固定开销。把请求攒成批,一批只付一份固定开销,这就是批处理(batch processing)的出发点。

批处理的核心收益来自摊薄。设每个请求的可变成本为 v(数据序列化、逐条处理),每次“执行一批”的固定成本为 f(系统调用、网络往返、事务提交),批量大小为 B,则处理 n 个请求的总成本为:

这就像老周把 40 分钟进城费摊到 8 件包裹上,每件只摊 5 分钟。理解了“摊薄”,就理解了批处理为什么能提升吞吐——吞吐就是单位时间处理的件数,总开销降了,单位时间能处理的自然就多了。

Tbatch=n×v+nB×f\displaystyle T_{\text{batch}} = n \times v + \frac{n}{B} \times f
总成本由两部分构成:n 件包裹各自“分拣贴单”的可变成本,加上“跑一趟车”的固定成本被 B 件包裹分摊后的总和。
  1. n × vn 件包裹各自要付出的可变成本,比如分拣、贴单,这部分无论怎么攒批都省不掉,只能原样转嫁。
    n×vn \times v
  2. n / B批次数——每 B 件凑一车,总共要跑 n/B 趟;B 越大,跑的趟数越少。
    nB\displaystyle \frac{n}{B}
  3. × f固定开销总账——每趟车的油钱、过桥费,跑几趟就付几份。
    nB×f\displaystyle \frac{n}{B} \times f
  4. T_batch 总成本把两部分相加,就是攒批后的全部开销;B 增大时,固定成本部分不断缩小。
    Tbatch=n×v+nB×f\displaystyle T_{\text{batch}} = n \times v + \frac{n}{B} \times f

合并策略

街坊们发现,老周发车有两个规矩:一是邮袋装满 8 件立刻走;二是就算没装满,下午三点必须走。因为如果只等装满,赶上淡季一袋子等一天,张婶的信就误了时辰;如果只按时走,旺季邮袋爆了也装不下。两个规矩缺一不可。

这两个规矩就是批处理的两大触发策略:数量阈值(count-based)和时间阈值(time-based)。前者保证批足够大、吞吐高;后者保证延迟有上界,不会在低峰期无限等待。工业实现几乎都是两者的组合——谁先满足谁触发,称为 hybrid 策略。比如 Kafka Producer 的 batch.size 和 linger.ms,一个管体积、一个管时间,配合使用。

在纯时间窗口策略下,平均每批的大小可以用一个简单公式估计:平均批大小 = 到达率 × 窗口长度。窗口越大,或者请求越密集,攒出来的批就越大。

老周还有个隐藏技巧:同一栋楼的包裹他会扎在一起,同一收件人的多封信装进一个信封——这叫合并去重。攒批不只是“放一起”,更要聪明地分组:按 key 合并、把重复请求去重,减少了总件数,批的含金量更高。

Bavg=λ×T\displaystyle B_{\text{avg}} = \lambda \times T
平均批大小等于请求到达率 λ 与攒批窗口 T 的乘积:到了多少“货”,决定一车能拉多少。
  1. λ到达率——平均每秒涌进驿站的包裹数,反映业务忙闲程度。
    λ\lambda
  2. T攒批窗口——老周承诺的“最多等多久”,也就是时间阈值。
    TT
  3. B_avg二者相乘,得到平均每批的大小;窗口固定时,业务越忙批越大。
    Bavg=λ×T\displaystyle B_{\text{avg}} = \lambda \times T

批量大小

老周试过把邮袋换大两倍,发现收益没那么明显了:一趟能装 16 件当然好,但车子更重、山路更慢,一天反而少跑了好几趟。装 8 件和装 16 件,每件的进城成本确实从 5 分钟摊到 2.5 分钟,但从 16 件再到 32 件,收益就趋近于零了。袋子不是越大越好。

这就是边际收益递减:单件平均成本等于可变成本加摊薄后的固定成本,批量 B 越大,f/B 越小,但最终只能无限趋近于 v,永远不可能低于 v。而且 B 变大还有隐藏代价:内存占用增大、单批处理时间变长、数据库长事务持锁更久,甚至拖垮 GC。

所以批量大小需要基准测试(benchmark)来验证。实践中常用二分或自适应调节:先以 1、16、64、256 等档位压测,找到吞吐曲线变平缓的点,再结合延迟预算取一个折中值。公式上,单件平均成本为:

老周最终选了 12 件的邮袋:既能让每件摊到很低的进城成本,又不会把摩托压坏。批量大小的选择没有放之四海而皆准的答案,只有“在你的路况下压测出来的答案”。

Cavg=v+fB\displaystyle C_{\text{avg}} = v + \frac{f}{B}
单件平均成本 = 可变成本 + 摊薄后的固定成本;B 趋近无穷大时,成本趋近于 v,收益耗尽。
  1. v每件包裹的固有成本,比如分拣、贴单,这是下限,永远省不掉。
    vv
  2. f / B固定成本被 B 件包裹摊薄的部分;B 越大,摊得越薄。
    fB\displaystyle \frac{f}{B}
  3. C_avg两者相加为单件平均成本;曲线先陡降后趋平,拐点附近就是最优批量。
    Cavg=v+fB\displaystyle C_{\text{avg}} = v + \frac{f}{B}

延迟与吞吐

老周开始攒批后,张婶发现她的信要多等一会儿了——第一封信要在邮袋里躺到袋子满,或者躺到下午三点。攒批的本质是“用等待换一趟车”,所以它天然在延迟和吞吐之间做交易:一趟车装得越满,吞吐越高,但最早那件等得也越久。

计算机里这个交易可以用 Little 定律来理解:一个系统里平均“在途”的请求数 L,等于请求到达率 λ 乘以平均停留时间 W。攒批让请求在缓冲区里多停留了一段时间(W 变大),因此系统里积压的请求数也变多了;看上去吞吐上去了,但每一件都等得更久。

取舍的标准是业务场景:写日志、批量导入、离线分析是“吞吐优先”——多等 100ms 无所谓,多处理 10 倍数据更重要;而用户登录、下单支付是“延迟敏感”——宁可每趟少装点,也要保证 P99 延迟在 SLA 内。微服务里的常见做法是双通道:急件走流式直发,普通件走攒批。

老周最后的解法是“两点发车”:上午 11 点前的急件单独送,普通包裹攒批走。延迟和吞吐不是单选题,而是按包裹类型分配车道——这在线上有个术语叫分级调度。

L=λ×W\displaystyle L = \lambda \times W
Little 定律:系统内平均请求数 = 到达率 × 平均停留时间。攒批增大了 W,于是系统里“排着队”的请求变多。
  1. L系统内的平均请求数——驿站里正在排队等发车的包裹总量。
    LL
  2. λ平均到达率——每秒有多少新包裹涌进驿站。
    λ\lambda
  3. W平均停留时间——一件包裹从进门到发出的平均等待时长。
    WW
  4. L = λ × W三者联动;攒批抬高 W,L 随之上升,积压变多,延迟变差。
    L=λ×W\displaystyle L = \lambda \times W

缓冲区管理

双十一来了,街坊们的包裹像暴雨一样砸向驿站。邮袋装满了,后面的包裹怎么办?老周准备了后手:小仓库先堆着;仓库也满了,就在门口贴上“暂停收件,下午再来”;实在不行,只能拒收少数几件。邮袋、仓库、暂停收件,构成了完整的缓冲体系。

计算机里的“邮袋”就是缓冲区,它的管理直接决定批处理系统的稳定性。缓冲区满了,上游还在拼命发请求,就需要背压(backpressure)机制:让上游减速、阻塞等待,而不是无脑接收然后崩溃。常见的溢出策略有三种:丢弃新请求、阻塞生产者、把数据降级(如落盘暂存)。

缓冲区大小的选择也有数学模型。把系统看成一个容量为 K 的队列,利用率 ρ(到达率与处理能力之比)接近 1 时,队列迅速积压,溢出概率为:

老周的经验是:邮袋 8 件、仓库 50 件、超过就暂停收件。线上系统也一样——小缓冲区吸收抖动,水位监控报警,超过水位就触发背压。缓冲区不是越大越好,而是“刚好接得住抖动,又不至于让延迟失控”。

Ploss=ρK×(1−ρ)1−ρK+1\displaystyle P_{\text{loss}} = \frac{\rho^{K}\times (1-\rho)}{1-\rho^{K+1}}
缓冲区越大,溢出概率越低,但延迟越高;利用率越高,系统越高效,但也越容易溢出。
  1. ρ队列利用率——包裹到达速度与驿站处理速度的比值,越接近 1 说明越忙。
    ρ\rho
  2. K缓冲区容量——小仓库最多能堆多少件,K 越大越不容易爆仓。
    KK
  3. P_loss溢出概率——ρ 固定时,K 增大则指数级降低溢出风险,但代价是延迟变大。
    Ploss=ρK×(1−ρ)1−ρK+1\displaystyle P_{\text{loss}} = \frac{\rho^{K}\times (1-\rho)}{1-\rho^{K+1}}

失败与部分成功

有一天,老周拉着一车 12 件包裹进城,投递时发现李叔的地址写错了。他犯了难:整辆车拉回去,其他 11 户都得跟着白等;只退这一件,其他 11 件照常签收,代价最小。他当然选了后者。一车货里有一件出问题,绝不能整车翻。

批处理恰恰最容易遇到这种情况:一批 100 条 INSERT,第 37 条违反唯一约束,剩下 99 条怎么办?两种设计哲学:整批原子(all-or-nothing,要么全成功要么全回滚)和逐条部分成功(每一行单独报告状态)。对于批量写入,逐条处理是主流,因为整批原子会把 99 条成功的也一起浪费掉。

还有两个配套机制。一是幂等重试:重试前给每条操作带上唯一 ID,服务端去重,避免“送了两遍”;二是死信队列:重试 N 次仍失败的消息丢进专门的队列,等人工或补偿任务处理。整批全部成功的概率也可以用概率论估算——单件成功率为 p 时,B 件全部成功的概率是:

老周把地址错的包裹退回驿站,贴上标签等李叔来改,这就是死信队列;他给每件包裹编了号,即使送错再送,李叔也知道不是重复收件——这就是幂等。批量系统要像老周一样:不因一件错而整车翻,也不让一件错悄悄消失。

Pbatch=pB\displaystyle P_{\text{batch}} = p^{B}
整批全部成功 = 每一件都成功;B 越大,这个概率指数级下降,所以“逐条处理”几乎是必然选择。
  1. p单件成功率——一件包裹准确送达的概率,来自历史统计。
    pp
  2. B批量大小——这一车装了多少件。
    BB
  3. p^BB 件同时成功的概率;批量从 8 变 64,成功概率从 0.9 的 8 次方掉到 0.9 的 64 次方,几乎归零。
    Pbatch=pB\displaystyle P_{\text{batch}} = p^{B}

流式与微批

老城另一头新开了一家“顺丰急送”。它的原则是:来一件送一件,不攒批,代价是每件都要付全额进城费,但延迟最低。老周的驿站则是微批模式:攒一车送一车,吞吐高、延迟高。两家的生意都好,因为客人不同——急件找顺丰,普通包裹找老周。

计算机里对应流式处理(streaming)和微批处理(micro-batch)。流式逐条处理,延迟最低,但每次都要付固定开销;微批把到达的数据按窗口切成一段段小批,固定开销被摊薄,吞吐更高。典型代表是 Spark Streaming 的微批模式与 Flink 的流式模式,二者的取舍正是延迟和吞吐的经典博弈。

微批还有个方便估算的延迟特点:在固定窗口 T 内,一条数据到达的时刻是随机的,它平均要等窗口结束,也就是大约 T/2 的时间才被处理。窗口缩小一半,延迟减半,但批数翻倍、吞吐下降——这是微批调参最核心的跷跷板。

老周最近也学着“混合经营”:加了急件专线,普通件继续攒批。技术选型同理:延迟敏感的支付风控走流式,批量导入、日志分析走微批。没有哪个模式绝对更好,只有合不合适——这永远是批处理优化的最终答案。

Wbatch=Twindow2\displaystyle W_{\text{batch}} = \frac{T_{\text{window}}}{2}
微批模式下,一条数据平均要等窗口的一半时间才被处理;窗口越短延迟越低,但吞吐也越低。
  1. T_window微批窗口长度——老周攒一车货的固定间隔。
    TwindowT_{\text{window}}
  2. ÷2数据到达时刻在窗口内均匀分布,平均等待时间恰好是窗口的一半。
    12\displaystyle \frac{1}{2}
  3. W_batch平均等待时间——窗口越短,等待越短,但发车次数变多、固定开销变大。
    Wbatch=Twindow2\displaystyle W_{\text{batch}} = \frac{T_{\text{window}}}{2}

AI 应用场景

数据库批量写入

阶段:数据接入与存储优化

解决的问题:传统逐条 INSERT 时,每一行都要经历一次 SQL 解析、网络往返、事务提交,写 1 万行等于 1 万次固定开销;批量写入把多行拼进一条 INSERT 语句,固定开销被摊薄到千分之一,写入吞吐大幅提升。

具体案例:某后端服务把用户行为日志攒在内存列表里,满 500 条或超过 200ms 就用一条多行 VALUES 语句刷入 MySQL。压测显示,写入吞吐从约 3000 rows/s 提升到约 2 万 rows/s,数据库连接数反而下降。这正是老周“装满发车 + 到点发车”双阈值的直接应用。

微服务 RPC 批量接口

阶段:服务调用与性能优化

解决的问题:前端列表页循环调用 20 次单查接口,就有 20 次网络往返、20 次序列化开销;把调用攒成一笔批量 RPC 后,还能在服务端统一做缓存和去重,整体延迟反而更低。

具体案例:订单服务原先的 GetOrder 接口在高峰期 QPS 吃紧。团队新增 GetOrders 批量接口,网关把 200ms 窗口内的取订单请求合并成一次内部 RPC,外部接口时延从 50ms 降到 30ms,内部调用次数减少 80%。这批合并窗口,就是老周挂在门口的邮袋。

日志与指标聚合上传

阶段:数据管道与可观测性建设

解决的问题:日志采集 Agent 如果每产生一条日志就立即上报,海量小包会打满网卡、耗尽 Kafka 连接;攒批后统一压缩上传,网络流量和成本同步下降。

具体案例:某日志采集 Agent 攒够 4KB 或超过 1 秒才批量上报到 Kafka,并开启 gzip 压缩。上线后网络流量下降约 80%,Kafka 分区写入压力明显缓解——邮袋从“一件一送”变成了“一车一送”。

Python 演示

python56 行
import time  class BatchBuffer:    # 快递驿站: 攒齐一批再发货    def __init__(self, batch_size, max_wait, flush_cost):        self.batch_size = batch_size        self.max_wait = max_wait        self.flush_cost = flush_cost        self.items = []        self.sent = 0        self.batches = 0        self.overhead = 0.0        self.wait_sum = 0.0        self.last_flush = time.time()     def put(self, now):        # 收到一件包裹, 放进邮袋        self.items.append(now)        if len(self.items) >= self.batch_size:            self.flush(now)     def tick(self, now):        # 到点强制发车, 防止无限等待        if self.items and now - self.last_flush >= self.max_wait:            self.flush(now)     def flush(self, now):        # 发车: 一次性清空邮袋, 但只付一次固定开销        if not self.items:            return        self.wait_sum += sum(now - t for t in self.items)        self.sent += len(self.items)        self.batches += 1        self.overhead += self.flush_cost        self.items = []        self.last_flush = now  def run(batch_size, total=2000, flush_cost=0.01, max_wait=0.05):    # 模拟 2000 件包裹依次到达, 统计批次数/总开销/平均等待    buf = BatchBuffer(batch_size, max_wait, flush_cost)    for i in range(total):        buf.put(i * 0.001)        buf.tick(i * 0.001)    buf.flush(total * 0.001)    avg_wait = buf.wait_sum / buf.sent    return buf.batches, buf.overhead, avg_wait  if __name__ == "__main__":    # 对比不同批量大小: 越大批次越少, 总开销越低, 但等待越长    print("batch_size  batches  overhead/s  avg_wait/s")    for bs in (1, 16, 64, 256):        batches, overhead, avg_wait = run(bs)        print("%-10d %-8d %.3f        %.4f" % (bs, batches, overhead, avg_wait))
目的:模拟一个带“数量阈值 + 时间阈值”双触发的攒批缓冲区,对比不同批量大小下的发货次数、总固定开销与平均等待时间,直观演示批处理优化的收益与代价。
  1. class BatchBuffer:驿站本体:封装了邮袋(items)、数量阈值、时间阈值和每次发货的固定成本。
  2. def put(self, now):收到一件包裹先塞进邮袋,一旦数量达标立刻触发 flush。
  3. if len(self.items) >= self.batch_size:数量触发条件:邮袋装满 8 件立即发车。
  4. def tick(self, now):定时检查:即使没装满,超过 max_wait 也必须发车,保证延迟有上界。
  5. def flush(self, now):发车动作:一次性清空邮袋、累计等待时间,并且只加一次 flush_cost。
  6. self.overhead += self.flush_cost每趟车只付一份固定开销,这就是“摊薄”的落点。
  7. buf.flush(total * 0.001)结束时兜底清空,避免残留包裹。
  8. for bs in (1, 16, 64, 256):对照实验:批量从 1 涨到 256,批次从 2000 降到约 40,开销从 20 秒降到约 0.4 秒。

常见误区

误区“既然攒批能摊薄固定开销,那我把批量调到 10000,吞吐不就起飞了吗?延迟高点无所谓,反正吞吐上去了。”

正解批量增大的收益是边际递减的:单件平均成本 = v + f/B,B 大到一定程度后曲线趋平,再增大只换来延迟暴涨和内存压力。就像老周的摩托,装 8 件和 16 件都是跑一趟,但硬装 200 件就得换卡车、雇装卸工,反而更慢。批量大小必须靠基准测试找拐点。

为何常见:人们只看到“固定开销被摊薄”这个线性增长的早期阶段,忽略了资源约束和边际递减的物理限制。

误区“批处理和低延迟是死对头,只要用了批处理,延迟就控制不住了,所以低延迟系统绝不能攒批。”

正解攒批确实给最早的请求增加了等待,但加上 max_wait 时间阈值后,延迟有明确上界,是“可控的等待”而不是“失控的堆积”。老周承诺下午三点必发车,张婶的信最晚也就等到三点。实践中更常用双通道:急件走流式直发,普通件走攒批,两者并不互斥。

为何常见:把“延迟增加”和“延迟失控”混为一谈,也忽略了时间阈值这一关键约束的存在。

误区“反正要攒批,只要有请求来了就统统丢进同一个缓冲区,满了就发,逻辑简单又高效。”

正解无脑合并会破坏局部性:不同 key、不同优先级的请求混在一起,一条慢请求可能拖垮整批;同一收件人的包裹绑在一起才是好批。有效的合并策略要考虑按 key 分组、合并去重、数量/时间/空闲三种触发条件的组合,而不是简单的一锅烩。

为何常见:把“策略”(什么时候发、怎么分组)和“机制”(攒起来发)混为一谈,以为攒批只是加一个缓冲区的事。

误区“这批里有一条失败了,那就整批重发呗,反正我有重试机制,多试几次总能成功。”

正解整批重发会放大写入量,还可能造成大量重复数据。正确做法是把部分成功的结果逐条落账,只对失败项做幂等重试(带上唯一 ID 去重);多次失败的进死信队列人工处理。老周绝不会因为李叔一件地址错了,就把另外 11 件全部重新送一遍。

为何常见:习惯了单请求“失败就重发”的思维,忽略了批量场景下失败概率被放大的事实——B 越大,整批全成功的概率 p^B 越低,部分成功才是常态。

面试题

Q1. 批处理提升吞吐的核心原因是什么?

单选题 · easy

  • A.减少了单件可变成本
  • B.摊薄了每批固定开销← 正确答案
  • C.消除了全部网络开销
  • D.提高了单个请求速度

答案:B。批处理把 n 个请求攒成一车,固定开销(网络往返、系统调用)只付一次,所以核心是摊薄固定成本。A 错在可变成本(分拣贴单)省不掉;C 错在开销还在,只是平摊了;D 错在单个请求的速度并没有变化,变的是总量。就像老周骑车进城一次送 8 封信,单封信依然要走 5 分钟分拣,但每封信摊到的进城时间从 40 分钟降到了 5 分钟。

Q2. 攒批时为了给延迟设一个明确上界,通常采用哪种策略?

单选题 · easy

  • A.强制刷新阈值
  • B.最长等待时间← 正确答案
  • C.随机丢弃请求
  • D.无限扩大缓冲区

答案:B。最长等待时间(max_wait / linger.ms)保证即使数量没攒够,到点也必须发车,把延迟控制在已知范围内。A 的“强制刷新”不是标准术语;C 随机丢弃会丢数据;D 无限扩大缓冲区只会让延迟失控。老周的“下午三点必发车”就是这个策略,张婶的信最坏也就等到三点。

Q3. Little 定律 L = λ × W 中,W 表示什么?

单选题 · mid

  • A.每秒到达率
  • B.系统内请求数
  • C.平均停留时间← 正确答案
  • D.批量大小上限

答案:C。Little 定律是排队论的基本结论:系统内平均请求数 L = 到达率 λ × 平均停留时间 W。W 是每件包裹从进门到发出的平均等待时长。A 是 λ 的定义,B 是 L 的定义,D 与定律无关。攒批恰恰是通过增大 W 来换取更高的吞吐。

Q4. 典型的攒批触发条件有哪些?

多选题 · mid

  • A.数量达到阈值← 正确答案
  • B.时间达到窗口← 正确答案
  • C.数量时间先到← 正确答案
  • D.每请求立即发送

答案:ABC。数量阈值保证批足够大、吞吐高;时间阈值保证延迟有上界;组合策略(谁先到谁触发)是工业界最常用的 hybrid 方案。D 是“不攒批”的逐条发送,与批处理相反。老周的邮袋就是“装满 8 件”或“下午三点”这两个条件的组合。

Q5. 批量大小无限增大的主要问题是什么?

单选题 · mid

  • A.吞吐量单调下降
  • B.延迟增长内存压力← 正确答案
  • C.固定开销变得更大
  • D.完全没有任何收益

答案:B。批量增大后,摊薄收益趋近于 v 而耗尽,延迟却随攒批时间线性增长,内存占用也随之上升,还可能拉长数据库事务持锁时间。A 的“单调下降”过于绝对,通常是先升后平;C 固定开销没有变大;D 说完全没收益也不对,只是收益递减。

Q6. 面对批处理的部分成功,正确的做法有哪些?

多选题 · hard

  • A.整批原子回滚
  • B.逐条记录成功失败← 正确答案
  • C.对失败项单独重试← 正确答案
  • D.直接丢弃失败项

答案:BC。批量写入的主流做法是逐条报告状态(B),只对失败项做幂等重试(C)。A 整批原子回滚会把成功项也浪费掉,只在强一致的极少数场景使用;D 直接丢弃失败项会造成数据丢失。老周绝不会因为李叔一件地址错了就把整车 12 件退回去,而是只退那一件。

Q7. 在纯时间窗口攒批中,平均每批大小等于什么?

单选题 · mid

  • A.到达率乘以窗口← 正确答案
  • B.批量总数除以窗口
  • C.窗口除以到达率
  • D.始终等于阈值批量

答案:A。平均批大小 B_avg = λ × T,到达率 λ 乘以窗口长度 T。窗口固定时,业务越忙批越大。C 是倒数关系,不符合常识;D 忽略了一个窗口内可能攒不满固定的数量阈值。老周下午三点发车时邮袋里到底有几件,取决于街坊们这半天送来了多少。

Q8. 缓冲区已满时,让上游放慢速度等待的机制叫什么?

单选题 · easy

  • A.弹性伸缩机制
  • B.背压控制机制← 正确答案
  • C.死信队列机制
  • D.流水线处理机制

答案:B。背压(backpressure)是指下游处理不过来时,向上游传递减速信号,让生产者阻塞或降速,而不是硬扛到崩溃。老周在驿站门口贴“暂停收件,下午再来”就是最朴素的背压。A、C、D 分别是扩容、失败隔离、并行流水线,与本问题无关。

Q9. 关于流式处理与微批处理,下列说法正确的是?

多选题 · hard

  • A.微批平均等待约为窗口一半← 正确答案
  • B.流式处理的延迟通常更低← 正确答案
  • C.微批处理的吞吐通常更高← 正确答案
  • D.流式延迟总是低于微批

答案:ABC。微批模式下数据平均等 T/2 才被处理(A 对);流式逐条处理,省去了攒批等待,延迟通常更低(B 对);微批摊薄了固定开销,吞吐通常更高(C 对)。D 的“总是”过于绝对,流式在高并发下也可能因每次付全额固定开销而吞吐受限,延迟不一定永远低。

Q10. 单件成功率为 p 时,整批 B 件全部成功的概率是 p^B,这一事实说明什么?

单选题 · hard

  • A.增大批量降低失败率
  • B.放弃批量改为逐件发送
  • C.逐条处理而非整批原子← 正确答案
  • D.减少批量固定开销占比

答案:C。p^B 随 B 增大指数级下降,因此批量越大,整批原子提交失败的概率越高,成熟系统普遍采用逐条确认、部分成功的方案,把失败控制在单条范围内。A 与事实相反;B 因噎废食,忽略了批处理的巨大收益;D 与失败概率无关。老周一车 12 件里坏 1 件,只退 1 件——这就是逐条处理。