腾讯云后台开发一面:容器网络、iptables 与分布式缓存手撕
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 请做一下自我介绍。
- 介绍一下你的缓存项目。
- 这个项目目前是单机还是多机?改成多机的话可能要怎么做?
- 这个项目最大的难点是什么?
- 介绍一下你的容器运行时项目。
- 容器网络是怎么配的?
- 了解 iptables 吗,大概是什么原理?
- 你的实现跟 Docker 比较一下?
- 进程与线程的区别是什么?听过协程吗?
- 知道死锁吗?你举的这个例子怎么解决?
- 你在经历里用了 VLAN,它是用来做什么的?VLAN 本身是什么?
- 了解 AI Coding 吗,比如 Codex 之类的?
- Skill 和 MCP 是什么?
- 算法题:有序数组,查找第一个和最后一个出现 target 的位置。
- 算法题:实现分布式缓存系统,要求支持并发访问,并自动移除最久没用的项。
《参考解析》
容器网络与 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 的原因。