国际化电商多数据中心分布式系统架构设计

版本:v9.0
日期:2026-05-15
参与方:架构师(A)、技术产品(P)、研发(D)
状态:已评审确认


阅读指引

本文档采用由粗到细、逐层展开的方式组织:

层次 章节 读者 回答的问题
第一层 1-2 所有人 系统要解决什么问题,核心约束是什么
第二层 3-4 架构师、TL 整体如何组织,存储怎么分层
第三层 5-6 架构师、研发 核心概念定义,业务怎么流转
第四层 7-9 研发、运维 具体怎么实现,怎么配置,出了问题怎么查
第五层 10-11 所有人 分阶段怎么落地,关键决策为什么这样做

目录


术语与统一语言

区域与部署

术语 说明
Region 地理区域,本文指 EU(欧盟)、SEA(东南亚)、US(北美)三个独立数据中心集群
AZ Availability Zone,可用区,同 Region 内物理隔离的数据中心,AZ 间延迟 < 2ms
权威节点 Authoritative Node,某类数据的唯一写入主节点,其状态代表最终事实
影子引用 Shadow Reference,原始数据的轻量副本,仅含非敏感字段,用于跨区查询路由
Control Plane 控制平面,负责配置下发、服务注册、策略管理
Data Plane 数据平面,负责实际业务请求处理
数据主体 Data Subject,数据所描述的自然人(即用户),GDPR 核心概念
数据主权 Region 数据主体注册时所在 Region,决定其 PII 数据的存储归属

合规元数据体系

术语 说明
合规元数据 Compliance Metadata,描述数据字段敏感类型、处理规则、保留期限等的结构化信息
合规元数据注册中心 Compliance Metadata Registry,运行时维护字段合规类型的内存缓存,从 DB 加载
字段合规元数据表 compliance_field_metadata,持久化存储所有字段合规类型的数据库表(Global DB)
DataType 数据敏感类型:PII / FINANCIAL / BEHAVIORAL / PUBLIC
Sensitivity 敏感等级 1-5,影响日志脱敏强度(5=完全遮盖,1=不脱敏)
合规围栏 ComplianceFence,运行时拦截数据访问、执行合规决策的 AOP 层
合规代理读 跨 Region 访问 PII 数据时,通过数据所在 Region 的服务代理读取,数据本身不传输
文档性注解 @ComplianceData 注解在 v4 中仅作开发期文档提示,运行时以 DB 元数据为准

一致性

术语 说明
STRONG_LOCAL 强一致(Region 内),Seata AT + MySQL MGR 保证,分区时拒绝请求
STRONG_GLOBAL 强一致(跨 Region),Redis 原子预占 + 消息异步落库权威节点
SESSION Session 一致,Vector Clock token 保证读己之写
EVENTUAL 最终一致,Canal + Kafka 异步复制,允许短暂不同步
Vector Clock 向量时钟,追踪分布式事件因果顺序
CRDT Conflict-free Replicated Data Type,无冲突可复制数据类型
OR-Set Observed-Remove Set,CRDT 的一种,并发 add/remove 时 add 语义优先

事务与消息

术语 说明
Seata AT 自动事务模式,框架自动生成 undolog,对业务代码透明
Seata TCC Try-Confirm-Cancel,业务方手动实现三个接口,精细控制
Outbox Pattern 发件箱模式,业务写入与消息记录在同一本地事务,由 Canal CDC 异步投递 Kafka
CDC Change Data Capture,变更数据捕获,Canal 监听 binlog 实现
幂等 同一操作执行多次与执行一次效果相同
重试 Topic 链 Kafka 无原生重试,自建 retry..1/2/3 → dlq. 的消息重试机制
DLQ Dead Letter Queue,死信队列,消息超过最大重试次数后进入,触发 P1 告警
MirrorMaker 2 Kafka 官方跨集群复制工具,用于跨 Region Topic 同步

存储

术语 说明
MGR MySQL Group Replication,MySQL 官方高可用集群,本文用单主模式
Canal 阿里开源 MySQL binlog 订阅工具,本文承担 Outbox 投递和非 PII 数据同步
Global Coordination DB 全局协调库,MySQL MGR 部署于 SEA,存储影子引用、幂等记录、合规元数据等
Regional MySQL 区域业务库,各 Region 独立,存储本区业务数据和 PII
预占 Pre-occupy,下单时先 Redis 原子扣减库存,实际 DB 写入异步进行
水位线 Watermark,库存剩余量告警阈值,低于此值触发动态补充

可观测性

术语 说明
SkyWalking Apache 开源 APM,Java Agent 零侵入链路追踪
Span 链路追踪最小单元,代表一次操作
Trace 由多个 Span 组成的完整请求调用链
P99 延迟 99% 请求延迟低于此值,反映尾部延迟
SLO Service Level Objective,服务级别目标
RTO Recovery Time Objective,故障恢复时间目标

第一层:背景与目标

1. 业务背景

1.1 现状与问题

国际化电商平台覆盖 EU、SEA、US 三大 Region,现有单数据中心架构存在五类问题:

问题一:访问延迟高
  EU / US 用户访问 SEA 数据中心,跨洲 RTT 150-300ms
  每增加 100ms 延迟,转化率下降约 7%

问题二:合规风险
  EU 用户 PII 数据存储在 SEA,违反 GDPR
  最高罚款全球营业额 4%

问题三:可用性不足
  单数据中心故障 → 全球服务中断
  年故障时长约 8.7 小时(99.9%)

问题四:大促扩展瓶颈
  黑五、Ramadan 等大促峰值是日常 50 倍
  单中心最高承载 10x

问题五:新站点上线慢
  每个新 Region 需研发介入,周期 2 周+
  错过市场进入时间窗口

1.2 目标

指标 当前 目标 验收方式
P99 访问延迟 350ms < 100ms SkyWalking
服务可用性 99.9% 99.99% Prometheus
合规覆盖率 0% 100%(GDPR/PDPA/PIPL) 合规审计扫描
新 Region 上线 14 天 3 天 checklist 耗时
大促峰值承载 10x 50x 压测报告
库存超卖率 无监控 < 0.01% 对账日报

1.3 技术选型约束

可直接使用(团队有生产经验):
  MySQL 8  ·  Redis Cluster  ·  Kafka
  Seata AT  ·  Spring Boot / Spring Cloud Alibaba

主动排除(无生产经验,风险不可控):
  TiDB  ·  RocketMQ(统一到 Kafka)

新引入(门槛可控,安排专人负责):
  MySQL Group Replication(MGR) — 1 人专项
  Canal(binlog 订阅)           — 1 人专项
  MirrorMaker 2                  — 与 Canal 同一人负责
  SkyWalking                     — Java Agent,零侵入

1.4 三方核心诉求

架构师(A):核心机制强制在框架层,不依赖人的自觉。
             每个组件故障都有清晰排查路径,出问题能接住。

技术产品(P):新 Region 上线 = 填写配置文件,研发不介入。
               合规规则变更由合规团队自助完成,不走研发排期。
               大促流程可提前预演,不出现临时决策。

研发(D):本地一条命令启动完整环境,分布式细节框架屏蔽。
           查日志直接看到字段合规类型,不用翻代码。
           出了线上问题有标准排查手册。

2. 核心问题与约束

2.1 三大核心矛盾

