事件体系结构
事件体系结构(EDA)
1)什么是事件和为什么EDA
事件是在域中已经发生的不变事实("PlayerVerified","PaymentCaptured")。EDA围绕这些事实的发布和对它们的反应来构建集成:- 服务连通性薄弱,
- 独立地扩大消费者的规模,
- 放置/重构投影,
- 透明审计。
EDA不会取消同步API-它通过将跨服务依赖项带到异步层来补充它们。
2)事件类型
域:有意义的业务事实(OrderPlaced, BonusGranted)。
集成:外部系统的"快照"/更改(UserUpdated, WalletBalanceChanged)。
技术:生命周期和遥测(Heartbeat,PipelineFailed)。
命令(不是事件,但附近):"做X"指令(CapturePayment)。
建议:域事件-主要事件;通过针对特定消费者的投影来形成集成。
3)事件和计划合同
Схема: Avro/Protobuf/JSON Schema + Schema Registry;兼容性策略:"BACKWARD"用于消费者进化,"FULL"用于关键主题。
CloudEvents (id, source, type, time, subject, datacontenttype)-统一标题。
强制性元数据:"event_id"(ULID/UUID),"occurred_at","producer","schema_version","correlation_id"/"causation_id","idempotency_key"。
转化:仅附加字段,禁止重命名/语义断裂;新类型-新主题/类型。
json
{
"type":"record","name":"PaymentCaptured","namespace":"events.v1",
"fields":[
{"name":"event_id","type":"string"},
{"name":"occurred_at","type":{"type":"long","logicalType":"timestamp-micros"}},
{"name":"payment_id","type":"string"},
{"name":"amount","type":{"type":"bytes","logicalType":"decimal","precision":18,"scale":2}},
{"name":"currency","type":"string"},
{"name":"player_id","type":"string"}
]
}
4)交付,顺序和一致性
在默认情况下,需要处理程序的幂等→。
顺序:在聚会(Kafka)或队列(RabbitMQ)中得到保证,但在回避时可能会受到干扰;事件密钥必须反映顺序域颗粒(例如"player_id")。
一致性:金钱/贷款-仅通过杂志/传奇/报销;避免LWW。
阅读模型:投影和缓存可以是事件-显示"正在进行更新"……并使用RNOT策略进行严格的路径。
5) Outbox/Inbox и CDC
Outbox:该服务将事实写入其DB,并在单个事务中写入outbox表→用户发布到总线。
收件箱:消费者保留"event_id"与处理结果进行重复数据消除。
CDC(更改数据捕获):从DB (binlog/WAL)到总线的更改流,用于在不更改应用程序的情况下构建集成。
Idempotency:通过"idempotency_key"/"event_id"处理,在固定之前不要改变外部世界。
6) CQRS и Event Sourcing
CQRS:共享写模型和读取投影;投影是由事件构成的,可能会滞后。
Event Sourcing:聚合状态=其事件的卷积。优点:全面审计/重播;缺点:迁移/计划/狙击的复杂性。
实践:ES-不在任何地方,而是历史和补偿重要的地方;CQRS几乎总是在EDA中。
7)传奇: 编排和编舞
编排:协调员发送命令并等待事件响应;方便于复杂的流程(KYC→Deposit→Bonus)。
编舞:服务部门对彼此的事件做出反应;更容易但更难追踪。
始终定义补偿和截止步骤。
8)拓扑设计(Kafka/RabbitMQ)
Kafka
Topik per域事件: 'payments。captured.v1`, `players.verified.v1`.
派对键:"player_id"/"wallet_id"-顺序重要的位置。
`replication.factor=3`, `min.insync.replicas=2',prodewser' acks=all'。
Retention:按时间(例如7-90天)和/或匹配(按键的最后状态)。
背靠背的复古和DLQ拓扑。
RabbitMQ
Exchanges: `topic`/`direct`, routing key `payments.captured.v1`.
对于广泛的粉丝,"topic"+排队数次;对于RPC/命令-单独的队列。
HA的Quorum Queues;用于中继的TTL+dead-letter交换。
9)可观察性和SLO EDA
SLI/SLO:
端到端延误(occurred_at →处理):p50/p95/p99。
Lag/age:消费者滞后(Kafka消费者lag,Rabbit backlog age)。
通过出版/处理。
DLQ-rate和重复比例。
业务运营成功(例如,"存款确认≤ 5 c")。
- 通过"trace_id"/"correlation_id"(OTel)关联事件。
- 来自→轨迹度量的实例(exemplars)。
- 带有burn-rate alertes的dashbords "Producer→Broker→Consumer"。
10)Replay,重建和backfill
重建投影/修复错误:赛车进入新的投影/内空,然后切换读数。
重组:法律/业务要求(GDPR/PCI);敏感字段-加密和/或令牌化。
Backfill:一次性主题/队列,明确的RPS限制,以免扼杀prod。
11)安全和合规性
TLS transit,mTLS面向内部客户。
授权:per-topic/per-Exchange ACL;multitenancy通过namespace/vhost。
PII:将事件中的字段最小化;envelope元数据是分开的,有效载荷在需要时加密。
审核事件访问,禁止"万能"密钥。
还原策略和删除权限(GDPR):要么存储数据引用,要么在投影中存储tombstone事件和删除。
12)在EDA进行测试
合同测试:消费者验证他们对计划的期望(消费者驾驶)。
Replay测试:通过新的处理程序/模式版本运行历史样本。
混沌情景:经纪人的延迟/损失,节点下降,消费者积压→ SLO仍然存在。
CI中的Smoke:临时主题上的简短端到端管道。
13)迁移"CRUD集成→ EDA"
1.识别域事实。
2.将outbox引入源服务。
3.发布最小域事件并连接1-2投影。
4.逐步禁用点同步集成,代之以订阅。
5.输入Schema Registry和兼容性策略。
6.将事件扩展到仅附加字段;切片-仅通过新类型。
14)反模式
事件="DTO API"(过于胖,取决于内部模型)-破坏消费者。
缺少Schema Registry和兼容性-"脆弱"集成。
从代码中发布并写入DB不是atomaran(不是outbox)-丢失事件。
"无处不在"-无利可图的高价;更好-least-once+等效性。
一个"通用"党派钥匙→热门党派。
直接回放到投影中-打破在线SLO。
15)实施清单(0-45天)
0-10天
定义域事件及其密钥(顺序颗粒)。
部署Schema Registry并批准兼容性策略。
将outbox/inbox添加到1-2服务中;最小的CloudEvents-envelope。
11-25天
输入retry/DLQ、backoff、处理程序等。
Dashbords:lag/age/end-to-end;burn-rate alerta。
事件文档(目录),所有者以及通过模式评论的过程。
26-45天
首次投影/重建;runbook中继和backfill。
安全策略(TLS,ACL,PII),续集,GDPR程序。
定期为经纪人和消费者提供混沌和游戏日。
16)成熟度量
100%的域事件由电路描述并注册。
Outbox/inbox涵盖了所有Tier-0/1制作人/用户。
SLO:目标范围内的p95端到端延迟和消费者lag ≥ 99%。
在没有市区的情况下,Replay/Backfill是可行的。有一个经过验证的运行手册'和。
转化:新领域-无碎片;老消费者不会倒下。
安全性:TLS+mTLS、ACL每主题、访问日志、PII/重建策略。
17)迷你嗅探
Kafka Producer(可靠的出版物,想法):properties acks=all enable.idempotence=true max.in.flight.requests.per.connection=1 compression.type=zstd linger.ms=5
消费者处理程序(等效性,伪代码):
python if inbox.contains(event_id): return # дедуп process(event) # побочные эффекты детерминированы inbox.commit(event_id) # atomically with side-effect commit_offset()
RabbitMQ Retry通过DLX(想法):
- `queue: tasks` → on nack → DLX `tasks.retry.1m'(TTL=60 s)→返回"tasks";接下来是"5 m/15 m"。
18)结论
EDA将集成转变为具有明确合同和管理一致性的业务事实流。构建基础:电路+注册表,outbox/inbox,阶键,等效处理程序,SLO和可观察性,安全重构和继电器。然后,事件将成为你扩大规模,分析和新奇观的"真理之源"-没有脆弱的联系和夜间迁移。