Appearance
高级面试:答案必须能落到一条失败断言
以下四题各对应一个项目实验。讲法是先回答结论,再解释哪一次读写建立保证,最后说出边界。每题末尾都给出可现场改变的条件。其他 80 道练习保留在专项题库。
Go:为什么 race 通过仍可能超卖?
原子读和原子写分开做,中间仍允许另一个请求先扣;即使没有未同步内存访问,两个人也可能返回成功。CAS 在同一个计数器上提供“比较并扣减”的线性化点。项目的并发测试同时断言成功次数和余量;只断言余量为零会漏掉重复成功。
完整源码:modeldock/labs/runtime/lab_test.go · 第 43–55 行
go
// A single CAS linearizes one counter, not two tenants plus a global limit.
func claim(remaining *atomic.Int64) bool {
for {
n := remaining.Load()
if n == 0 {
return false
}
if remaining.CompareAndSwap(n, n-1) {
return true
}
}
}**追问:**如果再加一个团队上限?两个 CAS 分别成功与失败之间没有整体事务。单机锁住所有相关状态,多进程用条件更新和事务;选择取决于不变量覆盖的边界。并发实验。
Kubernetes:Reconcile 返回成功,为什么对象还未 Ready?
返回值描述本轮处理,而不是容器承诺。API Server 已接受 Deployment,副本仍需要调度、拉镜像、探针和 status 汇报。要先检查控制器看到的 generation、更新副本与可用副本。否则滚动中旧 Pod 为 Ready 会被误报为新版本成功。
完整源码:modeldock/operator/main.go · 第 145–155 行
go
if ready {
phase, truth = "Running", "True"
}
prior, _, _ := unstructured.NestedMap(u.Object, "status")
transition := meta.Now().Format("2006-01-02T15:04:05Z07:00")
conditions, _, _ := unstructured.NestedSlice(u.Object, "status", "conditions")
if len(conditions) > 0 {
if old, ok := conditions[0].(map[string]any); ok && old["status"] == truth {
if t, ok := old["lastTransitionTime"].(string); ok {
transition = t
}**追问:**为什么不每轮更新 lastTransitionTime?没有状态转换时改变时间也会触发 Watch 和资源版本变化,白白占满工作队列。envtest 与 Kind 的不同断言。
ModelDock:数据库扣费成功,进程没来得及创建 CR,为什么不能退钱了事?
此刻你可能不知道 apiserver 有没有接受请求。业务事务同时保存 outbox;重试向确定身份投递、核对实际 UID 后确认。预留是预算,不是物理设备承诺;实际释放前要确认受管对象消失。
完整源码:modeldock/admission/main.go · 第 66–72 行
go
if _, e = tx.Exec("UPDATE reservation SET state='reserved',node=? WHERE id=?", node, id); e != nil {
return e
}
if _, e = tx.Exec("INSERT INTO outbox(id) VALUES(?)", id); e != nil {
return e
}
return tx.Commit()**追问:**为什么不在 SQL 事务内一直等 Kubernetes?另一个系统不会参加本地提交协议,而且慢请求会延长锁竞争。生产可用短事务租约加后台对账,代价是引入明确的中间状态与补偿监控。实际验收。
OpsPilot:如何避免审批后改到另一个同名服务?
审批描述的是特定 UID、generation 和动作。执行前重读,JSON Patch 将条件 test 和修改原子提交。响应丢失用 operation ID 检查是否已经生效,再用新的 rollout 状态验证。仅按名字 GET 再 PATCH 仍有竞争窗口。
完整源码:opspilot/cluster/tools_live.py · 第 49–58 行
python
def patch_plan(d,uid,generation,operation):
m=d["metadata"];spec=d["spec"]["template"]["spec"]
if m["uid"]!=uid:raise ValueError("Deployment replaced")
if m.get("annotations",{}).get("lab.modeldock.dev/last-operation")==operation:return []
if m["generation"]!=generation or spec.get("nodeSelector")!={"lab.invalid/pool":"absent"}:raise ValueError("stale or out-of-policy plan")
return [{"op":"test","path":"/metadata/uid","value":uid},{"op":"test","path":"/metadata/resourceVersion","value":m["resourceVersion"]},
{"op":"test","path":"/spec/template/spec/nodeSelector","value":{"lab.invalid/pool":"absent"}},
{"op":"remove","path":"/spec/template/spec/nodeSelector"},
{"op":"add","path":"/metadata/annotations","value":{**m.get("annotations",{}),"lab.modeldock.dev/last-operation":operation}}]**追问:**保存检查点能不能替代幂等键?图的下一节点在 Agent 数据库里,副作用在集群;两处没有同一个事务。故障插在两次写之间就可以重放。恢复与拒绝测试。