┌──────────────────────────────────────────────────────────────────┐
│  矛盾一:合规不动 vs 数据随用户走                                 │
│  法规:GDPR 要求 EU 用户 PII 必须存储在 EU 境内                  │
│  业务:用户在 SEA 下单,收货地址(PII)需要跨区查询              │
│  取舍:数据分级 + 影子引用 + 合规代理读                          │
├──────────────────────────────────────────────────────────────────┤
│  矛盾二:强一致 vs 高可用                                         │
│  强一致:支付扣款、库存扣减不能有数据丢失                        │
│  高可用:网络分区时系统仍需对外服务                              │
│  取舍:强一致边界收缩到 Region 内                                │
│        跨 Region 用 Redis 预占 + 消息最终一致                    │
├──────────────────────────────────────────────────────────────────┤
│  矛盾三:数据集中 vs 数据分散                                     │
│  集中:强一致要求单一权威节点协调所有写入                        │
│  分散:低延迟要求数据就近副本                                    │
│  取舍:库存单写权威节点(集中)                                  │
│        + 各 Region Redis 预占(分散)                            │
│        + Canal 异步复制只读副本(分散)                          │
└──────────────────────────────────────────────────────────────────┘

2.2 数据分级矩阵

级别 代表字段 存储位置 一致性级别 跨区读策略 合规约束
PII 姓名、地址、手机 Regional MySQL,锁定本区 STRONG_LOCAL 代理读,审计日志 GDPR/PDPA/PIPL
FINANCIAL 支付流水、退款 SEA-MySQL 权威 STRONG_GLOBAL 直读权威节点 金融监管留存 5 年
BEHAVIORAL 购物车、浏览、评价 Redis + Regional MySQL EVENTUAL Canal 副本直读 无特殊约束
PUBLIC 商品、分类、配置 全局同步 EVENTUAL 直读本地副本 无约束

2.3 合规元数据设计原则

原则一:注解只标类型,规则只在配置,元数据只在数据库
  @ComplianceData(type = DataType.PII) → 文档提示,不参与运行时决策
  合规规则(谁可以读、如何读)→ Apollo 配置中心
  字段合规类型(哪个字段是什么类型)→ compliance_field_metadata 表

原则二:DB 是唯一权威来源
  代码注解与 DB 不一致时,DB 优先
  CI 校验:注解降级方向仅警告,注解升级方向阻断发布

原则三:合规元数据变更不走研发流程
  合规团队通过管理 API 提申请 → 两人审批 → 到期自动生效
  变更历史永久留档,满足 GDPR 审计要求

原则四:元数据对所有角色可见
  研发:代码注解(开发期提示)
  DBA:MySQL 字段 COMMENT 自动同步([PII-5] 收货人姓名)
  运维:日志自动脱敏并标注类型
  合规团队:管理界面统一查询、修改、审计

2.4 一致性策略定义

策略 实现机制 适用场景 分区时行为
STRONG_LOCAL Seata AT + Region 内 MGR 支付写入、PII 写入 拒绝,返回 503
STRONG_GLOBAL Redis 原子预占 + 异步落库权威节点 库存扣减 Redis 继续,落库消息堆积
SESSION Vector Clock token + 本地从库读 订单查询、用户信息 降级,返回 X-Stale: true
EVENTUAL Canal + Kafka 异步复制 购物车、推荐、搜索 继续服务陈旧数据

第二层:整体架构

3. 系统全景

3.1 整体架构图

╔═══════════════════════════════════════════════════════════════════════╗
║                             用户访问入口                               ║
║  EU 用户 ──┐                                                          ║
║            ├──► GeoDNS(就近解析)──► Anycast VIP ──► Regional GW    ║
║  SEA 用户 ─┤                                                          ║
║  US 用户 ──┘                                                          ║
╚═══════════════════════════════════════════════════════════════════════╝
                                   │
╔══════════════════════════════════▼════════════════════════════════════╗
║                  Gateway 层(Spring Cloud Gateway)                    ║
║  限流熔断(Sentinel) │ 认证鉴权(Sa-Token) │ Region路由 │ 灰度分流      ║
║  注入:X-Region / X-Trace-Id / X-Vector-Clock / X-User-Tags           ║
╚══════════════════════════════════╦════════════════════════════════════╝
                                   ║
╔══════════════════════════════════▼════════════════════════════════════╗
║                    Service 层(Spring Boot 3.x)                       ║
║                                                                       ║
║  OrderService │ InventoryService │ UserService │ CartService │ ...    ║
║                                                                       ║
║  ─────────────── 框架能力层(Starter 统一提供)──────────────────     ║
║  ConsistencyPolicy AOP  ComplianceFence AOP  Idempotent AOP          ║
║  VectorClock Filter     FeatureFlag Engine   RegionContext            ║
║  ComplianceMetadataRegistry(运行时合规元数据,从 DB 加载)            ║
╚══════════════════════════════════╦════════════════════════════════════╝
                                   ║
╔══════════════════════════════════▼════════════════════════════════════╗
║                        消息层(Kafka)                                 ║
║                                                                       ║
║  Outbox CDC(Canal → Kafka)  业务事件 Topic  重试 Topic 链  DLQ      ║
║  MirrorMaker 2(跨 Region Topic 同步:cart.crdt.sync / catalog.*)    ║
╚══════════════════════════════════╦════════════════════════════════════╝
                                   ║
╔══════════════════════════════════▼════════════════════════════════════╗
║                        存储层(MySQL 生态)                            ║
║                                                                       ║
║  ┌─────────────────────────────────────────────────────────────┐    ║
║  │  Global Coordination DB(MySQL 8 MGR,SEA 机房,3 节点)     │    ║
║  │  order_ref │ compliance_field_metadata │ idempotent_record   │    ║
║  │  user_region_mapping │ inventory_warmup │ outbox              │    ║
║  └─────────────────────────────────────────────────────────────┘    ║
║                                                                       ║
║  ┌────────────┐      ┌────────────┐      ┌────────────┐             ║
║  │ EU-MySQL   │      │ SEA-MySQL  │      │ US-MySQL   │             ║
║  │ 1主2从     │      │ 1主2从     │      │ 1主2从     │             ║
║  │ EU业务数据 │      │ SEA业务    │      │ US业务数据 │             ║
║  │ EU PII     │      │ 全局库存   │      │ US PII     │             ║
║  └────────────┘      └────────────┘      └────────────┘             ║
║        │    Canal+Kafka(非PII字段,EVENTUAL)   │                    ║
║        └─────────────────┬─────────────────────┘                    ║
║                           │ 只读副本同步                              ║
║  ┌────────────┐      ┌────▼───────┐      ┌────────────┐             ║
║  │ EU-Redis   │      │ SEA-Redis  │      │ US-Redis   │             ║
║  │ Cluster    │      │ Cluster    │      │ Cluster    │             ║
║  │ 库存预占   │      │ 库存预占   │      │ 库存预占   │             ║
║  │ 幂等一级   │      │ 幂等一级   │      │ 幂等一级   │             ║
║  │ Cart CRDT  │      │ Cart CRDT  │      │ Cart CRDT  │             ║
║  └────────────┘      └────────────┘      └────────────┘             ║
╚═══════════════════════════════════════════════════════════════════════╝
                                   ║
╔══════════════════════════════════▼════════════════════════════════════╗
║                        Control Plane(SEA 部署)                       ║
║  Apollo(配置)  Seata Server  SkyWalking OAP  Prometheus+Grafana     ║
║  Canal Server(HA)  XXL-Job(调度)  Nacos(注册中心)               ║
╚═══════════════════════════════════════════════════════════════════════╝

3.2 Region 间数据流动规则

允许的跨 Region 数据流动:
  非 PII 业务字段  → Canal + Kafka 异步复制(EVENTUAL)
  影子引用(OrderRef)→ Canal 复制到 Global Coordination DB
  购物车 CRDT     → MirrorMaker 2 双向同步 cart.crdt.sync
  商品目录        → MirrorMaker 2 单向同步 catalog.*

禁止的跨 Region 数据流动:
  PII 字段       → 禁止直接复制,只允许代理读(数据不离开源 Region)
  支付详情       → 禁止跨区复制(金融合规)
  Redis 数据     → 各 Region 独立,不跨区同步
  同步写操作     → 禁止跨 Region 同步写(延迟不可接受)

3.3 Kafka 消息平台设计

