面试系列 04|操作系统与网络面试:从调度、内存到 RPC 背压的 Staff 追问链

面向数据库内核与 AI 基础设施岗位,用十四组问题贯穿调度、缺页、大页、文件 I/O、TCP、RPC、RDMA、eBPF 与容器资源治理,并给出版本边界与故障验证条件。

系列导航:面试索引 · 代码评审与 Agentic 研发

沿请求路径区分资源未获得、资源正在工作、结果尚未消费,再解释排队、阻塞和超时。 每项优化都要说明内存何时释放、反馈能否停到上游,以及失败后谁继续推进完成与清理。

一、先回答谁能运行,再讨论线程数量

1. 线程池扩容后,为什么吞吐不涨而长尾变差?

强答要点:先确定容器实际可用的处理器集合、带宽配额和任务到达率,再看线程是在运行、可运行但未被调度,还是睡眠等待。主机平均利用率不能代表该容器的可运行预算;同样,睡眠线程多也不能直接推出磁盘慢。

Linux 使用任务表示可调度的执行实体,同一进程的线程通常共享地址空间和文件描述符表。跨进程切换可能更换地址空间;同进程切换仍有寄存器、栈、调度器和缓存局部性成本,不能概括为“只切寄存器”。任务模型与共享关系参见 clone(2)。

把一次请求拆成接入排队、执行器排队、CPU 执行、锁等待、设备等待和完成回调六段。可运行队列等待增长支持调度竞争假设;锁持有者长时间不能运行则可能是配额或抢占放大了锁等待;读请求耗时增长还要区分提交前排队与设备服务时间。

工作线程数至少受三个预算约束:可用 CPU、下游可承受的在途操作、每个在途任务占用的内存。I/O 等待占比只能提供起始估计;如果增加线程导致内存回收、争用或下游排队,原来的等待占比也会改变,固定公式就失效了。

调度还必须说明版本。完全公平调度器(Completely Fair Scheduler,CFS)是经典背景;现代公平调度路径还需理解最早合格虚拟截止期优先(Earliest Eligible Virtual Deadline First,EEVDF)。该算法以公平份额的欠账判断资格,在合格任务中选择虚拟截止期较早者;虚拟截止期不是业务请求的真实截止时间。参见 EEVDF 官方说明。

追问:线程都处于运行状态,为什么仍慢?运行状态包括正在运行与等待调度。测量唤醒到实际运行的间隔,核对亲和性、容器节流和同核竞争;不要把状态字母当成 CPU 计时器;换成用户态协程是否解决?协程可以减少阻塞时占用的内核线程,但承载线程被节流或被阻塞调用占住,协程仍无法推进。CPU 密集计算也不会因协程数量增加获得更多执行预算。

差答:“线程数等于核数”“I/O 密集就无限加线程”“CPU 不满说明没有 CPU 瓶颈”。

【模板】填一例真实线程池调整:内核与容器配置、原先判断、支持或推翻判断的指标、最终并发预算、本人负责部分。没有可披露数据时保留定性结论,不填虚构百分比。

2. 为什么容器有“空闲 CPU”和“可用内存”,请求仍会被卡住?

强答要点:区分资源权重、资源上限和资源放置。cpu.weight 决定竞争时的相对份额,cpu.max 限制周期内 CPU 时间,cpuset.cpus.effective 描述可用 CPU 集合;三者不能互相代替。这里的 CPU 带宽推演按普通公平调度任务讨论,不能直接套用到实时调度任务或任意 sched_ext 策略。

内存的 memory.high 是节流和回收压力边界,memory.max 是硬限制,无法通过回收降低占用时可能触发组内内存不足终止(Out-of-Memory Kill,OOM Kill),但占用在某些情况下可暂时超过该值;文件缓存等也可能进入控制组内存记账。具体接口与层级约束参见 cgroup v2 官方文档。子组配置不能绕过祖先组的资源限制。

假设 cpu.max 为 200000 100000,仅用于说明平均两核的时间预算。在没有其他限制时,八个线程同时执行 CPU 工作可能约 25 毫秒消耗完该周期预算,然后等待补充。实际节流时长受执行分布、层级和突发配置影响,这不是每个周期都会出现的实测曲线。

因此,周期性尖峰要对齐 cpu.stat 中节流相关计数的增量与请求时间线。内存变慢要对齐 memory.events、匿名页与文件页分布、回收活动和压力停顿信息(Pressure Stall Information,PSI),而不是只比较进程常驻内存与上限。

压力停顿信息描述 CPU、内存和 I/O 资源争用造成的停顿;some 表示至少部分任务受影响,内存与 I/O 的 full 可表示所有非空闲任务同时停顿的时间。系统级 CPU full 没有定义,自 Linux 5.13 起为兼容性报告为零;控制组级 CPU full 才有相应停顿统计。参见 PSI 文档。

