面灵AI→

腾讯云后台开发一面:容器网络、iptables 与分布式缓存手撕

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 请做一下自我介绍。
  2. 介绍一下你的缓存项目。
  3. 这个项目目前是单机还是多机?改成多机的话可能要怎么做?
  4. 这个项目最大的难点是什么?
  5. 介绍一下你的容器运行时项目。
  6. 容器网络是怎么配的?
  7. 了解 iptables 吗,大概是什么原理?
  8. 你的实现跟 Docker 比较一下?
  9. 进程与线程的区别是什么?听过协程吗?
  10. 知道死锁吗?你举的这个例子怎么解决?
  11. 你在经历里用了 VLAN,它是用来做什么的?VLAN 本身是什么?
  12. 了解 AI Coding 吗,比如 Codex 之类的?
  13. Skill 和 MCP 是什么?
  14. 算法题:有序数组,查找第一个和最后一个出现 target 的位置。
  15. 算法题:实现分布式缓存系统,要求支持并发访问,并自动移除最久没用的项。

《参考解析》

容器网络与 iptables:容器网络的基本模型是「网络命名空间 + veth pair + 网桥」。每个容器有独立的 netns(自己的一套网卡、路由表、iptables 规则),创建时用 veth pair 把容器内的 eth0 接到宿主机的一个网桥上(Linux bridge,如 docker0 或 CNI 插件建的网桥),容器从网桥的网段分配 IP,网关指向网桥地址;容器出网靠宿主机的路由与 SNAT(源地址改成宿主机 IP),外部访问容器靠 DNAT(把宿主机某端口的流量改写到容器 IP:端口)以及端口映射规则。这些改写就是 iptables 干的活——iptables 本身不是「防火墙」而是一套基于 Netfilter 钩子的包处理框架:数据包在网络层经过五个链(PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING),每条链上挂若干「表」(filter 过滤、nat 地址转换、mangle 改包、raw),规则按顺序匹配,匹配到就执行 ACCEPT/DROP/REJECT 或 NAT 动作(DNAT/SNAT/MASQUERADE),同一条链里第一条匹配的规则生效,所以顺序即优先级。从 Docker 的角度对比:Docker 自带一套默认实现(docker0 网桥 + iptables 的 DOCKER/DOCKER-USER 链 + 端口映射),开箱即用但网络模型固定、跨主机要另配 overlay;自研容器运行时通常自己管 CNI 插件(bridge/host/macvlan/overlay 可选)、自己做 IPAM 与规则下发,好处是能贴合业务做网络策略和性能优化,代价是路由、DNS、策略隔离这些都要自己兜住。常被追问的点还有:iptables 规则多了性能会退化(线性匹配),所以大规模集群会转向 nftables 或 eBPF(Cilium);容器跨主机通信可以用 overlay(VXLAN 封装)或直接路由(BGP 宣告 Pod 网段)。

进程、线程与协程:进程是资源分配单位(独立地址空间、fd 表),线程是调度单位(共享地址空间,只独立栈与寄存器),进程切换要换页表和刷 TLB、代价更高。协程是用户态的并发单元:切换不陷入内核、由运行时调度,栈很小(KB 级)所以能开几十万个,代价是遇到阻塞式系统调用会卡住整个线程(需要运行时把所有 IO 改成非阻塞 + epoll 事件驱动),而且无法利用多核(要配多线程 M:N 调度)。线程间通信用的是共享内存 + 同步原语(互斥量、条件变量、信号量、原子操作);进程间才需要管道、共享内存、消息队列、socket 这类 IPC 机制。面试里常追问「线程共享了什么」(堆、全局变量、代码段、fd)以及「为什么需要协程」(高并发 IO 场景下线程栈内存与切换开销成为瓶颈)。

死锁:死锁的四个必要条件——互斥、持有并等待、不可剥夺、循环等待,破坏任意一个就能避免。工程上的手段:① 统一加锁顺序(比如按资源 id 排序后依次加锁),这是最实用的做法,直接破坏循环等待;② 用带超时的锁(try_lock_for)或一次性申请全部资源,破坏持有并等待;③ 用无锁结构、原子操作或单线程串行化把锁去掉;④ 用可剥夺的机制(数据库事务里设锁等待超时后回滚重试)。排查上要会看:jstack 能直接打出死锁检测结果,pstack/gdb 看线程栈,SHOW ENGINE INNODB STATUS 看数据库的行锁等待。注意区分死锁与活锁、饥饿:活锁是都在动但没人推进(重试退避不当),饥饿是优先级/调度导致某线程长期拿不到锁,解法各不相同。

手撕:二分边界与分布式 LRU 缓存:第一题是手写 lower_bound 与 upper_bound——第一个等于 target 的位置用「找第一个 >= target」的二分,最后一个等于 target 的位置用「找第一个 > target 再减一」,两个函数都写成左闭右开区间 [lo, hi) 的模板:while (lo < hi) { mid = lo + (hi - lo) / 2; if (a[mid] < target) lo = mid + 1; else hi = mid; } 返回 lo。易错点是 mid 用 (lo+hi)/2 会溢出、循环条件写成 lo <= hi 导致死循环、以及「找不到时返回什么」(返回插入位置还是 -1)要先和面试官对齐;复杂度 O(log n)。第二题是在 LRU 的基础上加并发与分布式,答题顺序是「先单机、再并发、最后分布式」:单机 LRU 用哈希表 + 双向链表,get 把节点移到头部,put 满时淘汰尾节点,读写都是 O(1)(C++ 里可以直接用 std::list + unordered_map<key, list::iterator>)。并发上给哈希表和链表加一把互斥锁最简单但会串行化,优化方向是分段锁(按 key 哈希分桶,每桶一把锁)或读写锁(读多写少)、以及用原子操作做引用计数避免锁内做内存分配;一定要说明锁内不做慢操作(如回源 DB、序列化)否则吞吐上不去。分布式层则是:多节点用一致性哈希(或哈希槽)分片,让节点增减时只迁移一部分 key;副本用主从 + 异步/半同步复制保证可用性;跨节点的一致性问题要显式取舍——强一致就得走共识协议(Raft)并付出延迟,弱一致可以本地 LRU + 失效广播(近似 LRU 是可接受的工程折中,因为 LRU 本身只要求「淘汰不常访问的」)。最后主动补一句「分布式 LRU 的『最久未用』只能是近似的」——全局精确 LRU 需要协调所有节点的访问时间,代价远大于收益,这也是 Redis 用近似 LRU/LFU 的原因。