统一消息平台(Canal + 业务消息 共用 Kafka,替代 RocketMQ):

Topic 命名规范:
  Canal 数据变更:  canal.{database}.{table}
  业务事件:        {domain}.{event}
  Outbox 中继:     canal.{db}.outbox
  延迟消息:        delay-{duration}(1s/10s/1m/5m/30m/1h)
  重试:            retry.{domain}.{event}.{n}(n=1,2,3)
  死信:            dlq.{domain}.{event}

MirrorMaker 2 跨 Region 同步:
  SEA → EU/US:  cart.crdt.sync, inventory.sync, catalog.*
  EU → SEA:     cart.crdt.sync(购物车 CRDT 双向)
  PII 相关 Topic 严禁进入 MM2 同步规则

RocketMQ 能力替代映射:
  事务消息  → Outbox Pattern + Canal CDC(延迟 < 50ms)
  延迟消息  → 订单超时用 XXL-Job 扫描,其他用分级延迟 Topic
  自动重试  → 重试 Topic 链(retry.*.1/2/3)
  死信队列  → dlq.* Topic + P1 告警
  顺序消息  → 分区键路由(orderId/userId/skuId 同分区)

4. 存储架构

4.1 存储分层与职责

┌─────────────────────────────────────────────────────────────────────┐
│  Global Coordination DB(全局协调库)                                │
│  技术:MySQL 8 MGR 单主,3 节点跨 AZ,部署在 SEA                   │
│                                                                     │
│  存储内容(全部为非 PII,全局可访问):                              │
│  · order_ref                 订单影子引用(orderId/status/region)  │
│  · compliance_field_metadata 字段合规元数据注册表(唯一权威)        │
│  · compliance_change_approval 合规类型变更审批流                    │
│  · compliance_field_metadata_history 变更历史(永久保留)           │
│  · user_region_mapping        用户归属 Region 映射                  │
│  · idempotent_record          幂等兜底(Redis 降级时使用)          │
│  · outbox                     事务性消息发件箱(Outbox Pattern)    │
│  · inventory_warmup           大促预热分配记录                      │
│  · promotion_usage            促销核销记录                          │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  Regional MySQL(区域业务库)                                        │
│  技术:MySQL 8 主从,1 主 2 从,跨 AZ 部署                         │
│  各 Region 独立,SEA-MySQL 额外承担全局库存权威                     │
│                                                                     │
│  EU-MySQL:EU 业务数据 + EU PII(不出境)                           │
│  SEA-MySQL:SEA 业务数据 + 全局库存权威(inventory_global)         │
│  US-MySQL:US 业务数据 + US PII(不出境)                           │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  Redis Cluster(区域缓存)                                           │
│  技术:Redis 6 Cluster,3 主 3 从,各 Region 独立,不跨区同步       │
│                                                                     │
│  inv:pre:{skuId}               实时可售库存(预占防超卖)           │
│  inv:warmup:{activityId}:{reg} 大促预热分配库存                     │
│  inv:lock:{orderId}:{skuId}    订单预占锁定(用于回滚释放)         │
│  cart:orset:{userId}           购物车 CRDT(OR-Set 序列化)         │
│  idempotent:{bizKey}           幂等记录一级(快速判重)             │
└─────────────────────────────────────────────────────────────────────┘

4.2 库存权威节点设计

设计决策:全局库存写入集中在 SEA-MySQL

写入路径(大促):  下单 → Redis 原子预占(< 1ms)
                        → RocketMQ 事务消息 → SEA-MySQL 异步落库
写入路径(非大促):下单 → Redis 原子预占(< 1ms)
                        → 直接写 SEA-MySQL(~100ms,可接受)

故障降级:
  SEA 主库故障(AZ 级别)→ Sentinel 自动切换(< 30s),Redis 无感知
  SEA 整区故障(Region 级别)→ 手动切换脚本,RTO < 15 分钟

4.3 Outbox Pattern 设计

替代 RocketMQ 事务消息,保证本地事务与消息发送原子性:

  业务写入 + outbox 写入(同一本地事务)
      │ binlog 变更
      ▼
  Canal 监听 outbox 表
      │ < 50ms 延迟
      ▼
  Kafka Producer 发送(幂等生产者)
      │
      ▼
  业务消费者(@Idempotent 幂等消费)

Canal 故障兜底:
  OutboxRelay 定时任务每 100ms 轮询 PENDING 状态消息
  确保 Canal 故障期间消息最终被投递

第三层:领域模型与核心链路

5. 领域模型

5.1 统一语言约定

下列术语在代码、数据库、文档、口头交流中保持一致:

领域概念 统一用词 禁止混用的同义词
订单主体 OrderCore order_main, order_info
订单影子引用 OrderRef order_shadow, order_global
全局库存 InventoryGlobal stock_global, inventory
库存预占 PreOccupy reserve, lock_stock
购物车 Cart(OR-Set 实现) basket, shopping_bag
合规围栏 ComplianceFence data_barrier, data_fence
合规元数据注册中心 ComplianceMetadataRegistry compliance_cache
一致性策略 ConsistencyPolicy sync_policy
区域上下文 RegionContext locale_context, site_context
向量时钟 VectorClock logical_clock
幂等记录 IdempotentRecord dedup_record
大促预热 StockWarmup promotion_prepare
事务发件箱 Outbox message_box, tx_message

5.2 聚合边界

Order 聚合:
  聚合根:OrderCore(含 PII,存 Regional MySQL,不出境)
  值对象:OrderItem(存 Regional MySQL)
  影子:  OrderRef(无 PII,存 Global Coordination DB,Canal 同步)
  不变量:同一 OrderCore 的 OrderItem 属于同一 Region
          状态变更必须通过状态机,不允许直接赋值

Inventory 聚合:
  聚合根:InventoryGlobal(SEA-MySQL,唯一写入入口)
  前置缓冲:Redis 预占(inv:pre:{skuId})
  流水:  InventoryPreOccupy(存 SEA-MySQL)
  不变量:available >= 0(不允许超卖)
          totalStock = available + preOccupied + locked + sold

Cart 聚合(CRDT):
  聚合根:ORSetCart(以 userId 标识,存 Redis)
  不变量:value() 中 qty 之和 >= 0
          merge() 操作幂等、可交换、可结合

ComplianceMetadata 聚合:
  聚合根:FieldMetadata(存 Global Coordination DB)
  历史:  FieldMetadataHistory(永久保留,不可删除)
  审批:  ChangeApproval(变更必须经过两人审批)
  不变量:data_type 变更必须有 change_reason 和 approver

5.3 核心实体定义

OrderCore(订单主体)

OrderCore {
  orderId:       全局唯一,Snowflake(含 Region 位)
  userId:        用户 ID(非 PII,可跨区流动)
  region:        下单所在 Region
  status:        OrderStatus 状态机
  currency:      货币代码
  totalAmount:   订单总金额(含税)

  [PII-5] receiverName:   收货人姓名,不出境
  [PII-4] receiverPhone:  收货人手机,不出境
  [PII-3] receiverAddr:   收货地址,不出境

  version:       乐观锁版本号
}

OrderStatus 状态机(不可跳过,不可逆):
  PENDING_PAYMENT → PAID / CANCELLED
  PAID            → SHIPPED / CANCELLED
  SHIPPED         → COMPLETED

OrderRef(订单影子引用)

OrderRef {
  orderId:      关联 OrderCore.orderId
  userId:       关联 OrderCore.userId
  region:       数据所在 Region(跨区代理读的路由依据)
  status:       订单状态(Canal 异步同步自 OrderCore)
  currency:     货币代码
  totalAmount:  订单总金额(整数脱敏)
}
说明:OrderRef 不含任何 PII 字段,存 Global Coordination DB,全局可读

FieldMetadata(字段合规元数据)