追问:CPU 限制为两核,绑定两个核是否等价?不等价。配额是时间带宽,绑定是放置限制;后者影响缓存与迁移,前者允许短时在多个核上消耗预算。还要说明是否与其他任务共享物理核;memory.high 上升为什么影响网络请求?分配可能进入直接回收,使接入或回调线程停顿;如果接收缓冲和应用队列继续增长,停顿又反过来延长缓冲占用。需要分配栈与控制组事件支持这条因果链。

差答:“容器是轻量虚拟机,所以有独立内核”“cpu.weight 等于核数”“只要没到 memory.max 就不会受内存限制”。

二、地址空间不等于物理内存,分配成功不等于访问便宜

3. 分配成功后第一次扫描变慢,如何解释缺页和内存承诺?

强答要点:先分开地址保留、实际物理页、页表映射和资源记账。申请返回只说明某个分配路径成功,不保证未来访问所有页面都有足够物理内存或控制组预算;是否允许过量承诺还取决于系统策略。

快表(Translation Lookaside Buffer,TLB)未命中通常触发页表遍历,不等于缺页。缺页可能只是为匿名页分配和清零、建立已有文件页的映射或执行写时复制(Copy-on-Write,COW),也可能需要读取后备存储。Linux 的次缺页与主缺页计数按是否需要 I/O 区分,参见 getrusage(2)。

内存过量承诺(Memory Overcommit)有独立策略,参见 Linux 内存承诺文档。大地址空间可以依赖按需映射与稀疏访问存在,但持续活跃工作集超过可承受内存时,性能与生存性都没有保证。

排查要保留匿名映射还是文件映射、首次读还是首次写、是否存在交换空间、容器预算、分配器行为和非统一内存访问(Non-Uniform Memory Access,NUMA)拓扑。只报“缺页很多”不能区分初始化清零、远端访问和回收后的重新读取。

在可控启动阶段预先触碰页面,可以把部分缺页成本从请求路径移走,同时提前暴露预算不足;代价是启动更慢、预占更多物理内存,也可能在错误 NUMA 节点建立放置。它不适合无差别触碰远大于实际工作集的地址空间。

追问:mmap 是否意味着零拷贝且没有 I/O?文件映射建立访问关系,实际访问仍可能缺页读取;应用省掉显式读入私有缓冲的动作,不代表磁盘数据凭空进入内存;fork 为什么快,之后为什么可能突然变慢?页表复制与写时复制避免立即复制所有数据;后续写入触发复制。并发写入量、页表规模与大页行为决定尖峰,不能把 fork 宣称为完全不阻塞。

差答:“虚拟内存就是磁盘上的交换区”“TLB 未命中一定读盘”“分配成功就保证不发生内存不足”。

brk 与 mmap 的选择不遵循固定 128 KB 分界;分配器版本、空闲块与动态阈值都会改变路径。

4. 大页为什么能改善吞吐,也可能制造长尾?

强答要点:先说明目标是减少地址翻译开销,而不是泛称“内存更连续所以所有访问更快”。大页扩大单个地址翻译项覆盖范围,但分配、清零、规整、合并和内部浪费可能提高请求路径成本。

在基础页为 4 KiB、页大小为 2 MiB 的例子中,一页覆盖的基础页数量为 512;这只是覆盖范围的算术关系,不是查询加速倍数。显式预留大页与透明大页的分配、回收和管理方式不同,参见 HugeTLB 文档。

2024 年 Linux 6.8 文档已描述多尺寸透明大页(Multi-size Transparent Huge Pages,mTHP):匿名内存可使用介于基础页和传统大页之间的粒度,而且可继续通过页表项映射。较小粒度减少单次清零量,但具体地址翻译收益依赖架构。参见 Linux 6.8 透明大页文档。

长期存活、访问密集且地址翻译开销明显的内存池,是优先实验对象;大量稀疏访问、短命分配或极严尾延迟场景要更谨慎。先对选定内存范围提出建议,再讨论全机策略,避免让一个服务的吞吐目标替所有租户决定内存策略。

验证必须同时看翻译事件、CPU 时间、实际大页覆盖、规整与回收活动,以及请求延迟。只看透明大页开关不能证明目标映射已经使用大页;只看吞吐提高也无法解释内存峰值是否超出了服务预算。

追问:开启后为什么没变化?可能工作集不受地址翻译限制,目标映射不符合条件,或未实际获得大页。先确认映射与硬件事件,不能直接判定功能无效;大页越大越好吗?越大,覆盖收益可能越高,但分配与清零粒度、稀疏使用浪费也越大。多尺寸选择需要结合架构与真实访问分布,不能只比较页数量。

