相似性和钥匙
什么是相容性
相似性是操作的属性,其中具有相同标识符的重复不会更改最终效果。在分布式系统中,这是使结果等效于"正好一个处理"的主要方法,尽管有转发,重复消息和计时器。
关键思想:每个可能重复的操作都必须用一个键标记,系统通过该键识别"已经完成"并应用最多一次的结果。
这在哪里很重要
付款和资产负债表:"operation_id"注销/贷记。
预订/配额/限额:同一时段/资源。
Webhooks/通知:重新交付不应复制效果。
导入/迁移:重新运行文件/数据包。
流处理:经纪人/CDC的配音。
密钥的种类及其范围
1.操作键是特定业务操作尝试的ID
示例:"idempotency_key"(HTTP),"operation_id"(RPC)。
区域:服务/单位;存储在重复数据消除表中。
2.事件密钥-唯一事件/消息ID
示例:"event_id"(UUID),"(producer_id,序列)"。
领域:消费者/消费者群体;保护投影。
3.商业钥匙是学科领域的自然钥匙
例如:"payment_id","invoice_number","(user_id,日)"。
区域:聚合体;适用于唯一性/版本检查。
TTL和保留策略
TTL密钥≥可能的重播窗口:log retency+网络/处理延迟。
对于关键域(付款),TTL-天/周;遥测-时钟。
用jobami清除祖先表;用于审计-归档。
密钥存储(重复数据消除)
事务性DB(推荐):可靠的upsert/unique索引,协同效应事务。
KV/Redis:快速,方便短的TTL,但没有与OLTP的联合交易-谨慎。
State store流处理器:经纪人中的本地+cheinjlog;在Flink/KStreams很好。
- idempotency_keys
`consumer_id` (или `service`), `op_id` (PK на пару), `applied_at`, `ttl_expires_at`, `result_hash`/`response_status` (опц.).
索引:"(consumer_id,op_id)"是唯一的。
基本实现技术
1)交易"效应+进展"
在单个事务中写入结果并提交读/位置进度。
pseudo begin tx if not exists(select 1 from idempotency_keys where consumer=:c and op_id=:id) then
-- apply effect atomically (upsert/merge/increment)
apply_effect(...)
insert into idempotency_keys(consumer, op_id, applied_at)
values(:c,:id, now)
end if
-- record reading progress (offset/position)
upsert offsets set pos=:pos where consumer=:c commit
2) Optimistic Concurrency(单元版本)
在比赛中防止双重影响:sql update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
-- if 0 rows are updated → retry/conflict
3)异位夹具(upsert/merge)
一次累积操作:sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
协议中的幂等性
HTTP/REST
标题"Idempotency-Key:
服务器存储密钥条目并重新返回相同的响应(或不变性冲突中的代码"409"/"422")。
对于"不安全"的POST,"Idempotency-Key"+可持续的taymout/retray政策是必需的。
gRPC/RPC
元数据"idempotency_key","request_id"+deadline。
服务器实现-如REST:事务中的重复数据消除表。
经纪人/流媒体(Kafka/NATS/Pulsar)
制片人:稳定的"event_id"/偶数制片人(支持的地方)。
消费者:"(consumer_id,event_id)"和/或聚合的业务版本。
单独的DLQ,用于非偶数/损坏的消息。
Webhuki和外部合作伙伴
在合同中要求"Idempotency-Key"/"event_id";重新交付必须安全。
存储"notification_id"和发送状态;在转发-不要重复。
密钥设计
确定性:retrai必须发送相同的密钥(在客户/编排器上提前生成)。
可见性区域:将"op_id"形成"服务:aggregate: id: purpose"。
冲突:使用来自业务参数的UUIDv7/ULID或哈希(必要时与盐)。
层次结构:前面的通用"operation_id" →翻译成所有子运算(偶数链)。
UX和产品方面
对密钥的重新查询必须返回相同的结果(包括主体/状态)或显式"已经完成"。
向用户显示"操作/完成"状态,而不是"为好运"重新尝试。
对于冗长的操作-按键("GET/operations/{op_id}")。
可观察性
编写"op_id","event_id","trace_id",结果:"APPLIED"/"ALREADY_APPLIED"。
度量标准:重复百分比、去势表大小、事务时间、版本冲突、DLQ投注。
Trace:关键必须通过命令→事件→投影→外部呼叫。
安全性和合规性
不要将PII存储在密钥中;密钥是ID,不是付费。
在冗长的TTL中加密重复数据消除记录中的敏感字段。
保留政策:TTL和档案;被遗忘的权利-通过加密擦除响应/元数据(如果其中包含PII)。
测试
1.重复:运行一个消息/请求2-5次-效果恰好是一个。
2.在步骤之间下降:记录效果之前/之后,正交固定之前/之后。
3.消费者重启/重组:无双重用途。
4.竞争:具有一个"op_id" →一个效果的并行查询,第二个效果是"ALREADY_APPLIED/409"。
5.长寿命密钥:TTL过期和恢复后重复检查。
反模式
每个回程的随机新键:系统无法识别重复。
两个独立的公用程序:首先是效果,然后是失真-它们之间的下降复制了效果。
仅信任经纪商:在合成器/设备中没有重复数据消除。
缺少聚合版本:重复事件第二次更改状态。
胖键:键包括业务领域/PII →泄漏和复杂的索引。
没有可重复的答案:客户无法安全地回避。
示例
客户端:"POST/payments"+"Idempotency-Key: k-789"。
服务器:事务-创建"payment"并写入"idempotency_keys"。
重播:返回相同的"201"/身体;不变性冲突为"409"。
奖金(sink)
sql insert into credits(user_id, op_id, amount, created_at)
values(:u,:op,:amt, now)
on conflict (user_id, op_id) do nothing;
事件投影
消费者存储该单元的"seen(event_id)"和"版本";重复-忽略/等效的upsert。
读取进度与投影更新记录在相同的事务中。
生产清单
- 对于所有不安全的操作,都定义了幂等键及其可见性区域。
- 有带有TTL和唯一索引的重复数据消除表。
- 读取效果和进度以原子方式计算。
- 写作模型包括乐观的竞争(版本/序列)。
- API合同记录了"Idempotency-Key"/"operation_id"和重播行为。
- 度量指标和日志包含"op_id"/"event_id"/"trace_id"。
- 复制、摔倒和比赛测试-在CI。
- 符合TTL/存档政策和 PII安全性。
FAQ
"Idempotency-Key"与"Request-Id"有什么不同?
'Request-Id'-跟踪;它可以在后台进行更改。"Idempotency-Key"是操作的语义ID,在重复时必须相同。
没有DB就能达到等效性吗?
对于短窗口,是的(Redis/进程内关节),但是如果没有联合交易,双倍的风险就会增加。在关键域中-在单个DB交易中更好。
如何与外部合作伙伴合作?
商定密钥和可重复的响应。如果合作伙伴不支持-将呼叫包裹到其幂等层中并存储"已经应用"。
如何选择TTL?
总结最大延迟:log+worst-case 网络/rebalance+缓冲区。添加库存(× 2)。
底线
相似性是钥匙,交易和版本的学科。稳定的操作ID+原子固定的效果和阅读进展+等效的sinks/投影可产生"完全相同的效果",而无需传输层魔力。使密钥确定性,TTL逼真,测试恶意。然后,撤退和重复将成为例行公事而不是事件。