FieldMetadata {
  appName:        服务名(order-service)
  tableName:      数据库表名(order_core)
  fieldName:      字段名 snake_case(receiver_name)
  javaFieldName:  Java 字段名 camelCase(receiverName)
  dataType:       PII / FINANCIAL / BEHAVIORAL / PUBLIC
  description:    字段业务含义说明
  sensitivity:    敏感等级 1-5(影响日志脱敏强度)
  status:         ACTIVE / DEPRECATED / PENDING_REVIEW
  effectiveFrom:  生效时间
  effectiveTo:    失效时间(NULL = 永久有效)
  changeReason:   变更原因
  regulationRef:  关联法规条款(GDPR-Art4)
}

注意:DB 是唯一权威来源。
      @ComplianceData 注解只作开发期文档提示,运行时不参与决策。

ORSetCart(购物车 CRDT)

ORSetCart {
  userId:    购物车所属用户
  addSet:    Map<skuId, Set<"{epoch}:{uuid}:{qty}">>
  removeSet: Map<skuId, Set<tag>>
  epoch:     GC 纪元(每 24h 推进,压缩历史 tag)
}

操作语义:
  add(skuId, qty)  → 生成新 tag 加入 addSet
  remove(skuId)    → 将已知 tag 移入 removeSet(并发新 add 不受影响)
  value()          → addSet - removeSet 的有效商品及数量
  merge(other)     → 各集合取并集(幂等、可交换、可结合)
  gc()             → epoch+1,重建 addSet,清理历史 tag

5.4 值对象定义

VectorClock {
  clocks: Map<nodeId, Long>
  tick()           本地写时递增本节点计数
  merge(other)     取各分量最大值
  happenedAfter()  因果顺序判断
  serialize()      HTTP Header 序列化(Base64 JSON)
}

ConsistencyContext {
  level:          一致性级别
  maxStalenessMs: SESSION 级别允许的最大陈旧毫秒数
  vectorClock:    因果令牌
  traceId:        全局链路追踪 ID
  sourceRegion:   请求来源 Region
  fallback:       分区时降级策略(REJECT / SERVE_STALE / RETURN_CACHED)
}

RegionContext(ThreadLocal,请求级别){
  currentRegion:    当前服务所在 Region(环境变量 REGION_ID 注入)
  userHomeRegion:   用户数据主权 Region(从 user_region_mapping 查询)
  dataOwnerRegion:  当前操作数据的归属 Region
}

6. 核心链路

实线(→)= 同步调用;虚线(⇢)= 异步消息

6.1 下单链路

用户提交订单(EU Region)
    │
    → [Gateway] 验证 JWT,注入 X-Region/X-Trace/X-Vector-Clock,Sentinel 限流
    │
    → [OrderService.createOrder()]
         │
         ①  幂等检查(Redis,< 1ms)
             key = clientOrderId
             PROCESSING → 返回 409
             DONE       → 返回缓存结果
             未命中     → 写入 PROCESSING,继续
         │
         ②  Redis 库存预占(EU-Redis,< 1ms)
             Lua 原子:DECRBY inv:pre:{skuId} {qty}
             remaining < 0 → 立即返回库存不足(不进事务)
             remaining >= 0 → 记录 inv:lock:{orderId}:{skuId}
         │
         ③  本地事务(Seata AT,EU Region 内)
             @GlobalTransactional(timeoutMills=5000)
             ├─ INSERT order_core → EU-MySQL(status=PENDING_PAYMENT)
             └─ INSERT outbox → Global DB(topic=order.created,同一事务)
         │
         ④  事务提交后 Canal CDC 触发
             Canal 监听 outbox 表变更(< 50ms)
             Kafka Producer 幂等发送 order.created
             UPDATE outbox SET status=SENT
         │
         ⑤  幂等更新:PROCESSING → DONE
         │
         → 返回 {orderId, status, X-Vector-Clock}

    ⇢  异步消费 order.created:
         Consumer A:INSERT order_ref → Global DB(@Idempotent)
         Consumer B:INSERT stock.deduct → SEA-MySQL 权威落库(@Idempotent)
         Consumer C:通知 WMS 仓储分配(@Idempotent)

异常处理:
  ② Redis 预占成功,③ Seata 事务失败
    → Seata AT 回滚 order_core + outbox
    → 回调中释放 Redis 预占:INCRBY inv:pre:{skuId}

  ④ Consumer B 落库失败
    → Kafka 重试 Topic 链(retry.order.created.1/2/3)
    → 超过 3 次 → dlq.order.created → P1 告警 + 运维手动处理

  订单 30 分钟未付款
    → XXL-Job 扫描超时订单 → 发 order.timeout 消息
    → OrderService 消费 → status=CANCELLED + 释放 Redis 预占

6.2 库存扣减链路(大促场景)

大促前 T-30min(XXL-Job 触发 StockWarmup.warmup):
    ① SELECT ... FOR UPDATE 锁定 InventoryGlobal,快照 available
    ② 按 Region 流量权重分配(EU:30% SEA:50% US:20%)
    ③ Pipeline 批量写各区 Redis:
         SET inv:warmup:{activityId}:{region} {quota}
         SET inv:wmk:{activityId}:{region}   {quota*0.2}(水位线)
    ④ UPDATE InventoryGlobal SET available=0(全部锁入预热)
    ⑤ 状态机:IDLE → ACTIVE

大促中:
    deduct(skuId, qty, orderId)
    → 查 Apollo 开关 warmup_active?
    → 大促:Lua 原子 DECRBY inv:warmup:{activityId}:{region} {qty}
           remaining < watermark → 异步触发动态调拨
           remaining < 0         → 库存不足,拒绝
    → 非大促:DECRBY inv:pre:{skuId},异步落库 SEA-MySQL

动态调拨(XXL-Job 每 5s):
    某区剩余 < 水位线 → 从全局预留池借出 → INCRBY

大促结束对账:
    各区 Redis GETDEL 剩余量 → 计算实际消耗
    → UPDATE InventoryGlobal SET available=totalStock-实际消耗
    → 状态机:ACTIVE → SETTLED,生成对账报告

6.3 跨区 PII 读取链路

SEA-OrderService 查 EU 用户订单详情
    │
    → ComplianceFence.intercept()
         ① 查 ComplianceMetadataRegistry:
              getByEntityField(OrderCore, "receiverName")
              → DataType=PII,sensitivity=5
         ② 查 CompliancePolicyEngine:
              规则来自 Apollo compliance-rules namespace
              PII.crossRegionRead = PROXY_ONLY
              currentRegion=SEA ≠ dataOwnerRegion=EU
              → 决策:requireProxy(targetRegion="EU")
    │
    → RegionProxyClient.proxyRead("EU", pjp)
         HTTP GET eu-order-service/internal/proxy/order/{orderId}
         Headers:
           X-Internal-Token:     HMAC-SHA256 签名
           X-Compliance-Purpose: ORDER_DISPLAY(白名单内)
           X-Requester-Service:  sea-order-service
           X-Requester-Region:   SEA
    │
    → EU-OrderService 处理:
         验证 X-Internal-Token 签名
         验证 X-Compliance-Purpose 在白名单
         写入合规审计日志(EU-MySQL,不出境)
         返回 OrderCore(含 PII)
    │
    → SEA 服务使用数据,响应结束后 PII 不持久化

6.4 合规元数据变更链路

场景:法务要求将 phoneNumber 从 PII 降级到 BEHAVIORAL

    合规团队通过管理 API 提交变更申请
    → compliance_change_approval 写入 PENDING
    → 通知两位合规审批人
    │
    两位审批人分别审批通过
    → approval.status = APPROVED
    → XXL-Job 到 effectiveTime 时执行变更:
         UPDATE compliance_field_metadata
         SET data_type='BEHAVIORAL', change_reason=?, updated_by=?
         WHERE table_name='user_profile' AND field_name='phone_number'
         INSERT compliance_field_metadata_history(不可删除)
         发布 Apollo compliance-metadata 变更通知
    │
    各服务 ComplianceMetadataRegistry.reload()(热更新,无需重启)
    MySQL 字段 COMMENT 自动更新:
    [BEHAVIORAL-2] 收货人手机(原 PII-4,PDPA_MY-2026 修订)