差答:“大页固定是 2 MB”“开启透明大页就保证使用”“任何数据库都应关闭或开启”。

三、I/O 接口改变了什么,又没有改变什么

5. 数据库已有缓存,是否应该使用 O_DIRECT?

强答要点:先画清各层缓存存的是什么。压缩文件页和解压列块不是完全重复的数据;是否值得保留双层缓存,取决于复用率、解压成本、内存预算和顺序扫描对其他租户的干扰。

O_DIRECT 的缓冲地址、长度和偏移约束依赖文件系统与内核;Linux 6.1 起可在支持的文件上通过 statx 的 STATX_DIOALIGN 查询对齐信息。直接 I/O 也不等于 O_SYNC,不能据此宣布数据已持久化。参见 open(2)。

页缓存路径提供内核缓存、预读和写回机制,应用仍可能受复制与回收成本影响。直接路径减少数据经页缓存的缓存过程,却把缓冲复用、对齐、预取和并发组织交给应用。这里的成本迁移才是方案比较的核心。

fsync 与 fdatasync 用于同步文件状态,但创建或重命名后的目录项持久性需要另行考虑父目录同步。参见 fsync(2)。日志协议还要说明顺序、错误传播和设备缓存语义,不能只换一个打开标志。

一次性大扫描可能污染缓存,重复小读可能受益于页缓存;但缓存污染也可通过访问建议和容量管理缓解。应用具备成熟块缓存、预取和内存治理时,直接 I/O 才更容易形成完整方案;这些是选型条件,不是所有数据库的固定答案。

追问:如何公平比较两条路径?固定文件系统、设备、请求大小、队列深度、读写比例、数据压缩状态和总内存预算;分别记录冷缓存与热缓存,避免一边重复读缓存,一边持续读设备;可以混用缓存读写、直接读写与映射吗?重叠范围会引入一致性与协调成本,也可能损害性能;应优先统一访问策略,确需混用时按文件系统支持验证,不把它当成免费并行优化。

差答:“数据库有缓存所以必须直接 I/O”“直接 I/O 完全绕过内核”“写成功即落盘”。

6. io_uring 是完成接口,为什么仍可能消耗很多线程和 CPU?

强答要点:io_uring 是通过共享环组织提交与完成的异步接口,不保证每项操作都由设备原生异步完成,也不保证零系统调用、零复制或按提交顺序完成。

提交队列(Submission Queue,SQ)承载请求,完成队列(Completion Queue,CQ)返回结果;完成项的结果字段按操作类型表达返回值,例如读写的字节数、accept 的新描述符或某些操作成功时的零;失败通常为负错误码。需要阻塞执行的路径可转由工作线程处理。参见 io_uring(7)。

应用需给请求稳定标识,区分准备、已提交、业务超时、取消请求已提交、目标操作终结和资源可回收。业务超时可以结束对调用者的等待,但不能据此判断内核已停止访问缓冲。

取消请求本身也有完成结果,并与目标请求存在竞态;目标可能已完成、正在执行或成功被取消。参见 io_uring_prep_cancel(3)。取消结果必须与目标操作的终结事件共同解释,不能只等取消项就释放目标上下文。

最小安全不变量是:调用者完成状态只提交一次,资源须等其对应的底层访问与必要释放通知结束后才能归还;请求上下文还须保留到所有仍会引用它的事件处理完毕。引用计数只是实现工具,还必须防止请求标识被过早复用、完成项命中新对象,以及回调线程与取消线程同时完成用户承诺。

1准备 → 提交 → 等待目标完成 → 目标终结 → 回收
2              │
3              └→ 业务超时 → 请求取消 ─┘
4                 调用者可返回,资源继续由运行时持有

这张图只表达普通单次操作的生命周期。多次完成操作、零拷贝通知和跳过部分完成项的选项,需要扩展终结条件;不能照搬“一次提交一定一个完成项”的简化假设。

追问:队列越深,吞吐越好吗?超过设备有效并行度后,新增请求主要增加排队与缓冲占用。分别测提交等待、底层服务和完成消费时间,限制在途请求与字节数;开启提交轮询是否免费减少系统调用?提交轮询(Submission Queue Polling,SQPOLL)用轮询线程换取部分提交开销下降;需要计入其 CPU、唤醒与容器资源预算,不能只统计业务线程。

差答:“io_uring 比 epoll 新,所以全面替换”“业务超时后立刻释放缓冲”“每个请求只可能收到一个事件”。

7. epoll 的边缘触发如何兼顾不漏事件和公平性?

