multi-livelisted
Install: claude install-skill 1398281322-a11y/java-backend-guardrails
# 异地多活
## When to Invoke
明确要跨城同时写、机房级容灾且 RTO 接近 0、面试追问多活。文旅默认 **先同城多 AZ**,不要一上来多活。
## When NOT
普通高可用、备份、主从 → `ha-dr`。多活比容灾贵一个数量级(冲突、数据同步、路由)。
## 风险(面试考点)
灾备:异地冷/热备,切过去才服务,RTO/RPO 较大。
**多活**:多个城市 **同时** 接流量。难点是 **写冲突** 和 **会话路由**。
没有单元化(按 user_id 固定落到一个单元)就会双写同一订单。跨单元查询(全国排行、商户看所有门店)会变难。时钟、延迟、分区(脑裂)都要预案。
微信红包一类是单元化 + 尽量不跨单元写。不是「两个机房随便写同一行再 last-write-wins」。
## 方案选型(轻量优先)
| 优先级 | 方案 |
|--------|------|
| 1 | 单城多 AZ + 异地备份(默认) |
| 2 | 同城双活(距离短、同步复制可接受) |
| 3 | 异地单元化多活:用户/商户哈希到单元,写不跨单元 |
| 禁止默认 | 多城对同一主键同时写 + 无冲突规则 |
## 默认方案
单元键与分片键类似:**不可变、查询总带着**(`user_id` 或 `merchant_id`)。DNS/网关按单元路由。跨单元只走异步消息,接受延迟。全局唯一 ID 带单元号,避免冲突。
资金类:支付渠道本身有区域限制时,更不要假装全球同一钱包双活。
## 反例
错误:两个机房都连同一主库叫多活。
正确:那是计算多活、存储仍单点。
错误:冲突用「谁新谁赢」覆盖支付状态。
正确:资金状态机,不允许无序覆盖。
错误:没单元化就上异地双写。
正确:先单元化。
## 验证
- 单用户请求稳定落到同一单元。
- 一城宕机,该城用户有预案(切换或降级),他城用户不受影响。
- 没有跨城同步改同一余额行。
## 评审清单
- [ ] 是否其实 ha-dr 就够
- [ ] 有单元键且写不跨单元
- [ ] 资金冲突规则不是 last-write-wins
---