变更生效验证(运维 API):
    GET /internal/compliance/fields?tableName=user_profile
    → 确认 phone_number 的 data_type 已变更
    → 观察合规违规指标 compliance_violation_total(应为 0)

6.5 购物车 CRDT 同步链路

EU 用户在 EU 区加购 SKU_A 数量 2,同时在 SEA 区加购 SKU_B 数量 1:

EU-CartService.add("U001", "SKU_A", 2):
    ① loadCart → ORSetCart from EU-Redis
    ② cart.add("SKU_A", 2) → addSet["SKU_A"].add("0:uuid1:2")
    ③ SET cart:orset:U001 {serialize} → EU-Redis
    ④ ⇢ Kafka: cart.crdt.sync {userId:"U001", payload, sourceRegion:"EU"}
       → MirrorMaker 2 同步到 SEA-Kafka

SEA-CartSyncConsumer 消费 EU 发出的事件:
    sourceRegion("EU") ≠ currentRegion("SEA") → 需要 merge
    local  = loadCart("U001") from SEA-Redis → {SKU_B:1}
    remote = deserialize(payload)             → {SKU_A:2}
    merged = local.merge(remote)              → {SKU_A:2, SKU_B:1}
    SET cart:orset:U001 → SEA-Redis

最终两区一致:{SKU_A:2, SKU_B:1},无冲突,无数据丢失

GC(XXL-Job 每日 03:00):
    扫描 tag 总数 > 1000 的购物车
    cart.gc() → epoch+1,重建,⇢ Kafka 广播 GC 后快照

6.6 全链路追踪链路

请求注入追踪上下文(Gateway):
  X-Trace-Id:    {regionPrefix}-{timestamp}-{random}
  X-Vector-Clock: base64({"sea-node1":42})
  X-Region:      SEA

SkyWalking Java Agent 自动采集(零侵入):
  HTTP Span / MySQL Span(含 SQL)/ Redis Span
  Kafka 生产消费 Span / Seata 事务 Span

跨 Region Trace 不断链:
  HTTP 跨区:Header 透传 X-Trace-Id
  Kafka 跨区消息:Message Header 透传

Trace 存储:
  各区 SkyWalking OAP → 完整 Span(PII 字段脱敏后记录)
  Global Trace Index  → 仅 Span ID + 父子关系 + 时间戳
  根因分析:Global Index 定位 Region → 跳转该区 OAP UI

日志脱敏(ComplianceAwareLogger):
  查 ComplianceMetadataRegistry 自动识别敏感字段
  按 sensitivity 级别脱敏:
    5 → ***(完全遮盖)
    4 → 张***(保留首字符)
    3 → 广州***路(保留首尾各 30%)
  日志格式:
  {"receiverName":{"type":"PII","masked":"***"},
   "totalAmount":{"type":"FINANCIAL","masked":"1**.**"}}

第四层:落地细节

7. 基础设施配置

7.1 MySQL MGR 关键配置

# Global Coordination DB(SEA 机房,my.cnf)
[mysqld]
server_id                                              = 1
gtid_mode                                              = ON
enforce_gtid_consistency                               = ON
binlog_format                                          = ROW
binlog_row_image                                       = FULL
log_slave_updates                                      = ON
loose-group_replication_single_primary_mode            = ON
loose-group_replication_group_seeds = "az1:33061,az2:33061,az3:33061"

# Regional MySQL(以 EU 为例)
[mysqld]
server_id                        = 100
log_bin                          = mysql-bin
binlog_format                    = ROW           # Canal 依赖
binlog_row_image                 = FULL          # Canal 需要完整行镜像
sync_binlog                      = 1             # 每次提交刷盘
innodb_flush_log_at_trx_commit   = 1             # 强 fsync
# 从库
read_only                        = ON
super_read_only                  = ON
slave_parallel_workers           = 4
slave_parallel_type              = LOGICAL_CLOCK

7.2 Canal 配置与高可用

# canal.properties
canal.zkServers=zk1:2181,zk2:2181,zk3:2181
canal.serverMode=kafka
canal.kafka.bootstrap.servers=kafka1:9092,kafka2:9092

# canal.instance.properties(EU 实例)
canal.instance.master.address=eu-az1-mysql:3306

# 白名单:只订阅业务表和 outbox 表
canal.instance.filter.regex=ecommerce_eu\\.order_core,ecommerce_eu\\.outbox,ecommerce_eu\\.user_behavior

# 黑名单:显式排除所有 PII 表(黑名单优先于白名单)
canal.instance.filter.black.regex=ecommerce_eu\\.user_profile,ecommerce_eu\\.payment_detail

canal.mq.topic=canal.{database}.{table}
canal.mq.partitionsNum=12
canal.mq.partitionHash=orderId,userId

7.3 Kafka 生产者与消费者配置

// 生产者(幂等配置)
props.put(ENABLE_IDEMPOTENCE_CONFIG, true);  // 幂等生产者
props.put(ACKS_CONFIG, "all");               // 需要所有副本确认
props.put(RETRIES_CONFIG, Integer.MAX_VALUE);
props.put(MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 5);
props.put(COMPRESSION_TYPE_CONFIG, "lz4");   // 压缩,降低跨区传输成本

// 消费者(手动提交 offset)
props.put(ENABLE_AUTO_COMMIT_CONFIG, false);
props.put(AUTO_OFFSET_RESET_CONFIG, "earliest");
props.put(MAX_POLL_RECORDS_CONFIG, 100);
// 手动 ACK 模式
factory.getContainerProperties()
    .setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);

7.4 Kafka 重试 Topic 链

// 统一消费基类:业务只实现 process(),框架处理重试路由
public abstract class RetryAwareKafkaConsumer<T> {

    // 消费失败自动路由到下一级 retry Topic
    protected void consume(ConsumerRecord<String, String> record,
                           Class<T> eventClass, Acknowledgment ack) {
        try {
            process(JSON.parseObject(record.value(), eventClass));
            ack.acknowledge();
        } catch (NonRetryableException e) {
            retryRouter.routeToDLQ(record, e);  // 不可重试,直接 DLQ
            ack.acknowledge();
        } catch (Exception e) {
            retryRouter.routeToRetry(record, e); // 路由到 retry.*.{n+1}
            ack.acknowledge();
        }
    }

    protected abstract void process(T event) throws Exception;
}

// 重试间隔:retry.*.1=10s,retry.*.2=30s,retry.*.3=1min
// 超过 3 次 → dlq.*(P1 告警)

7.5 MirrorMaker 2 配置

# mm2.properties
clusters = sea, eu, us
sea.bootstrap.servers = sea-kafka1:9092,sea-kafka2:9092
eu.bootstrap.servers  = eu-kafka1:9092,eu-kafka2:9092

sea->eu.enabled = true
sea->eu.topics  = cart\\.crdt\\.sync,inventory\\.sync,catalog\\..*
# 严禁 PII 相关 Topic 进入 MM2 同步
sea->eu.topics.blacklist = order\\..*,payment\\..*,user\\.pii\\..*

eu->sea.enabled = true
eu->sea.topics  = cart\\.crdt\\.sync   # 购物车 CRDT 双向同步

7.6 Seata 关键配置

seata:
  client:
    rm:
      lock:
        retry-interval: 10       # 全局锁重试间隔 10ms
        retry-times: 30          # 最多 30 次(总 300ms)
    undo:
      log-delete-period: 30000   # 每 30s 清理过期 undolog
      log-save-days: 7
    tm:
      default-global-transaction-timeout: 5000

7.7 合规元数据注册中心配置