强答要点:先区分就绪通知与数据完成。epoll 返回某文件描述符(File Descriptor,FD)可读,不保证后续读取不遇到竞争,也不保证一次读能得到完整应用消息。

水平触发(Level-triggered,LT)与边缘触发(Edge-triggered,ET)是不同通知方式;边缘模式应使用非阻塞描述符,并在等待下一次内核通知前把当前就绪状态处理到不能继续。参见 epoll(7)。

若为公平性设置每轮字节或时间预算,预算用完但尚未读至 EAGAIN 时,把连接放入用户态就绪队列,主动安排后续轮次。只有确认暂时不可继续,才把推进责任交还内核事件;否则应用把“主动暂停”误写成了“等待新边缘”。

写路径也需状态机:处理部分写入,剩余数据保留偏移;遇到暂不可写才等待可写通知,发送队列清空后撤销不必要的关注。否则长期可写的连接可能制造无意义唤醒。

epoll 维护关注与就绪集合,但返回多个事件、复制事件数组和处理这些事件都有成本;不能把整个服务的复杂度标成 O(1)。它仍受描述符上限、内存及内核资源限制,同一手册也描述了相关监控限制。

追问:收到可读后另一个线程先读走数据怎么办?非阻塞读取允许返回 EAGAIN,应用应回到等待状态;事件不是对字节的独占承诺。最好明确单连接的读取所有者;EPOLLONESHOT 能解决所有并发吗?它可以帮助控制事件交付,但重新启用、关闭、超时和对象回收仍需同步;不能靠一个标志替代连接状态机。

差答:“边缘触发只通知一次,所以更可靠”“没读完会丢掉 TCP 数据”“epoll 无限 FD 且没有任何复制”。

网络消息分帧属于连接状态机:长度前缀需要校验最大长度与整数溢出,累计读取需要限制内存。单独使用 Protocol Buffers(协议缓冲序列化格式)并不能自动定义 TCP 字节流上的消息边界。

8. 零拷贝省掉哪一次复制,谁负责缓冲区的后半生?

强答要点:在图上标出文件、页缓存、用户缓冲、套接字缓冲和网卡,再指出省掉的边。设备直接内存访问(Direct Memory Access,DMA)仍然搬运数据;少一次 CPU 复制不意味着没有数据传输、协议处理或生命周期成本。

sendfile 可在内核中组织文件到输出描述符的传输,避免先读入用户缓冲再写回的路径;它不等于所有平台、文件系统与发送配置都没有内部复制。参见 sendfile(2)。

MSG_ZEROCOPY 以页固定和完成通知等开销换取复制规避,调用返回后不能立即修改仍在使用的缓冲;应用需消费错误队列中的释放通知,路径也可能回退到复制。参见 Linux MSG_ZEROCOPY 文档。

是否获益取决于消息大小、内存带宽、页管理与完成消费成本。小消息不能摊薄固定开销,大消息在慢接收方下又会长期占用页;所以应同时限制未释放字节数,不能只限制发送调用次数。

io_uring 的零拷贝发送有普通发送完成与缓冲释放通知的区别;后续是否有通知需检查完成标志,不能无条件假定恰好两次完成。参见 io_uring_prep_send_zc(3)。

追问:发送完成是否表示对端已经处理?不是。发送接口完成、缓冲可重用、TCP 确认和对端业务提交分别属于不同契约;必须明确这次优化关注哪个边界;加密与压缩后还可以零拷贝吗?要看变换发生在用户态、内核还是设备,以及输出是否必须重新物化。先画变换后的路径,不能把明文文件发送结论移植到任意加密链路。

差答:“DMA 就是零拷贝”“少两次复制一定快”“完成回调后所有内存都能回收”。

Linux 6.15 文档已经给出 io_uring 零拷贝接收:报头仍由内核 TCP 栈处理,负载直接进入注册的用户内存;要求网卡支持报头与数据分离、流量定向和接收侧扩展(Receive Side Scaling,RSS)。参见 Linux 6.15 零拷贝接收文档。这不是任意旧网卡都可启用的软件开关。

2024 年 Linux 6.12 的设备内存 TCP(Device Memory TCP)文档描述了负载直接进入设备内存、报头进入主机内存的接收路径。参见 Linux 6.12 设备内存 TCP 文档。该路径要求兼容的网卡及驱动支持报头分离、流量定向和 RSS,需将 DMA-BUF 绑定到接收队列,并由应用通过 recvmsg(MSG_SOCK_DEVMEM) 接收和按接口归还缓冲;不能仅凭升级内核就认定已有通用 GPU 直收能力。它与用户内存零拷贝接收有共同的路径拆分思路,但目标内存与接口不同。

