题目重点:题目提供 NEW/FILLED 样例与 getOrderStatus,追问无成交才可 CANCEL;区分事件幂等与订单状态机。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练版单 orderId 的事件按分区顺序输入,NEW 给 totalQty>0,FILLED 给本次成交量>0;同一订单的 symbol/side 不能中途改变。
- 原始样例未给 event ID;如果上游可能重放,必须增加 eventId 或 Kafka offset+partition 等稳定标识才能可靠区分重试与两笔相同数量的真实 fill。
- 无 fill 可 CANCEL;取消后后续 fill 拒绝或隔离,题目未明确异步竞态处理。getOrderStatus 对未知 ID 的返回以接口契约为准,训练版 null。
解题路线
- 状态 orderId→(totalQty,filledQty,state,attrs)。NEW 若首次出现则创建;FILLED 先要求订单存在、未取消/完成且 0<qty≤totalQty−filledQty,再累计,filled=0 为 NEW、0<filled<total 为 IN_PROGRESS、filled=total 为 COMPLETED。
- CANCEL 仅在 filledQty=0 的订单允许,转为 CANCELED;已部分成交不可按题目扩展直接取消。若产品语义实际允许撤销剩余量,要另定义不同题。
- 消费消息与更新 DB 的提交需与 Kafka offset 协调;至少一次消费下先按 eventId 去重再改状态,状态与去重记录同事务写入,然后提交 offset。没有 eventId 时只能指出无法完美去重。
- 保留失败事件或错误原因:FILLED 超量、重复 NEW 属性冲突、无订单 FILLED、取消后 FILLED。读 API 只读已提交订单视图。
- 用样例 1234 的 5+3+2 和 1111 的 5/10 核验;再测两条数量相同但 event ID 不同的合法 fills,避免按数量去重。
手动走一遍
1234 NEW 10→NEW;FILLED 5→IN_PROGRESS(5/10);FILLED 3→IN_PROGRESS(8/10);FILLED 2→COMPLETED(10/10)。5678 NEW 20 后 CANCEL→CANCELED;1234 成交过后 CANCEL 不允许。
规模与取舍
E 条有序事件每条平均 O(1) 哈希更新,O(O+E_id) 存储,其中 O 为订单数、E_id 为保留的去重 ID 数;若去重记录设置有限保留期,必须与最大重放期协调。Kafka/数据库持久性和顺序开销另算。
易错点与追问
容易失分的边界
- 把每条 FILLED 数量当累计量而不是本次增量。
- 按 (orderId,qty) 去重,两笔相同成交量会被误删。
- 先提交 Kafka offset 后写状态,崩溃时丢事件。
- 部分成交后仍允许原扩展中的 CANCEL。
- 把此题与 live/paused 命令状态机混为一题。
继续深挖
- Kafka 重放但无 eventId 时能否保证正确?
- 同 orderId 消息跨 partition 乱序怎么办?
- 若允许撤销未成交剩余量,状态和数量怎样定义?
- 如何处理 FILLED 与 CANCEL 竞态?