// 启动加载 + Apollo 热更新
@Component
public class ComplianceMetadataRegistry {

    @PostConstruct
    public void load() { reload(); }

    // Apollo compliance-metadata namespace 变更时自动刷新
    @ApolloConfigChangeListener("compliance-metadata")
    public void onConfigChange(ConfigChangeEvent e) { reload(); }

    public void reload() {
        // 从 Global Coordination DB 加载所有 ACTIVE 元数据
        List<FieldMetadata> all = metadataMapper.selectActive(appName);
        // 构建三级 Map:appName → tableName → fieldName → metadata
        this.cache = buildCache(all);
    }

    // 查询接口:运行时使用
    public Optional<FieldMetadata> getByTableField(
            String tableName, String fieldName) { ... }

    public Optional<FieldMetadata> getByEntityField(
            Class<?> entityClass, String javaFieldName) { ... }
}

7.8 新 Region 上线配置(Apollo)

# namespace: region.MY(马来西亚新站点)
region:
  id: MY
  name: Malaysia
  timezone: Asia/Kuala_Lumpur
  currency: MYR
  languages: [ms, en, zh]
  compliance:
    dataResidency: MY
    regulations: [PDPA_MY]
  consistency:
    defaultLevel: EVENTUAL
    paymentLevel: STRONG_LOCAL
    inventoryLevel: STRONG_GLOBAL
  topology:
    primaryCluster: aws-ap-southeast-1
    inventoryAuthorityRegion: SEA
    fallbackRegion: SG
  inventory:
    warmupWeight: 0.08
  datasource:
    writeUrl: jdbc:mysql://my-az1-mysql:3306/ecommerce_my
    readUrl:  jdbc:mysql://my-az2-mysql:3306/ecommerce_my
    redis:    my-redis-cluster:6379

7.9 本地开发环境

# docker-compose.local.yml(一键启动)
services:
  mysql-global:    # Global Coordination DB
    image: mysql:8.0
    ports: ["3306:3306"]
  mysql-regional:  # Regional MySQL
    image: mysql:8.0
    ports: ["3307:3306"]
  redis:           # Redis(单节点模拟 Cluster)
    image: redis:7-alpine
    ports: ["6379:6379"]
  kafka:           # Kafka(含 Zookeeper)
    image: confluentinc/cp-kafka:7.5.0
    ports: ["9092:9092"]
  seata:           # Seata Server
    image: seataio/seata-server:2.0.0
    ports: ["8091:8091"]
  apollo:          # Apollo 配置中心
    image: nobodyiam/apollo-quick-start:2.1.0
    ports: ["8080:8080"]
  skywalking-oap:  # SkyWalking(H2 存储)
    image: apache/skywalking-oap-server:9.7.0
    ports: ["11800:11800", "12800:12800"]
// Local Profile:屏蔽分布式复杂度
@Configuration
@Profile("local")
public class LocalDevConfig {
    @Bean @Primary
    public ConsistencyRouter localRouter() {
        return new LocalPassthroughRouter();  // 所有请求走本地 MySQL
    }
    @Bean @Primary
    public ComplianceFenceAspect localFence() {
        return new WarnOnlyComplianceFenceAspect();  // 只打 WARN,不拒绝
    }
    @Bean @Primary
    public ComplianceMetadataRegistry localRegistry() {
        // 优先读 DB,DB 无数据时从代码注解兜底
        return new AnnotationFallbackRegistry(delegate);
    }
    @Bean @Primary
    public FeatureFlagEngine localFF() {
        // 环境变量强制开关:export FF_NEW_CHECKOUT=true
        return (name, ctx) -> Boolean.parseBoolean(
            System.getenv("FF_" + name.toUpperCase()));
    }
}

8. 研发规范

8.1 注解使用规范

必须使用(PR 门禁检查):

  @ConsistencyPolicy(level = ?)
    · 所有 Service 层跨区数据写入方法
    · 禁止对支付/库存扣减使用 EVENTUAL 级别

  @ComplianceData(type = DataType.?, description = "...")
    · 实体类中所有含个人信息的字段
    · 字段名含 Name/Phone/Email/Address/IdCard/BankCard 时必须标注
    · 注意:运行时以 compliance_field_metadata 表为准,注解仅作文档提示

  @Idempotent(keyExpression = "...")
    · 所有 @GlobalTransactional 方法
    · 所有 @KafkaListener 方法

  @GlobalTransactional
    · 跨表或跨服务写操作,必须配合 @Idempotent

禁止的模式:
  × 支付/库存扣减使用 ConsistencyLevel.EVENTUAL
  × 直接操作含 PII 字段的实体类 Mapper(必须经 ComplianceFence)
  × @KafkaListener 不加 @Idempotent
  × @GlobalTransactional 方法通过 this 内部调用(AOP 失效)
  × Seata AT 事务中操作 TEXT/BLOB 大字段(undolog 膨胀)

8.2 ArchUnit 门禁规则

@AnalyzeClasses(packages = "com.company.ecommerce")
class ArchitectureEnforcementTest {

    @ArchTest  // 跨区 Service 方法必须声明一致性策略
    static final ArchRule crossRegionPolicy = methods()
        .that().areDeclaredInClassesThat()
               .areAnnotatedWith(CrossRegionService.class)
        .should().beAnnotatedWith(ConsistencyPolicy.class);

    @ArchTest  // PII 字段必须标注(注解兜底,DB 为主)
    static final ArchRule piiAnnotated = fields()
        .that().haveNameMatching(
            ".*(Name|Phone|Email|Address|IdCard|BankCard).*")
        .should().beAnnotatedWith(ComplianceData.class);

    @ArchTest  // 全局事务方法必须幂等
    static final ArchRule txIdempotent = methods()
        .that().areAnnotatedWith(GlobalTransactional.class)
        .should().beAnnotatedWith(Idempotent.class);

    @ArchTest  // Kafka 消费方法必须幂等
    static final ArchRule listenerIdempotent = methods()
        .that().areAnnotatedWith(KafkaListener.class)
        .should().beAnnotatedWith(Idempotent.class);

    @ArchTest  // Service 层禁止直接访问 PII Mapper
    static final ArchRule noDirectPii = noClasses()
        .that().resideInAPackage("..service..")
        .should().accessClassesThat()
                 .areAnnotatedWith(PiiMapper.class);
}

8.3 合规元数据与代码注解一致性校验(CI)

// CI 流程中执行:扫描注解与 DB 对比
@Test
void complianceMetadataConsistencyCheck() {
    List<FieldMetadata> fromCode =
        importer.scanFromCode("com.company.ecommerce");
    List<FieldMetadata> fromDB =
        metadataMapper.selectAll();

    List<String> issues = importer.checkConsistency(fromCode, fromDB);

    // 升级方向(DB 要求更严格,代码注解宽松)→ 阻断发布
    List<String> blockers = issues.stream()
        .filter(i -> i.contains("[升级方向不一致]"))
        .collect(Collectors.toList());
    assertTrue(blockers.isEmpty(),
        "合规元数据升级方向不一致,必须先更新代码注解:\n"
        + String.join("\n", blockers));

    // 降级方向(注解比 DB 严格)→ 仅警告
    issues.stream()
        .filter(i -> i.contains("[降级方向]"))
        .forEach(w -> log.warn("[CI] 合规元数据警告:{}", w));
}

8.4 Schema 变更规范

阶段一:扩展(只加列,允许 NULL 或有默认值)
  各 Region MySQL 逐个执行(EU → SEA → US),每区间隔 30 分钟观察

阶段二:代码发布(新代码读写新列)
  灰度:1% → 10% → 50% → 100%

阶段三:清理(至少 2 个迭代后)
  DROP COLUMN(需单独排期)

禁止:RENAME COLUMN / DROP 正在使用的列 / 添加 NOT NULL 无默认值列

8.5 超时与重试配置