模型服务跨机传输时,应先问最终数据要进入主机还是设备,以及拓扑是否支持直接路径;不能把“采用 TCP”与“必须经过主机负载复制”画等号,也不能由此宣称任何部署都优于 RDMA。

四、网络吞吐由多个控制环共同决定

9. 换成 BBR 后,为什么吞吐与长尾仍可能没有改善?

强答要点:区分应用生产速率、连接发送缓冲、接收方通告窗口、拥塞窗口与实际链路容量。TCP 提供有序字节流,但不能保证应用及时生产、消费或提交。

稳态近似下,吞吐受可用带宽、有效在途窗口除以往返时间(Round-trip Time,RTT)、应用供给和消费速率中的较小者限制。这个近似帮助选择观测,不适用于直接预测短流启动过程或突发丢包恢复。

例如假设路径为 100 Gbit/s、往返时间 1 毫秒,则带宽时延积(Bandwidth-delay Product,BDP)约为 12.5 MB。这是单位换算后的推演,不是某云产品参数;若目标在途量远小于它,即使链路无拥塞,也难持续填满该假设路径。

BBR 根据瓶颈带宽与传播往返时延建立模型,调节发送节奏和在途数据;不同代际对丢包、显式拥塞通知及探测有不同处理。Google 官方提供 v3 测试分支和文档,不能从模块名 bbr 推断实现就是 v3。参见 Google BBR 官方入口与 BBRv3 发布说明。

方案比较要保留短流与长流、往返时延、随机损失与排队损失、竞争流、接收端处理速度和 CPU 配置。单连接吞吐提高不代表共享瓶颈上公平性或请求长尾改善,也不证明拥塞控制替应用解决了过载。

追问:如何区分接收窗口小与拥塞窗口小?结合连接状态、通告窗口、在途字节、重传和接收队列;如果接收应用长期不读,提高拥塞窗口并不能创造接收容量;看到重传就能认定网络拥塞吗?不能。损失、乱序、路径变化与观测位置都影响解释;还要排查主机丢包、网卡队列和对端处理,不能只数某一个计数器。

差答:“BBR 不看丢包”“BBR 永远比 CUBIC 快”“Linux 默认算法在任何环境都一样”。

10. TIME_WAIT 太多,应先调哪个参数?

强答要点:先定位主动关闭者、连接方向、失败错误、地址四元组与周转率。连接数量本身不是故障结论;监听端可以通过不同对端共享同一本地端口,客户端到固定目的地的临时端口约束却可能成为瓶颈。

TCP 的关闭状态机保留半关闭能力,结束不要求恰好四个独立报文;确认与结束标志可以合并。TIME_WAIT 用于处理关闭末尾的重传与旧报文问题,同时关闭也可使双方进入该状态。参见 RFC 9293。

tcp_fin_timeout 针对孤儿连接的 FIN_WAIT_2 行为,不是通用的 TIME_WAIT 调节器;somaxconn 也不是该状态数量的上限。参见 Linux IP 参数文档。调整前先确认参数管的是哪个对象和状态。

如果每秒关闭连接数为 λ,状态平均驻留时间为 T,稳态数量约为 λT。这个关系只能用于解释连接周转,不应把协议中的两倍最大报文生存时间(Maximum Segment Lifetime,MSL)硬写成所有系统统一的分钟数。

CLOSE_WAIT 表示本端已收到对方结束发送的通知,而应用尚未完成本地关闭;重点查对象生命周期、异常路径和任务是否仍持有连接。TIME_WAIT 则通常先检查连接复用、短连接周转与临时端口压力,二者不能用相同“快速回收”方案处理。

追问:服务端只能接受 65535 个连接吗?不能这样推断。连接按本地与远端地址、端口区分;实际限制还包括描述符、套接字内存、跟踪表、代理和应用预算。端口数量不是监听端连接总数;SO_REUSEADDR 是否清除旧连接?不是。地址绑定复用与连接状态管理是不同机制;它不能无条件保证相同四元组立即重新连接,也不替应用修复泄漏。

差答:“把 tcp_fin_timeout 调小”“加大 backlog”“重启机器”“用新网卡缩短两倍 MSL”。

11. TCP 有流量控制,应用为什么仍会内存不足?

强答要点:TCP 约束字节进入连接的速度,不会自动限制已生成的列块、序列化对象、线程池任务或等待重试的请求。应用只要不断把数据移入用户队列,就可以绕开网络上的流量限制。

首先建立字节信用:下游可接纳多少有效数据,上游才生产多少;信用的返还应绑定真实资源释放,而非某个提交函数返回。多个队列可以有各自局部额度,但所有未释放字节必须归入明确的总预算。

1扫描器 → 编码队列 → RPC 在途数据 → 接收队列 → 算子消费
2  ↑                                              │
3  └──────────── 释放容量后返还信用 ────────────────┘

