最终一致性如何兼顾用户体验?
简化版
最终一致不应让用户看到“成功后又消失”或无解释旧值。 主流程返回明确的 PENDING/SUCCEEDED/FAILED 状态和查询凭证;会话内可合并待处理结果实现读己之写,后台以通知或刷新展示最终状态。 涉及资金和库存时不能伪造成功,超出承诺窗口要升级并提供可追踪处理入口。
详细版
先按业务定义可接受陈旧时间和用户承诺。 写请求返回 operationId,查询接口读取权威状态并展示处理阶段。 同一用户短期读取可携带 session token、最低版本或 sticky routing,避免读到比上次更旧的数据。
UI 使用“处理中”而非乐观成功;可逆低风险动作可以 optimistic UI,但失败时必须明确回滚。 重复提交以 idempotencyKey 返回同一操作。 补偿完成后通过推送、站内信或轮询更新,并保留客服查询证据。
完整版教学
一、用户在意的是稳定预期
技术上 5 秒收敛可能可接受,但页面先显示成功、刷新后消失会破坏信任。 体验设计要表达不确定状态和预计时间。 不同业务对“等待”和“错误成功”容忍度不同。
最终一致允许后台延迟,不允许前台含糊承诺。
二、显式状态机
ACCEPTED -> PROCESSING -> SUCCEEDED
|-> FAILED
|-> MANUAL_REVIEW
接口返回 operationId、当前状态和建议重试时间。 状态转换单调,终态不能因旧事件回退到处理中。
三、读己之写
用户提交地址修改后,可在会话层暂存 pending 值。 查询返回权威数据时合并尚未确认的本次操作,并明确“同步中”。 也可携带最低版本 token,让服务路由到已追平副本或等待。 pending 数据必须有超时和最终核验,不能永久覆盖权威值。
四、单调读
用户已经见过版本 12,下一次不应返回版本 10。 客户端或网关携带 lastSeenVersion。 副本低于该版本时等待、转主库或返回可重试。 多设备之间若无共享会话 token,不自动获得同样保证。
五、乐观 UI 的边界
点赞、收藏等可逆低风险操作可先更新界面。 扣款、库存确认、实名审核不能在权威确认前显示最终成功。
| 场景 | 推荐体验 |
|---|---|
| 点赞 | 乐观更新,失败回滚 |
| 积分 | 显示预计到账 |
| 支付 | 处理中,查询最终结果 |
| 发货 | 以权威状态为准 |
六、重复与超时
网络超时不代表操作失败。 客户端用同一 idempotencyKey 重试,服务返回同一 operationId 和结果。 不要让用户反复点击产生多个订单或扣款。 达到一致性窗口后自动升级告警和人工队列。
七、通知与客服
后台完成后可用 WebSocket、Push、站内信或客户端轮询更新。 通知也可能丢失,用户重新查询仍应得到权威状态。 客服可按 operationId 查看时间线、失败原因和补偿进度。 提示语不泄露内部堆栈或敏感信息。
八、常见误区与追问
- 误区:最终一致就统一提示稍后重试。 应给状态、凭证和预计时间。
- 误区:所有动作都适合乐观成功。 高风险不可逆操作必须等权威确认。
- 误区:超时就是失败。 原请求可能已成功,重试必须幂等。
- 误区:推送通知能替代查询。 通知会丢,查询仍是权威入口。
- 追问:如何实现读己之写? pending 合并、最低版本 token 或主库路由。
- 追问:如何避免状态倒退? 版本比较与单调状态机。
- 追问:超窗怎么处理? 自动升级、限制相关动作并提供人工追踪。
九、加强记忆
- 明确 PENDING,而非伪造成功。
- operationId 让用户可查询。
- 会话 token 提供读己之写和单调读。
- 乐观 UI 只用于低风险可逆动作。
- 超时幂等重试,超窗升级并让客服可追踪。