阿里控股一面:RPC 链路、内核收包与 Go 协程
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 请做一下自我介绍。
- Java 的熟悉程度如何?主要使用过哪些 JDK 版本?
- 了解 JDK 17 的哪些新特性?
- 字节实习了多长时间?为什么没有留用?
- 期望的工作地点是哪里?可以接受杭州吗?老家是哪里的?
- 选择一个实习项目详细介绍一下。
- 这个项目有多少人参与?涉及哪些上下游团队?
- 你在项目中具体负责哪一部分?
- 介绍一下底层 RPC 调用链路,以及 Kitex、Consul 和 Mesh 在其中的作用。
- 开发过程中是否使用 AI Coding?如何验证和 Review AI 生成的代码?
- 这个项目持续了多长时间?从人工 SOP 到自动化治理经历了哪些阶段?
- 项目中遇到的最大挑战是什么?分别从技术和非技术角度说明。
- 论文是否已经发表?主要研究方向是什么?
- 实习结束后为什么没有继续科研?这段时间在做什么?
- 介绍一下你的个人开源项目,项目创意、技术调研和前后端开发是否都是独立完成的?
- 为什么开发这个项目?项目的用户和 Star 是如何增长的?后续是否计划维护?
- 一个网络数据包从网卡到达用户态应用,需要经过哪些步骤?
- DMA 在网卡收包过程中起什么作用?Netfilter 处理流程是怎么样的?DPDK 了解吗?
- mmap 的原理是什么?为什么能够减少内核态到用户态的数据拷贝?
- Go 协程相比线程有什么优势?为什么创建、销毁和切换开销更低?
- 目前还有哪些公司或岗位正在推进?Offer 情况如何?
《参考解析》
网卡到用户态的收包路径:硬件收到帧后,用 DMA 把数据写进驱动预先分配好的 RX ring(描述符数组),写完回写描述符并触发中断;驱动在中断上半部只做最少的事(记录状态、屏蔽该队列中断),把真正的收包交给 NAPI 的 poll 在下半部批量处理(软中断 NET_RX_SOFTIRQ)。之后 sk_buff 向上走:二层补齐与校验、VLAN 处理后进入协议栈,途经 Netfilter 的各 hook 点(PREROUTING → 路由决策 → INPUT/FORWARD),再到传输层按四元组找到对应的 socket,把 sk_buff 挂进该 socket 的接收队列并唤醒等待的进程;应用 recv 时数据从内核缓冲区拷贝到用户缓冲区。所以延迟与开销主要来自三处:中断频率、协议栈逐层处理、以及最后那次内核到用户的拷贝。DPDK 正是针对这三点:用 PMD 轮询替代中断、用户态直接操作网卡队列、大页加无锁 ring 省掉拷贝,代价是独占 CPU 核、要自己实现协议栈,通常只用在专用转发/网关机器上。
DMA 与 mmap:DMA 让外设不经过 CPU 直接把数据写进内存,驱动只需把缓冲区地址填进描述符、收完后回收,CPU 因此不参与搬运——这是零拷贝的前提,但要注意 DMA 缓冲区需要物理连续或经 IOMMU 映射,且存在 cache 一致性问题(收包前可能需要 invalidate,或直接使用一致性映射)。mmap 则是把内核里的物理页帧映射进用户进程的页表,用户态和内核态访问同一块内存,从而省掉「内核缓冲区→用户缓冲区」这一次拷贝。典型应用是 AF_PACKET 的 PACKET_MMAP 收包环、mmap 读文件、以及进程间共享内存。要主动讲清边界:mmap 省的是数据拷贝,不是系统调用与页表建立的开销;缺页、映射维护、以及写回时机(可用 msync 控制)仍要付出代价,所以它并不是「所有 IO 都快」,只有大块、频繁的数据传输才划算。
Netfilter 的处理流程:Netfilter 在内核协议栈里埋了五个 hook 点——PREROUTING(进路由决策之前,DNAT 在这里做)、INPUT(送往本机)、FORWARD(转发)、OUTPUT(本机发出)、POSTROUTING(出网卡前,SNAT 在这里做)。iptables/nftables 的规则按表挂在 hook 上,遍历顺序基本是 raw → mangle → nat → filter,每条链内按顺序匹配,命中后执行 target(ACCEPT、DROP、REJECT、JUMP 到自定义链等)。连接跟踪(conntrack)在 PREROUTING 建立流状态,后续包命中已建立连接走快速路径。排查时用 iptables -t nat -L -n -v 看每条规则的包计数,就能判断请求走到了哪一条以及被谁丢掉——这比看日志快得多;容器环境里还要注意规则分别落在宿主机的哪个 netns 上,否则会「明明配了却不生效」。
Go 协程为什么比线程轻:三点叠加。第一,初始栈只有 2KB 且可动态伸缩(不够时分配更大的栈并搬迁),线程栈通常是固定的 MB 级。第二,调度完全在用户态完成——GMP 模型里 G 挂到 P 上由 M 执行,切换只保存少量寄存器,不陷入内核、不切特权级、也不刷 TLB。第三,运行时配套:工作窃取让空闲 P 去偷别人的 G,netpoller 把网络 IO 变成可运行的 G,避免为等待 IO 而阻塞 M。所以创建百万级 goroutine 的代价远小于同量级线程。代价也要讲清:阻塞式系统调用会占住 M(运行时会 handoff 出新的 M)、调度是带抢占点的协作式调度,长循环或纯计算代码可能延迟调度、跨 goroutine 的数据竞争该加锁还是得加锁——协程不是并发安全的替代品。
AI Coding 的验证与 Review:把 AI 生成的代码当成外部贡献者提的 PR 来审。流程上:先让它复述需求和约束确认理解一致,再让它小步产出;生成后本地跑单测、lint、typecheck,重点审边界——空值、并发、错误分支、资源释放、异常吞掉、依赖是否真实存在(模型最容易编造 API),还要检查它有没有为了让测试通过而改动测试本身。涉及事务、权限、SQL、金额的地方默认不信模型,自己写或自己逐行核对。落到习惯上:每步可回滚,代码自己必须能讲清每一行;样板、单测、脚本、正则这类重复劳动收益最高,核心逻辑仍归自己。另外留一份 prompt 与 diff 的对应记录,能复盘出哪类任务适合交给模型、哪类交给它反而要花更多时间返工。