这条反馈链要求暂停能停到扫描器,而不是只让编码线程在满队列上等待。若暂停仍持有下一阶段必须取得的锁或全部执行线程,就可能把限流变成死锁。

gRPC 的流式流量控制会影响消息发送进度,但写入框架不代表数据已离开主机。双方大量写入而不推进读取时也可能相互等待。参见 gRPC 流量控制。对单次请求的接入预算仍要在应用层另行设计。

队列必须同时限制数量、字节与最大等待时间。单条消息大小差异很大时,数量限额会漏掉内存风险;无限等待则把已经不可能按时完成的任务留在系统里,继续挤占新请求预算。

追问:所有队列都设为有界,是否就安全?不一定。若生产和消费共用耗尽的线程池,生产者阻塞会让消费者无法运行;需要保证关键推进路径有执行预算,或采用可挂起的等待;信用用字节还是行数?网络缓冲按编码后字节更直接,执行器还要估计解压后或构建状态的内存。应明确预算覆盖哪一阶段,不能只用压缩大小保护膨胀后的算子。

差答:“交给 TCP”“给队列多加一点内存”“发送 API 返回就返还信用”。

五、RPC 与 RDMA 要回答语义,不能只比较接口速度

12. 如何比较 brpc 与 gRPC,并解释超时后的结果不确定性?

强答要点:建立共同维度:协议兼容、序列化与缓冲、执行模型、连接与流复用、截止时间与取消、重试、接入控制和诊断能力。没有相同消息大小、连接数、加密、并发与业务开销的测量,不能宣布框架普遍更快。

brpc 的 bthread 是多对多(M:N)用户态线程机制;阻塞承载线程的操作与可协作挂起的等待不能视为相同。参见 bthread 官方说明。同步写法不自动等于每请求独占内核线程,也不保证任意阻塞库调用都被自动改造成异步。

gRPC 官方性能指南指出连接上的并发流限制可能使调用排队。参见 gRPC 性能指南。超文本传输协议第二版(HTTP/2)允许多流复用,但共享 TCP 的有序交付仍会让丢失字节影响后续字节交付;应用流隔离不能消除传输层队头阻塞(Head-of-Line Blocking)。

gRPC 默认不替业务指定一个合理的截止时间(Deadline);超时传播与服务端取消还需语言支持及应用配合。参见 gRPC 截止时间指南。应用代码应检查取消并停止后续工作,但已经产生的外部副作用不能靠取消自动撤销。

“超时”描述调用者没有按时获得结果,可能发生在发送前、服务端执行中,或提交之后回复丢失。具有副作用的请求需要操作标识、幂等处理或可查询结果;网络层的可靠传输不提供业务恰好执行一次。

追问:重试放在哪里?明确框架、代理和业务分别是否重试,保留尝试标识与原始截止时间。多层各自重试会放大流量,必须计算最坏尝试数并统一预算;收到响应头后还会不会透明重试?按具体框架重试契约判断。gRPC 的提交点与重试策略有明确条件,不能把某个错误状态一律等同于安全重放,参见 gRPC 重试指南。

差答:“brpc 用协程所以一定更快”“gRPC 写成功说明对端收到”“超时之后直接再执行一次”“HTTP/2 消除了所有队头阻塞”。

【模板】填一条真实 RPC 链路:调用者、框架版本、代理、服务端、幂等键、截止时间传播与取消收敛时间。本人没实现过幂等协议,就明确这是待验证设计,不叙述为上线经验。

13. RDMA 为什么既不是远程函数调用,也不是远程事务?

强答要点:先区分双边消息与单边内存操作。双边发送需要接收端提供接收资源;单边读写使用远端授权的内存区域,可减少远端 CPU 在搬运路径上的参与,但远端应用仍要建立、管理并撤销这些区域。

RDMA 使用内存区域(Memory Region,MR)、保护域(Protection Domain,PD)、队列对(Queue Pair,QP)和完成队列;访问权限、操作类型与传输模式有具体契约。参见 NVIDIA 的 RDMA 编程手册。本文所述单边读写主要按可靠连接的常见使用方式讨论,不推广到所有传输模式。

读取协议至少需要控制面返回块标识、地址、长度、访问凭据和版本,并保证读取期间内存不会被复用为另一块。完成后验证版本或校验信息,失败则重新解析;地址有效不等于它仍属于原来的逻辑对象。

缓冲固定、注册缓存和连接状态也占资源。注册池过大降低其他工作负载可用内存,池过小导致回收或重新注册;大量队列与完成轮询还可能把节省的协议处理成本转成轮询和连接管理成本。

