Skip to content

高级面试:答案必须能落到一条失败断言 ​

以下四题各对应一个项目实验。讲法是先回答结论,再解释哪一次读写建立保证,最后说出边界。每题末尾都给出可现场改变的条件。其他 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 数据库里,副作用在集群;两处没有同一个事务。故障插在两次写之间就可以重放。恢复与拒绝测试。