技术笔记¶
面试存在的问题¶
| 问题 | 解决 |
|---|---|
| 答非所问 | 先确认问题,停顿思考再作答。如果没有理解问题,可以复述后,询问面试官确认问题 |
| 回答问题遗漏 | 有条件的话用纸笔记录问题,没有条件的话先记住123个问题,不要着急想答案,然后根据123分别作答,如果有遗忘,询问面试官问题 |
| 回答啰唆 | 先说结论,然后再拆解为123说原因或具体行动 |
| 无法捕获问题的关键字,提示时没用到提示信息 | 确认问题时整理关键字,收到提示时要先按照提示的思路方向考虑 |
自我介绍¶
你好,我是XXX,我做过toB的权限管控,做过toC的打车服务,也做过区块链的项目。我最近在openblock主导的重构对账方案,将对账复杂度从O(n)优化到O(logn)。现在主要使用Go开发,熟悉MySQL、Redis、Kafka、HTTPS等网络协议,也熟悉Agent的开发。具备较好的产品思维,GitHub开源项目累计获得3.9k Stars。
项目¶
反问¶
谈薪资¶
- 先不要报具体期望薪资,对方一定要问,先了解薪资结构,然后报出区间
- 以市场价报区间,并结合自己的面试表现与匹配度、其他公司offer来给出报价
- 如果新岗位做的活超过之前的范围,比如后端开发到全栈、运维等,则需要有相应工作能力的溢价
- 如果HR给出的薪资低于下限,可以以和预期有差距,需要考虑一下,稍晚一点回复。不要直接回答,避免自己吃亏后悔
- 对方以上份工作薪资作为锚点,或市场行情不好来压价,可以说当前薪资应该基于市场价值,而不是之前的薪水,当然底线是平薪(没有更好的机会时)
Go¶
GMP模型¶
| 角色 | 全称 | 含义 | 核心职责 |
|---|---|---|---|
| G | Goroutine | 协程 | 待执行的任务,用户态轻量级线程,初始栈仅 2KB,按需动态扩容 |
| M | Machine | 系统线程 | 真正执行代码的操作系统内核线程,由 OS 内核调度 |
| P | Processor | 逻辑处理器 | 包含调度 G 所需的上下文和本地队列,数量通常等于 CPU 核心数(GOMAXPROCS) |
首先,GMP中的G是用户态的协程,具备完整的栈信息,初始仅2KB,可动态扩容。M是操作系统的线程,由内核调度。P是调度G的逻辑处理器,M 必须绑定一个 P 才能执行代码。 在Go早期,只有GM模型,这导致全局锁竞争激烈,在加入了P之后,就分为了本地队列和全局队列,当创建新的 Goroutine 时,会优先放入本地队列,如果本地队列已满,则取本地队列的一半放入全局队列,避免频繁的全局锁竞争。 如果本地队列为空,则会依次从全局队列、网络轮询器、其他队列窃取任务(全局队列根据M数量窃取,其他队列窃取一半),同时为了避免全局队列的饥饿,本地队列会在每61次调度从全局队列取一个任务。 当G进行阻塞性的系统调用(如阻塞读写磁盘或网络),正在运行它的M会同G一起陷入阻塞状态,此时会把P剥离出来,转交给其他空闲的M。当系统调用结束后,原来的M会尝试重新绑定空闲的P,如果抢不到P,就把当前的G放入全局队列,自己转入休眠。 对于普通的网络I/O(Go封装的非阻塞网络调用),M不会被阻塞,而是会挂载netpoller,M直接执行其他G,数据就绪后再唤醒G。 另外,Go的调度之前是协作式,这导致了某些G可能一直占用M而不会释放,现在已经改为了抢占式,运行超过一定时间会强制中断M的执行。
GC¶
GC的演变一直在优化STW导致的停顿问题。Go的GC最早是标记-清除算法,整个过程都需要STW,导致程序严重的卡顿;之后引入了并发三色标记和插入写屏障,标记协程可以和用户协程并发执行,显著降低了STW的停顿,由于栈的操作非常频繁,因此在栈上不开启插入写屏障,这导致了标记结束后需要STW再次对栈扫描,确认标记期间的修改;最后引入了混合写屏障,写屏障只作用于堆,栈上全黑且栈上新建对象直接标黑,而堆上对象引用的插入和删除时标记为灰色,避免了栈的STW二次扫描,因此进一步降低STW导致的停顿。
GC的过程是这样的,
- 标记开始时,开启写屏障,短暂的STW
- 并发标记,GC协程与用户协程并发执行,GC协程从根对象开始,初始对象都是白色,已完全扫描的对象是黑色,部分扫描的对象是灰色。
- 标记结束时,关闭写屏障,短暂的STW
- 并发清理,回收所有标记为白色的对象,该过程没有STW
GC 会在内存达到阈值和超过2分钟没有GC时被触发。在实际的业务中,通过sync.Pool复用对象、预分配容量避免slice/map频繁扩容、减少变量逃逸(小对象优先传值而非指针)等方式来降低GC压力。
协助标记
Go 默认分配 25% 的 CPU 用于后台 GC 标记。如果某个协程分配内存过快(超出后台标记速率),运行时会强制该协程暂停业务代码,转而协助进行三色标记。这样既压制了该协程分配新内存的速度,又借用其 CPU 算力加速了标记的完成。
MySQL¶
Redis¶
分布式系统¶
分布式系统如何保证数据的一致性?¶
分布式系统下由于网络分区不可避免,根据 CAP / BASE 理论,绝大多数互联网业务都会选择牺牲强一致性、追求高可用,采用最终一致性(柔性事务);而在注册中心、元数据、分布式协调等场景则追求强一致性(刚性事务/共识)。我将从最终一致性和强一致性两方面展开:
1. 最终一致性(柔性事务,业务系统主流)¶
- 可靠消息最终一致性(本地消息表 / RocketMQ 事务消息,最推荐):
- RocketMQ 事务消息:利用“半消息(Half Message)+ 本地事务状态回查”机制,保证本地 DB 操作与消息发送的原子性。
- 本地消息表(Outbox Pattern):将业务数据变更和消息记录在同一个本地数据库事务中提交,后台定时任务或通过 CDC(Canal 监听 Binlog)抓取消息投递到 MQ。
- 下游保障:下游消费失败自动重试,必须实现接口幂等性(基于唯一业务流水号或防重表),极端失败进入死信队列(DLQ)。
- TCC 补偿型事务(针对强业务约束场景,如跨境转账、资产冻结):
- 分为 Try(资源预留/冻结,如冻结 100 元)、Confirm(确认扣除)、Cancel(释放预留资源)。
- 不依赖底层数据库锁,性能优于 2PC,但业务侵入性高(每个微服务需手写 3 个接口),且必须妥善处理幂等、空回滚、防悬挂三大难题。
- Saga 模式(适用于长业务链,如电商下单出库、旅游出行套票):
- 将一个分布式事务拆解为多个子事务 \(T_1, T_2, \dots, T_n\)。若某一步失败,按反向顺序依次调用对应的补偿事务 \(C_{n-1}, \dots, C_1\)。可通过状态机中心化编排(Orchestration)实现。
2. 强一致性(刚性事务 / 基础组件)¶
- 共识算法(Raft / Paxos):
- 应用于 etcd、ZooKeeper、TiKV 等分布式协调与分布式存储系统中。通过 Leader 选举 + 多数派(Quorum)日志复制确认,在节点故障时仍能对外提供强一致的读写。
- 2PC / 3PC(两阶段/三阶段提交,XA 协议):
- 依赖全局事务协调者(Coordinator),经历准备(Prepare)和提交(Commit)两阶段。缺点在于同步阻塞、协调者单点故障、性能极差,目前仅在单库跨表或传统金融内部极小范围使用。
3. 最终兜底闭环(工程落地核心)¶
- 唯一流水号与全链路幂等:所有跨服务操作强依赖全局唯一
tx_id / order_no,消费端通过 Redis SetNX 或 DB 唯一索引防止重复执行。 - 对账系统与人工兜底:任何分布式事务机制都不能保证绝对零差错。线上配合**定时对账任务(T+1 或准实时)**对比上下游流水差额,不一致时自动补偿修复,无法自动处理的触发高优先级报警交由人工对账补偿。
分布式事务如何实现?¶
单机事务依赖数据库原生的 ACID(Undo/Redo Log 和锁机制),微服务拆分后跨进程、跨库,本地事务失效。实现分布式事务主要有以下四种工业界主流方案,核心是根据业务场景、性能要求和侵入性做权衡:
1. 四大主流实现模式¶
- MQ 事务消息 / 本地消息表(异步最终一致性,首选方案):
- 原理:基于 RocketMQ 事务消息(Half 消息 + 本地事务回查)或本地消息表(业务与消息在同一本地事务提交 + 定时投递)。下游服务监听消息消费入库,失败不断重试。
- 适用:绝大多数允许异步解耦的非实时场景(如支付成功后发货、加积分、发短信)。
- Seata AT 模式(无侵入二阶段提交):
- 原理:第一阶段业务 SQL 执行时,框架自动解析 SQL 生成前镜像(Before Image)和后镜像(After Image)存入
undo_log表,随本地事务一同提交并快速释放本地行锁;第二阶段若成功则异步清理 undo log,若失败则通过前镜像数据反向生成回滚 SQL 还原数据。 - 适用:老系统改造、单体拆微服务、希望零业务代码侵入的企业级系统。
- TCC 模式(业务层两阶段提交,高并发强约束):
- 原理:将业务拆解为 Try(资源冻结/预留)、Confirm(真正扣除已冻结资源)、Cancel(释放冻结资源)。由于只在 Try 阶段冻结局部资源(如将余额拆出冻结字段),不长期占用底层 DB 行锁,性能极高。
- 适用:核心高并发交易、热点资金/库存账户扣减。
- Saga 模式(长事务编排):
- 原理:将大长事务拆分为多个独立的子事务 \(T_1, T_2, \dots, T_n\) 并发/串行执行;某一步失败时,状态机协调器(Orchestrator)反向依次回滚调用补偿接口 \(C_{n-1}, \dots, C_1\)。
- 适用:跨部门/跨第三方外部系统调用(如调用微信支付、航司订票接口,无法做 TCC 的场景)、长周期业务流程。
2. 面试必问三大深水区(TCC / 柔性事务踩坑与解决)¶
- 空回滚(Null Rollback):
- 原因:分支事务的 Try 请求因网络丢包或拥堵尚未到达,事务管理器判定超时直接触发了 Cancel。
- 解决:Cancel 接口执行前必须先查询分支事务日志。如果发现 Try 未执行,仅记录一条“空回滚标记”,绝不执行回滚业务逻辑。
- 防悬挂(Suspension):
- 原因:Cancel 执行完毕后,之前迟到的 Try 请求才到达目标服务并成功执行,导致预留资源被永久冻结,无法释放。
- 解决:Try 接口执行前必须先检查该事务是否已被记录为“已回滚/空回滚”,若是则直接拒绝执行。
- 幂等性(Idempotency):
- 原因:网络超时引发协调器自动重试 Try、Confirm 或 Cancel。
- 解决:每个分支接口基于全局
tx_id + branch_id记录状态流水,重复请求直接返回成功。
3. 选型总结(面试一句话收尾)¶
“在实际业务中,90% 的场景优先选 MQ 事务消息(性能最高、改造成本最低);核心高并发账户与库存选 TCC;无代码侵入的内部微服务系统选 Seata AT;涉及外部三方调用与超长跨系统链路选 Saga。”
秒杀系统怎么设计?(核心:防超卖与高可用)¶
秒杀系统本质是应对读极大、写极集中的瞬时极端流量超高并发场景,核心痛点就是绝不能超卖和必须保住系统高可用。整体架构遵循**『漏斗模型』**:通过动静分离、分层过滤、缓存扣减与异步削峰,层层拦截流量。在设计上我会重点围绕这两条核心主线展开:
1. 如何彻底杜绝超卖(库存一致性)¶
- 库存预热与 Redis 原子扣减:活动前将库存预热至 Redis。扣减逻辑通过 Lua 脚本把「校验用户限购 -> 校验库存 -> 扣减库存 -> 记录已买用户」打包成原子操作,在内存层毫秒级拦截 99% 的无效流量。
- 防重复下单(幂等):在 Redis 中用 Set 存储已购买的 UID;数据库订单表将
user_id + activity_id设为唯一索引,即便用户重试或并发穿透,也能保证一人只能下一单。 - DB 条件更新最终兜底:下游异步入库扣减 DB 真实库存时,采用带版本或条件约束的更新(
UPDATE stock SET count = count - 1 WHERE id = ? AND count > 0),依靠行锁保证 DB 绝对不会扣成负数。 - 超时未支付库存回补:利用 RocketMQ 延迟消息或 Redis 键过期监听支付状态,如果 15 分钟未支付则自动关闭订单、回退 DB 库存,并通过 Lua 脚本回补 Redis 缓存。
2. 如何保障系统高可用(流量治理与削峰)¶
- 动静分离与端侧防刷:商品详情页等静态资源推至 CDN;秒杀按钮防抖并结合验证码/算术题拉长并发请求区间;秒杀接口 URL 动态化(加盐 Hash,活动开启后才下发动态 Token),防止黑产逆向提前刷接口。
- 网关层限流(分层阻断):在 API 网关基于令牌桶/漏桶算法,对单 IP、单 UID 以及整体集群做阈值限流,超出压测水位的请求秒级快速失败,返回“排队中”友好提示。
- MQ 异步削峰:Redis 扣减成功即向用户返回“抢购成功,排队出单中”,后台向消息队列投递下单消息,订单/支付服务根据 DB 承载上限平滑消费入库,避免数据库连接池被瞬时打爆。
- 物理隔离与熔断降级:秒杀的域名、网关、服务集群、Redis 及 DB 均与日常主业务做物理隔离,避免秒杀异常影响全站;非核心依赖(如积分、推荐、消息推送)全面做降级或异步化,保障核心下单链路绝对存活。
线上故障¶
goroutine 泄露¶
内存泄露¶
网络问题¶
AI¶
如何使用AI提效?¶
- 自己先准确理解需求,将需求拆分为多个小的任务,降低AI幻觉
- 在每个SKILL中补充工具和业务术语等上下文信息,并增加约束
- 自己先设计一下实现方案,然后与AI讨论完善方案
- 交由AI编写代码,并在编写过程中通过单元测试来保证质量
- 针对关键模块,会使用不同的AI模型进行交叉验证
- 上线前人工review代码,避免AI编写的代码成为黑盒
- 每次编写时,都会及时对项目进行重构,保持模块边界清晰,降低耦合,增加可维护性
如何解决长对话摘要丢失关键信息?¶
单纯全局摘要很容易丢失用户早期核心需求、硬性偏好这类关键信息,我会四层机制组合规避:
- 先用工作内存把用户固定目标、输出约束单独隔离常驻提示词,完全不参与摘要压缩,守住底线;
- 不做一次性全局总结,采用分段增量摘要,给每段内容打上优先级与时序标签,避免抹平早期关键信息;
- 底层搭配RAG语义检索向量库,摘要只做提纲,完整原始对话永久存储,遗漏细节可随时召回原文;
- 针对超长会话、大量工具返回的复杂场景,上 Multi-Agent Memory:主对话 Agent 只负责交互,Observer / Reflector / Retriever 专职写记忆、评优先级、按需召回,用分工替代全文压缩,从架构上避免关键信息丢失与上下文膨胀。
同一个问题的两次输出不一致¶
LLM是概率生成,因此相同的输入有不同的输出是正常现象,可以通过工程手段确保业务结果稳定,如 - 使用 JSON 结构化输出 - 对输出字段与格式校验,不符合要求时自动重试,多次失败降级到规则系统或人工 - 通过prompt或temperature降低随机性 - 输出在某个阈值内即可
模型的升级与下线如何确保agent的稳定?¶
通过可观测系统评估模型的可用性与稳定性,出现故障时及时路由或降级。
Kubernetes¶
1. 核心架构与组件职责¶
K8s 整体采用 Master(控制面 Control Plane)- Worker(工作节点 Node) 架构,核心通信模式是所有组件均通过 kube-apiserver 交互,组件之间不直接通信,数据统一由 etcd 持久化。
| 节点类型 | 组件名称 | 核心职责与原理 | 面试高频关注点 |
|---|---|---|---|
| Control Plane | kube-apiserver | 集群唯一入口,暴露 RESTful API,负责认证(AuthN)、鉴权(AuthZ)、准入控制(Admission Control),管理 etcd 读写 | 无状态设计,可水平扩展;唯一能直接读写 etcd 的组件 |
| etcd | 高可用强一致性键值存储(基于 Raft 协议),存储集群所有资源的状态元数据 | 仅控制面组件;写请求需多数派节点确认;需注意心跳、磁盘 IOPS 及存储上限(默认 2GB,可调至 8GB) | |
| kube-scheduler | 调度器,监听未绑定的 Pod,根据节点资源状况将其分配给最合适的 Node | 两阶段调度:Predicates(预选过滤) 剔除不合规节点 -> Priorities(优选打分) 选出最高分节点 | |
| kube-controller-manager | 控制器管理器,运行一系列后台控制循环(Reconciliation Loop,期望状态 vs 实际状态) | 包含 DeploymentController、NodeController、EndpointController 等,体现“声明式设计”的核心驱动力 | |
| Worker Node | kubelet | 节点“带头人”,接收 apiserver 指令,通过 CRI/CNI/CSI 维护本节点 Pod 的完整生命周期与上报状态 | 负责拉镜像、起容器、执行探针(Liveness/Readiness)、监控资源消耗 |
| kube-proxy | 节点网络代理,维护本节点的网络规则,实现 Service 的虚拟 IP(ClusterIP)到 Pod IP 的负载均衡与转发 | 支持 iptables 和 IPVS 两种模式,控制面由其维护节点转发规则 | |
| Container Runtime | 容器运行时(如 containerd、CRI-O),遵循 CRI 标准,真正负责下载镜像并启动/停止容器 | 通过 pause 容器(Infra 容器)初始化网络命名空间和 IPC,供业务容器共享 |
2. 核心工作流程:从 kubectl run/apply 到 Pod 运行全流程¶
面试高频题:“在终端敲下
kubectl apply -f pod.yaml后,K8s 内部到底发生了什么?”
- 请求校验与入库(apiserver):
- kubectl 向
apiserver发送 HTTP POST/PUT 请求; - apiserver 依次进行:认证(身份) -> 鉴权(RBAC 权限) -> 准入控制器(Mutating/Validating Webhook,参数补全与合规校验);
- 校验通过后,将 Pod 对象的元数据写入
etcd,此时 Pod 处于Pending状态(NodeName为空)。 - 控制器监听(controller-manager):
- 对应控制器(如 DeploymentController)通过 List-Watch 机制感知到新资源,生成对应的 ReplicaSet 及底层 Pod 规范对象。
- 调度决策(kube-scheduler):
- scheduler 通过 List-Watch 发现新创建且未分配节点的 Pod;
- 执行预选算法(Filtering):检查节点资源容量(CPU/内存)、亲和性(NodeAffinity)、污点与容忍度(Taints/Tolerations)等;
- 执行优选算法(Scoring):计算多维度得分(如资源均匀分布、镜像已拉取等),选出最高分 Node;
- scheduler 将绑定关系封装为
Binding对象,提交给 apiserver 写入 etcd(将 Pod 的spec.nodeName赋值)。 - 节点拉起(kubelet):
- 目标 Node 上的
kubelet通过 Watch 机制发现该 Pod 已调度到本节点; - kubelet 调用 CRI(如 containerd):
- 首先创建 Pause 容器(Infra Container),分配该 Pod 专属的网络 Namespace 和 IPC;
- 调用 CNI(容器网络接口,如 Calico/Flannel) 为 Pause 容器分配 Pod IP 并打通宿主机网桥;
- 调用 CSI(容器存储接口) 挂载卷(Volume);
- 拉取业务镜像,启动业务容器并加入 Pause 容器的网络栈(共享网络与端口);
- 探针与状态就绪(kubelet -> apiserver):
- 容器启动后,kubelet 执行启动探针和就绪探针;探针通过后将状态置为
Running,kubelet 向 apiserver 上报最新状态,同步写入 etcd。
3. 服务注册与服务发现原理¶
后端微服务在容器化后,Pod 的 IP 是动态变化的(漂移、扩缩容、故障自愈),K8s 通过 Service + EndpointSlice + CoreDNS + kube-proxy 实现了去中心化的原生服务注册与发现。
Client Pod
│
▼ (1. 发起请求: http://user-service:8080)
CoreDNS ──(2. 解析为 ClusterIP: 10.96.0.10)
│
▼ (3. 流量到达 Linux 内核网络栈)
kube-proxy 维护的 iptables/IPVS 规则 ──(4. DNAT 转换 / 负载均衡)──► 真实目标 Pod IP (10.244.1.15)
1) 服务注册(Service 如何与 Pod 关联?)¶
- Label Selector 匹配:用户创建 Service 时指定
selector标签,匹配对应的后端 Pod。 - EndpointController 动态追踪:
- K8s 内核的
EndpointController不间断监听 Pod 的创建、销毁与就绪状态; - 只有就绪探针(ReadinessProbe)检查通过的 Pod,其 IP:Port 才会被加入到对应 Service 的
Endpoints(或新版EndpointSlice)列表中;若 Pod 异常或退出,自动从列表中剔除。 - EndpointSlice 优化:早期的 Endpoints 对象在一个大 List 中存储所有 Pod,成百上千 Pod 规模下哪怕变更一个 Pod 都会触发全量广播与节点刷新,造成网络暴风;
EndpointSlice将其切片分段(默认 100 个一组),极大降低了大型集群的控制面开销。
2) 服务发现与流量转发(两层协作)¶
- 控制面 / 名字解析(CoreDNS):
- K8s 集群内内置 CoreDNS 服务,作为集群内默认 DNS 递归服务器。
- 每个 Pod 的
/etc/resolv.conf默认配置了 CoreDNS 的 ClusterIP。 - 当调用跨服务短域名
user-service时,DNS 自动按搜索域(names.svc.cluster.local)补全完整 FQDN,解析并返回该 Service 的 ClusterIP(VIP)。 - 数据面 / 负载转发(kube-proxy):
- ClusterIP 本质上是一个虚拟 IP,并不挂载在宿主机的任何物理或虚拟网卡上,无法直接 ping 通;
- 宿主机上的
kube-proxy监听 Service 和 EndpointSlice 的变化,并在宿主机内核网络栈维护转发规则:- iptables 模式:
- 利用 Linux netfilter 的 PREROUTING 和 OUTPUT 链进行 DNAT(目标地址转换);
- 通过随机概率匹配(
statistic --mode random)模拟轮询转发; - 缺点:iptables 规则是线性遍历(复杂度 \(O(n)\)),当集群 Service 数量超过数千时,规则链条过长,网络吞吐急剧下降,且刷新规则有内核锁竞争。
- IPVS 模式(生产高并发推荐):
- 基于内核 IPVS(LVS)模块,使用**哈希表(Hash Table)**存储路由规则,查询复杂度为 \(O(1)\);
- 支持真实丰富的负载均衡算法(Round-Robin、加权轮询、最小连接数等);
- 高并发下连接建立延迟更低、吞吐更高、资源消耗更平稳。
3) Headless Service(无头服务)¶
- 定义:配置
clusterIP: None的 Service。 - 原理:不分配 ClusterIP,kube-proxy 也不生成任何转发规则;CoreDNS 解析该域名时,直接返回后端所有健康 Pod 的真实 IP 列表(A 记录)。
- 适用场景:
- 有状态服务(StatefulSet):如 MySQL 主从、Kafka、ZooKeeper、Elasticsearch 集群,客户端需要感知具体节点身份与角色,进行点对点特定路由或自建客户端负载均衡。
4. Pod 生命周期与零停机(平滑升级与优雅停机)¶
在后端微服务发布更新时,如果处理不当,极易发生 502 Bad Gateway 或 Connection Reset by Peer。
1) 三类健康检查探针(Probes)¶
- StartupProbe(启动探针):
- 作用:判断容器是否已启动成功。在 StartupProbe 成功之前,Liveness 和 Readiness 探针均不会启动。
- 场景:解决 Java/JVM 等冷启动很慢的胖应用在启动过程中就被 Liveness 误杀的问题。
- LivenessProbe(存活探针):
- 作用:判断容器是否存活。如果探测失败达到阈值,kubelet 会依据重启策略杀掉并重启该容器。
- 场景:解决服务陷入死锁、死循环假死等容器未退出但无法对外提供响应的场景。
- ReadinessProbe(就绪探针):
- 作用:判断容器是否已经准备好接收流量。若探测失败,不会重启容器,而是将其从 Service 的 Endpoints 列表中暂时剔除,不再接收新流量。
- 场景:服务热身、加载大缓存或瞬时依赖抖动,待就绪后自动重新接流。
2) 为什么发布时会出现流量抖动(502)?¶
- 根因分析:
- 当 Deployment 进行滚动更新触发 Pod 销毁时,应用接收
SIGTERM信号准备退出 与 K8s 控制面将该 Pod IP 从 Service/Endpoints 中摘除 这两个流程是异步并发执行的! - 若应用收到
SIGTERM立即结束进程,但此时局部 Node 的 kube-proxy 尚未完全刷掉该 Pod 的转发规则,新进入的请求依然会路由到已关闭的容器,导致连接重置(Connection Refused/502)。
3) 生产环境平滑发布黄金组合¶
- 配置
preStop延迟脚本: - 在容器 lifecycle 中配置
preStop: exec: command: ["sleep", "15"]; - 强制容器在收到
SIGTERM前先休眠 10~15 秒,确保所有工作节点的 iptables/IPVS 规则以及 Ingress/网关都已完全摘除该 Pod。 - 应用层支持优雅停机(Graceful Shutdown):
- 捕获
SIGTERM信号后,停止接收新请求,处理完在途现存请求并关闭数据库连接池等外部资源后再退出。 - 设置足够的停机等待时间:
- 确保
terminationGracePeriodSeconds(默认 30s)大于preStop sleep 时间 + 业务在途最长处理时间。
5. 资源管理与 QoS 调度等级¶
1) Requests vs Limits¶
requests(资源请求):- 调度阶段依据:kube-scheduler 仅根据节点的 剩余未分配 requests 资源 决定 Pod 是否能调度上去,而不是根据当前节点实际的实时消耗。
limits(资源上限):- 运行时控制:通过 Linux 底层
cgroups强制约束容器的最大资源开销。 - CPU 是可压缩资源:当容器 CPU 超出 limits,不会被杀死,而是被 Linux CFS 限流(Throttling),导致响应耗时显著升高。
- Memory 是不可压缩资源:当容器内存超出 limits,直接触发操作系统内核 OOM,被系统强制杀死(状态变为
OOMKilled,容器退出码为137)。
2) QoS(服务质量等级)与节点驱逐(Eviction)¶
当 Node 节点内存或磁盘耗尽时,kubelet 会按 QoS 优先级从低到高驱逐 Pod: 1. BestEffort(最低优先):Pod 内所有容器均未设置 requests 和 limits。节点资源紧缺时最先被杀死/驱逐。 2. Burstable(弹性伸缩):Pod 内容器设置了 requests,且 requests < limits。允许适度超卖,在 BestEffort 驱逐完后若仍吃紧则被驱逐。 3. Guaranteed(最高保障):Pod 内所有容器的 CPU 和 Memory 都显式配置了 requests 且 requests == limits。调度优先级最高,最后才会被考虑驱逐(生产关键核心业务标配)。
6. 服务暴露与网关入口对比¶
| 暴露方式 | 层级 | 核心工作原理 | 适用场景 |
|---|---|---|---|
| ClusterIP | L4 | 分配集群内部专用的虚拟 IP,只能在集群内部访问 | 微服务内部 RPC/HTTP 相互调用 |
| NodePort | L4 | 在每个 Node 节点上开放相同的静态端口(默认范围 30000-32767),请求到达任意 Node:NodePort 均被转发至后端 Pod | 线下测试、临时外通;缺点是占用宿主机端口、无法收敛管理 |
| LoadBalancer | L4 | 结合公有云厂商(AWS ELB、阿里云 SLB 等),自动申请云上外部负载均衡器并绑定 NodePort | 关键公网四层入口,但每个 Service 独占一个外部 LB,成本较高 |
| Ingress | L7 | 七层反向代理控制器(如 Ingress-Nginx、Envoy),通过单个公网入口 IP,根据 Host 域名和 Path 路由将请求转发至内部 ClusterIP | 生产首选外部 HTTP/HTTPS 入口;支持 SSL 卸载、灰度切流、URL 路由收敛 |