需要分别写出本地完成、远端可见、远端应用已消费与持久化的契约。即使某项操作已经满足传输完成语义,也不能推出数据库事务已经提交,或多个远端对象已经原子更新。

追问:接收端不及时提供资源怎么办?双边操作可能遭遇接收资源不足;应用仍需信用和缓冲管理,不能因为使用硬件队列就放弃背压;远端进程重启后旧地址还能用吗?需要失效旧访问凭据、连接或会话,并用代际阻止旧请求误命中新资源。重建连接本身不能证明业务对象身份正确。

差答:“RDMA 不用 CPU”“单边写完成就事务成功”“访问凭据等于完整租户认证”“使用 RoCE 就不会拥塞”。

六、最后用可归因观测检验前面的每个判断

14. 用 eBPF 定位长尾:如何从相关性走到可证伪的因果链?

强答要点:先提出竞争假设,再选择观测。请求慢可能因为无法获得 CPU、锁持有者未运行、直接回收、设备排队、网络重传或回调消费延迟;单看 CPU 栈只能解释正在 CPU 上执行的部分。

将应用请求标识与时间段关联到线程和控制组,再对照调度唤醒与切换、分配回收、块请求提交完成、连接状态。异步请求可能跨线程,设备与网络事件也未必带原始进程上下文,因此“按进程号过滤”不自动形成完整请求追踪。

libbpf 提供程序加载、挂载与对象管理,加载时的验证由内核验证器执行;它还支持一次编译、跨内核运行(Compile Once–Run Everywhere,CO-RE)的结构适配机制。参见 libbpf 官方概览。结构重定位不保证任意挂载点在所有版本存在,更不保证内部函数语义稳定。

BPF 环形缓冲可共享跨 CPU 事件存储并保留相应顺序,但容量仍有限,预留失败必须处理。参见 BPF ring buffer 文档。观测中应记录丢失或预留失败,不能把采集空白解释成系统没有发生事件。

用低成本直方图先定位等待类型,再对少量实例短时采集栈和关联事件;同时记录采样、事件速率和额外 CPU。观测开销若改变排队与长尾,就需要降低范围,并重新验证原始现象是否仍然存在。

追问:如何区分运行队列等待与锁等待?前者从可运行到实际调度,后者要识别阻塞与唤醒来源。持锁者的运行队列等待可能放大另一线程的锁等待,所以需要时间线而非两个独立总量;设备完成慢是否就是磁盘硬件慢?先定义挂载点覆盖的是块层排队、驱动还是设备服务;映射层、合并与重试会影响关联。应用端等待还可能发生在提交前和完成后。

差答:“装一个 eBPF 工具就能自动找到根因”“有验证器就没有开销”“CO-RE 保证所有版本兼容”。

2024 年 Linux 6.12 提供可扩展调度类(Extensible Scheduler Class,sched_ext),可由 BPF 程序定义调度行为,并在错误或任务停滞等条件下回退。参见 Linux 6.12 sched_ext 文档。它属于执行策略,不是普通观测探针;把它引入生产需要独立验证,不能在排障中顺手替换调度器。

七、把十四题收敛成一次完整的故障答辩

综合题:一个容器内的分布式查询节点,远端数据读取变慢,内存上升,超时后仍有 CPU 活动。面试官要求你先止损,再定位,最后决定是否采用 io_uring、BBR 或 RDMA。

强答要点:首先限定是题设,没有给定真实测量,不直接认定某项技术能解决。先确认服务级目标(Service-level Objective,SLO)、错误率、影响租户与变化时间,再冻结可回滚变更,减少新请求和在途字节,保证取消与完成推进仍有预算。

第一步检查容器实际 CPU 带宽、处理器集合、内存事件与压力信息。如果 CPU 节流与超时对齐,先解释为什么接收、完成和取消不能及时运行;不能因主机平均空闲就跳过这一层。

第二步拆请求时延。若远端读请求尚未真正提交,换拥塞控制没有针对性;若底层已完成但回调长时间排队,增加设备并发可能扩大积压;若接收应用不消费,先解决消费预算与背压。

第三步核对内存归属。对象在扫描器、编码队列、RPC 框架、未释放零拷贝缓冲或解压算子中有不同释放条件。把“超时已返回”与“底层资源已回收”分别观测,定位泄漏、正常延迟释放或取消未收敛。

第四步再选择实验。文件读提交开销或并发组织占主导时试 io_uring;持续长流且窗口与拥塞行为支持网络假设时比较 BBR;确实受大块搬运 CPU 或内存带宽约束、且网络与运维条件满足时试专用传输。每次只验证一个主要假设。

