题目重点:棋盘、轮次和棋钟由服务端统一提交,客户端倒计时用于展示。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 双人国际象棋,服务端验证合法走法/胜负;训练无spectator大规模fanout,棋钟每方独立,走后是否increment先声明。
- 同一game的动作有顺序,网络断开是否暂停需产品规则;训练不暂停,按server接受合法move的时刻扣时,不信客户端时间。
解题路线
- 匹配队列按模式、rating范围及区域分桶,等待时间越长逐步扩大范围;匹配双方时原子移除queue ticket,game ID及两方assignment幂等,避免一人同时进两局。
- Game存board、turn、move_seq、两方remaining、turn_started_at、version、status。move(game,expected_seq,client_move_id)先身份/轮次/合法性校验,再按服务端单调时间计算消耗,原子写move与新状态。
- 棋钟不必每秒写库:显示remaining−(now−start),合法move时结算。超时调度器持有game version,触发时重新检查当前turn/version及到期,过期timer不能在玩家刚走后判输。
- 同game单owner actor可简化提交,持久log/snapshot用于恢复;stateless API+数据库CAS也能保证顺序。状态放哪里与服务进程是否stateless是不同问题,没有唯一正确口号。failover需lease/fencing,旧owner不能继续提交。
- 重连按last_seq补move,重复move ID返回原结果;客户端乐观棋盘需接受server拒绝并回退。异地延迟补偿规则可有,但需界定上限、防造假及一致判定,不能悄悄依client timestamp扣时。
手动走一遍
剩5秒、server接收合法move时已过6秒,按本训练合同判超时,不因client说“我4秒就点了”改变。旧timeout任务携带seq12,当前已seq13,重检丢弃,避免新回合立刻判负。owner写move成功但响应丢失,重试同client_move_id返回seq13,不再走一次。
规模与取舍
G活跃局状态O(G),每move局部合法校验和一次条件提交;匹配桶检索取决于rating数据结构。广播每move O(双方设备+订阅者),棋钟展示不应产生每秒每局写放大。
易错点与追问
容易失分的边界
- 只靠前端倒计时可以被修改且多设备不同步。
- 先发走棋给对方再持久化,故障后两端与服务器分歧。
- 超时定时器不检查game版本。
继续深挖
- 进程重启没有原单调时钟基准时,怎样持久化恢复deadline?
- 大量观战如何从game权威写路径分离fanout?