timeout:
  http.connect:      1000ms
  http.read:         3000ms
  http.read-payment: 10000ms  # 等银行响应
  db.query:          5000ms
  db.slow-threshold: 100ms    # 超过记录慢查询
  redis.command:     500ms
  seata.global:      5000ms

retry:
  http.max-attempts:       3
  http.backoff-delay:      100ms
  http.backoff-multiplier: 2.0
  http.jitter:             true   # 防同步重试风暴
  seata-lock.max-attempts: 3

9. 监控与运维

9.1 监控分层模型

L4 业务监控:订单转化率 / 库存超卖率 / 支付成功率
L3 应用监控:接口 P99 / 错误率 / Seata 回滚率 / Outbox 积压
L2 中间件:  Kafka 消费延迟 / Canal 复制延迟 / Redis 命中率
L1 基础设施:MySQL 主从延迟 / 慢查询 / Redis 内存 / JVM GC

告警响应时间目标:
  L4/L3 → P1,5 分钟内响应
  L2    → P2,30 分钟内响应
  L1    → P2/P3,视严重程度

9.2 关键告警规则

# P1 告警(立即响应)
- 接口错误率 > 1% 持续 1 分钟
- 库存 available < 0(超卖)
- Outbox PENDING 积压 > 1000 条持续 5 分钟
- 死信队列(dlq.*)有新消息
- 合规违规事件 compliance_violation_total 增加

# P2 告警(30 分钟内响应)
- P99 延迟 > 500ms 持续 3 分钟
- MySQL 主从延迟 > 10s
- Canal 复制延迟 > 30s
- Kafka 消费积压 > 5000 条持续 5 分钟
- Seata 事务回滚率 > 5% 持续 5 分钟
- Redis 内存使用率 > 80%
- 合规元数据变更未在 24h 内同步到所有服务

# P3 告警(当日处理)
- Canal 主备切换事件
- Seata undolog 表行数 > 100 万
- 合规字段注解与 DB 不一致

9.3 Grafana Dashboard 分层

Dashboard 1:全局概览(5 分钟刷新)
  各 Region 实时 QPS / P99 延迟 / 错误率
  库存健康状态 / Kafka 积压 / Outbox 积压 / 活跃告警

Dashboard 2:Region 下钻(1 分钟刷新,可选 Region)
  接口延迟热图 / Seata 事务成功率
  MySQL 慢查询 / 主从延迟
  Redis 命中率 / 内存
  Kafka Topic 消费延迟 / Canal 复制延迟

Dashboard 3:库存专项
  SKU 库存水位(Top 20)/ 各区 Redis 预占 vs MySQL 对比
  大促预热状态 / 超卖记录 / 动态调拨记录

Dashboard 4:合规审计
  跨区 PII 访问次数(按 fromRegion/toRegion/目的)
  合规违规事件(应为 0)/ 合规元数据变更记录

9.4 典型故障排查手册

故障一:下单失败

Step 1:获取 TraceId
  ELK 查询:orderId=xxx AND level=ERROR → 获取 traceId

Step 2:SkyWalking 查完整链路
  搜索 traceId → 定位红色失败 Span

Step 3:按失败节点分类
  Gateway 失败:
    401 → JWT 问题 / 429 → Sentinel 限流 / 503 → 下游不可用

  Redis 预占失败(InsufficientStockException):
    redis-cli GET "inv:pre:{skuId}"
    0 → 真实库存不足(正常)
    负数 → 预占逻辑 BUG(立即 P1 告警)

  Seata 事务超时:
    Seata 控制台 http://seata-server:7091
    找到 xid → 查 Branch 状态
    TIMEOUT → 优化慢 SQL 或调大 timeoutMills
    LOCK_CONFLICT → 该 SKU 应走 Redis 预占路径

  Outbox PENDING 积压:
    SELECT COUNT(*) FROM outbox WHERE status=0 AND create_time < NOW()-300s
    积压 → Canal 故障,查 canal.log
    执行运维工具:POST /internal/ops/outbox/relay(手动补发)

故障二:Kafka 消费积压

Step 1:定位积压 Topic
  kafka-consumer-groups.sh --describe --group {group}

Step 2:分析原因
  消费者 Pod 崩溃 → kubectl logs {pod} --previous
  单条消息处理慢 → SkyWalking 查 Consumer Span 耗时
  消息量突增     → 对比历史 QPS,临时扩容消费者实例
  下游依赖不可用 → 查消费者日志异常类型

Step 3:DLQ 处理
  POST /internal/ops/dlq/replay?topic=order.created&limit=100
  观察 dlq.order.created 消息是否成功重放

故障三:Canal 复制延迟

Step 1:快速诊断
  ps aux | grep canal
  tail -200 /home/canal/logs/canal/canal.log | grep -E "ERROR|WARN|delay"

Step 2:判断延迟来源
  Canal → Kafka 慢:检查 Kafka 集群负载
  MySQL binlog 追不上:增大 Canal 线程数
  ZK 主备切换:等待 1-2 分钟自动收敛

Step 3:Outbox 积压影响评估
  SELECT COUNT(*) FROM outbox WHERE status=0
  如 > 1000 → 执行手动补发:POST /internal/ops/outbox/relay

故障四:合规元数据不一致

症状:服务启动日志 "[Compliance] Metadata loaded: 0 fields"
      或合规违规告警

Step 1:检查 Global Coordination DB 连通性
  SHOW STATUS WHERE Variable_name='Threads_connected';

Step 2:检查元数据表数据
  SELECT COUNT(*) FROM compliance_field_metadata WHERE status='ACTIVE';
  0 → 元数据未初始化,执行导入工具生成 SQL

Step 3:强制刷新各服务缓存
  POST /internal/ops/compliance/reload
  GET  /internal/compliance/fields → 确认数据已加载

Step 4:验证 MySQL 字段注释同步
  DESCRIBE order_core;
  确认 receiver_name 的 Comment 包含 [PII-5]

9.5 运维工具 API

@RestController
@RequestMapping("/internal/ops")
@PreAuthorize("hasRole('OPS')")
public class OpsController {

    // 手动重发 DLQ 消息
    @PostMapping("/dlq/replay")
    public ReplayResult replayDLQ(@RequestParam String topic,
                                   @RequestParam(defaultValue="100") int limit)

    // Outbox 消息强制补发(Canal 故障恢复后使用)
    @PostMapping("/outbox/relay")
    public RelayResult forceRelayOutbox(@RequestParam(defaultValue="500") int limit)

    // 库存手动对账
    @PostMapping("/inventory/reconcile")
    public ReconcileResult reconcileInventory(@RequestParam String skuId)

    // 合规元数据强制刷新
    @PostMapping("/compliance/reload")
    public void reloadComplianceMetadata()

    // 查看当前合规规则(验证变更是否生效)
    @GetMapping("/compliance/fields")
    public List<FieldMetadataVO> listFields(
        @RequestParam(required=false) String tableName,
        @RequestParam(required=false) DataType dataType)

    // 健康检查(K8s probe)
    @GetMapping("/health")
    public HealthResult health()
        // 包含:mysql/redis/kafka/seata 健康状态
        //       outboxBacklog / region / complianceMetadataLoaded
}

9.6 库存对账日报 SQL

-- XXL-Job 每日 02:00 执行
SELECT
    g.sku_id,
    g.available                                  AS mysql_available,
    COALESCE(p.total_pre_occupied, 0)            AS redis_pre_occupied,
    g.available + COALESCE(p.total_pre_occupied, 0) AS total_usable,
    CASE
        WHEN g.available < 0 THEN '【超卖!立即处理】'
        WHEN ABS(g.available + COALESCE(p.total_pre_occupied,0)
                 - g.total_stock) > g.total_stock * 0.05
             THEN '【偏差超5%,需核查】'
        ELSE '正常'
    END AS health_status