追问:无法立即复现,如何止损?降低新增在途资源,隔离受影响租户,保留最小必要指标与配置快照;拒绝策略应计入错误率,不宣称“延迟下降”即恢复;业务要求不拒绝任何请求怎么办?有限服务能力与持续超额到达无法同时保证有限排队与无限接收。需要协商排队上限、可重试响应、优先级和上游降速,这是容量约束的直接推论。

差答:把三个技术都列为“优化方向”,没有排除条件;只看平均时延,不看失败与取消后的资源;直接归因于内核老旧,却拿不出版本差异和触发路径。

【模板】实际答辩材料填写如下,不要求每例覆盖所有机制:

需要本人补充证据边界
版本、机器与容器配置可披露的真实信息;未知项明确写未知
请求类型、数据规模、并发与消息大小保留冷暖缓存、持续时间与负载发生方式
初始判断及被推翻的理由只写真实出现过的判断变化
本人决策、实现与他人贡献分清主导、参与、复核与阅读练习
变更前后结果及误差无原始记录不补写具体收益
回退条件、遗留问题与不适用场景说明仍未验证什么

八、验证方案:面试中说清如何测,别把建议写成实验结论

比较系统方案时,首先固定成功契约和资源总账。吞吐统计成功完成的业务量,延迟从请求进入被测边界开始计算;排队、重试和失败不能被悄悄移出计时范围。

待验证假设保持不变的条件主要观测反例与停止条件
工作线程过多导致调度竞争CPU 配额、请求分布、下游容量可运行等待、锁等待、完成吞吐减线程后设备不足或吞吐显著下降
大页降低地址翻译成本分配器、NUMA、数据与并发翻译事件、大页覆盖、缺页与长尾覆盖不足,或规整成本超过收益
直接 I/O 改善内存治理文件系统、缓存总额、请求大小缓存复用、回收、读延迟小读放大,预取不足,内存总账不降
io_uring 减少提交开销队列深度、批量、缓冲和持久性总 CPU、提交与消费等待完成积压、取消异常或工作线程成本上升
零拷贝减少搬运开销负载字节、连接、加密与接收速率CPU、带宽、未释放字节小消息回退多、缓冲池耗尽
BBR 改善目标路径实现版本、流量、时延与竞争流吞吐、排队、重传与公平性业务受限,或其他流退化
RDMA 数据通道值得建设对象协议、消息规模、可靠性要求端到端成本、注册与轮询开销控制与恢复复杂度超过可证明收益

请求发生器也属于实验条件。若只在前一请求完成后发下一请求,服务变慢时发生器会自动降速,可能低估过载和长尾;需要同时说明到达模式、实际请求率与最大在途数。反过来,无上限开放发送又可能测成发生器或队列崩溃,而不是目标稳态能力。

1cat /proc/self/cgroup
2cat /proc/pressure/cpu
3cat /proc/pressure/memory
4cat /proc/pressure/io
5vmstat 1
6ss -tin

对于 Linux 内存回收,也不要把教材中的最近最少使用(Least Recently Used,LRU)顺序直接当成当前系统实现。Linux 提供多代 LRU(Multi-Gen LRU)实现,其是否启用依赖配置和运行时开关,参见 官方回收文档。淘汰策略、物理页分配和应用缓存淘汰分别解决不同层的问题。

白板推演:业务结束与资源归还分开

单线程事件归并器可用两个状态表达:user_result 决定调用者是否收到成功或超时;payload_terminal 与附属释放通知决定缓冲能否回收。取消项完成不能替代目标操作终结。跨线程事件须先由同步协议串行化。

1业务超时:仅提交一次 timeout,发出取消;保留在途缓冲
2目标完成:更新目标终结状态;若结果仍 pending,提交结果
3释放通知:记录缓冲释放条件已满足
4回收:目标已终结 && 必要通知已到达 && 尚未归还 → 归还预算并释放

检验正常完成后超时、超时后完成、取消找不到目标但完成已排队、释放通知延后四种交错。状态先标记为不可重复,再执行可能重入的外部清理。请求标识需带稳定身份或代际,避免旧事件命中新对象。

这是设计推演,未处理部分发送、多次完成、连接销毁与通知错误;接口不要求释放通知时也不能凭空等待。实际实现须按具体接口补状态表及并发验证。

新增来源与阅读边界

以下均为官方项目资料、内核文档或协议规范。版本化文档可追溯相应能力;滚动手册不保证目标发行版具备全部特性。Google BBR 的 v3 链接是可移动分支,只用于发布和部署说明,本文没有据此声称完成某次固定提交的源码审计。

使用时先画清排队、执行和最后使用点,再用时间线检验一个假设。上述验证矩阵尚待执行,不能把接口支持或文章构建成功当作生产收益。操作系统页面与 PagedAttention 的逻辑 KV 块由不同层管理,容量与回收结论不能直接互推。