FROM inventory_global g
LEFT JOIN (
    SELECT sku_id, SUM(qty) AS total_pre_occupied
    FROM inventory_pre_occupy WHERE status = 1
    GROUP BY sku_id
) p ON g.sku_id = p.sku_id
WHERE g.available < 0
   OR ABS(g.available + COALESCE(p.total_pre_occupied,0)
          - g.total_stock) > g.total_stock * 0.05;

第五层:规划与决策

10. 落地路线图

Phase 1(M0-M3)框架搭建,SEA 单 Region 跑通
  研发框架 Starter 组件(优先级排序):
    P0:starter-region-context(RegionContext + Filter)
    P0:starter-idempotent(Redis + DB 双写降级)
    P0:starter-consistency(ConsistencyPolicy AOP)
    P1:starter-compliance-metadata(Registry + Fence AOP)
    P1:starter-vector-clock(因果一致性)
    P1:starter-feature-flag(组合条件开关)
  基础设施:
    · Canal + Kafka 本地环境(docker-compose)
    · ArchUnit 门禁规则 + CI 合规一致性校验
    · Apollo 多 Region 命名空间规范
    · SkyWalking 接入(SEA 先行)
    · 合规元数据初始化导入工具(扫描注解 → 生成 SQL)
  验收:SEA 单 Region 下单链路压测(10x 流量),合规元数据加载验证

Phase 2(M3-M6)多 Region 存储上线
  存储层:
    · EU / US Regional MySQL 主从部署
    · Global Coordination DB(MySQL MGR)含 compliance_field_metadata 表
    · Canal + Kafka 复制链路(EU/US 非 PII 字段 → 全局)
    · Redis Cluster 各 Region 独立部署
    · MirrorMaker 2 跨 Region Topic 同步
  合规体系:
    · ComplianceFence 上线(EU PII 不出境强制验证)
    · 合规元数据管理 API + 审批流上线
    · MySQL 字段 COMMENT 自动同步
    · 日志脱敏 + 合规类型标注
    · Canal PII 黑名单配置审计纳入上线 checklist
  验收:跨区 PII 代理读 E2E 测试通过,合规审计视图可用

Phase 3(M6-M9)大促能力建设
  库存能力:
    · 三级库存模型(预热 + 动态调拨 + 对账)
    · 库存权威节点故障切换脚本验证(每季度演练)
  业务能力:
    · CRDT OR-Set 购物车(含 Epoch GC)
    · 支付链路 Seata TCC 改造
    · 新站点 MY 配置驱动上线(3 天内完成验证)
    · Outbox + Canal CDC 全量替换 RocketMQ 事务消息
  验收:大促全流程压测(50x 流量),合规变更热更新验证

Phase 4(M9-M12)运维成熟
  平台能力:
    · Istio 服务网格接入(替换 Spring Cloud 路由层)
    · 全局 SLO Dashboard(Grafana 四层仪表盘)
    · 混沌工程演练(AZ 故障 / 网络分区 / Region 隔离)
    · 库存对账全自动化(日报 + P1 告警)
    · 合规元数据审计报告自动生成(满足 GDPR 第 30 条记录要求)
  验收:99.99% 可用性 SLA 连续 30 天达标

10.1 关键风险对策

风险 影响 对策
Canal 团队经验不足 复制链路故障无人处理 M1 安排 1 人专项,M2 前完成 HA 演练
Seata undolog 膨胀 存储耗尽 定时清理配置 + 热点 SKU 绕过 Seata
SEA 整区故障 库存写入中断 切换脚本每季度演练,RTO < 15min
CRDT addSet 无限增长 Redis 内存溢出 Epoch GC 每日执行,监控 cart key 大小
合规元数据审批周期长 变更被卡住 预设审批 SLA(24h),超时自动升级提醒
注解与 DB 长期不一致 误导研发 CI 阻断升级方向不一致,每迭代末统一同步

11. 架构决策记录

ADR-001:放弃 TiDB,使用 MySQL 生态

日期:2026-05-15 | 状态:已确认

背景:原方案使用 TiDB,但团队无生产经验,三套组件(PD/TiKV/TiDB Server)出故障无人能接。

决策:MySQL 8 + MGR + Canal + Redis,在应用层用"库存单写权威节点 + Redis 预占 + 消息最终一致"替代 TiDB 跨区原生强一致。

权衡:放弃自动分片和原生跨区强一致;接受极小概率超卖(≤ 0.01%)和 SEA 整区故障 RTO 15 分钟;收益是团队完全可控,出故障知道在哪查。


ADR-002:统一消息平台为 Kafka,移除 RocketMQ

日期:2026-05-15 | 状态:已确认

背景:Canal 已用 Kafka,维护两套 MQ 成本高;RocketMQ 事务消息、延迟消息、自动重试等特性均可在 Kafka 生态中替代。

决策:全面使用 Kafka,RocketMQ 特性替代方案如下:事务消息 → Outbox + Canal CDC;延迟消息 → XXL-Job 扫描(订单超时)+ 分级延迟 Topic;自动重试 → 重试 Topic 链;死信队列 → dlq.* Topic。

权衡:Outbox Pattern 增加了一张表和 Canal 监听逻辑;但统一运维平台,减少了一套 MQ 的运维负担,且 MirrorMaker 2 使跨区消息同步更简洁。


ADR-003:库存权威节点选 SEA,不做跨区 Quorum 写

日期:2026-05-15 | 状态:已确认

背景:跨区 Quorum 写延迟 150-300ms,用户体验不可接受;SEA 是主流量 Region(50% 订单)。

决策:库存单写权威在 SEA-MySQL,其他 Region 通过 Redis 预占 + 异步消息落库。SEA 整区故障时手动切换(RTO < 15 分钟)。


ADR-004:Seata 分级使用

日期:2026-05-15 | 状态:已确认

决策:支付链路 TCC;订单/库存(非热点)AT;热点库存 Redis 预占绕过 Seata;其他服务 Outbox + Kafka 最终一致。


ADR-005:合规元数据外置到数据库,注解降级为文档提示

日期:2026-05-15 | 状态:已确认

背景:@ComplianceData 注解是编译期元数据,无法动态调整合规类型;DBA 和运维人员无法从数据库直接判断字段合规类型;合规规则变更需走研发排期,周期 2 周+。

决策

  1. 合规元数据存储在 compliance_field_metadata 表(Global Coordination DB),运行时由 ComplianceMetadataRegistry 加载
  2. @ComplianceData 注解保留但降级为文档提示,运行时以 DB 为准
  3. DB 与注解不一致时,DB 绝对优先;升级方向不一致阻断 CI,降级方向仅警告
  4. MySQL 字段 COMMENT 自动同步合规类型([PII-5] 收货人姓名)
  5. 合规类型变更由合规团队通过管理 API 提交,两人审批后自动生效,无需研发介入

权衡:增加了 compliance_field_metadata 表的维护成本;换取合规规则变更与代码发布解耦,变更延迟从 2 周降到 24 小时内。


ADR-006:Canal PII 表使用显式黑名单

日期:2026-05-15 | 状态:已确认

背景:Canal 默认订阅所有表,遗漏 PII 过滤导致合规违规;白名单要求每张新表手动添加,容易遗漏。

决策:Canal 采用"白名单(只订阅业务表)+ 黑名单(显式排除 PII 表)"双重保障,黑名单优先。新增 PII 表时必须同步更新黑名单,纳入上线 checklist 强制项。


ADR-007:CRDT OR-Set 购物车 + Epoch GC

日期:2026-05-15 | 状态:已确认

背景:购物车需要支持多 Region 并发操作不冲突;传统锁在多活架构下不可行;OR-Set 的 tag 集合在长期使用后无限累积。

决策:使用 CRDT OR-Set 实现购物车,add 语义优先;引入 Epoch GC(每 24h 一次)压缩历史 tag,防 Redis 内存无限增长。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容