来文程序的保障
1)什么是"秩序",为什么需要它
消息顺序是单个实体(订单,用户,钱包)或整个线程的事件"必须先处理"的关系。它对于不变量很重要:"B之前的状态A","注销前的平衡","n+1之前的版本n"。
在分布式系统中,很少需要全球总道路顺序;通常,"按键"的本地顺序就足够了。
2)秩序保证的类型
1.Per-partition (log部分中的本地顺序)-Kafka:党内顺序保持不变,党内顺序保持不变。
2.按键(ordering key/message group)-将所有单键消息路由到一个处理的"线程"(Kafka key, SQS FIFO MessageGroupId, Pub/Sub ordering key)。
3.全球总命令-整个系统看到一个顺序(分布式日志/音序器)。价格昂贵,降低了可用性和通量。
4.Causal order(因果关系)是"如果B观察到A效应,则事件B之后"。无需全局测序器即可通过元数据(版本,Lamport时间/矢量时钟)实现。
5.Best-effort order-经纪人试图保持秩序,但是如果失败,可能会进行重新排列(通常在NATS Core,RabbitMQ中使用多个对齐器)。
3)订单中断的地方
一队列并行消费者(RabbitMQ:每个队列多个消费者→ interleaving)。
Retrai/重新交付(at least-once),taymauts "ack",重新排队。
Rebalans/feilover(Kafka:政党/领导人的举动)。
DLQ/重新处理-"有毒"消息离开DLQ,接下来是逻辑中断→。
多区域和复制-不同的延迟→同步。
4)"按键顺序"设计"
密钥形成"排序单元"。建议:- 使用自然键:'order_id'、'wallet_id'、'aggregate_id'。
- 留意"热键"-单键可以"锁定"流(头对头锁定)。如果需要,分解键:'order_id#shard (0.. k-1)",在合成器上进行确定性顺序重建。
- 在Kafka中-一个键→一个部分,订单将保持在键内。
java producer.send(new ProducerRecord<>("orders", orderId, eventBytes));
(键='orderId'保证了本地顺序。)
5)"订单与容量"
强大的保修通常与通配符和可访问性发生冲突:- 每个队列一个匹配器保留顺序,但减少并发。
- At-least-once+并发可以提高性能,但需要等速和/或阶还原。
- Global Order为音序器添加了嘻哈→ ↑latentnost和故障风险。
折衷方案:按键顺序,并行=政党/团体数,+等效辛基。
6)控制特定经纪人的秩序
Kafka
党内的秩序。
遵守'max。in.flight.requests.per.connection ≤ 5` с `enable.idempotence=true',这样制片人的复制品就不会改变顺序。
Consumer Group:一方→一方。可以重复交付→将序列/版本保持在业务层中。
事务(read-process-write)保持一致性"读取/写入/错配",但不创建全局顺序。
生产最低限度(生产者。properties):
properties enable.idempotence=true acks=all retries=2147483647 max.in.flight.requests.per.connection=5
RabbitMQ (AMQP)
单个concumer的顺序保证为单队列。多个消息匹配器可能会出现"前进"。
对于顺序:完成后,单个consumer或prefetch=1+ack。对于并行-按键划分队列(sharding exchanges/consistent-hash exchange)。
NATS / JetStream
NATS Core-最好的effort,低潜伏期,订单可能会中断.
JetStream:在流/序列中排序;如果重新排列,则可以重新排列concumer,→使用序列和恢复缓冲区。
SQS FIFO
Exactly once processing(有效,通过重复数据消除)和MessageGroupId中的顺序。并发是线头组内的组数。
Google Pub/Sub
Ordering键在键内给出一个顺序;如果出现错误,则在恢复之前会阻止发布-请注意后退。
7)保存和恢复秩序的模式
7.1个序列/转化
每个事件都带有"seq"/"版本"。Consumer:- 仅在"seq=last_seq+1"时接受事件;
- 否则-在失踪人员到达之前("last_seq+1")放置在等待缓冲区。
pseudo if seq == last+1: apply(); last++
else if seq > last+1: buffer[seq] = ev else: skip // дубль/повтор
7.2缓冲区和窗口(流处理)
Time-window+watermark:在窗口内接受订单外,在watermark上"关闭"窗口并排序。
Allowed lateness:迟到的频道(recompute/ignore)。
7.3按键按键sticky-routing
哈希路由"hash (key)% shards"将所有密钥事件发送到单个用户。
在Kubernetes-支持队列/sherda级别的会话(sticky),而不支持L4 HTTP平衡器。
7.4演员模型/"一键流"
对于关键单元(钱包):演员依次处理,其余并发由演员数处理。
7.5相等性+重新排序
即使恢复顺序,重复也是可能的。将UPSERT按键+版本和Inbox结合起来(请参阅"Exactly-once vs At-least-once")。
8)处理"有毒"信息(poison pills)
保持秩序面临的挑战是: "如果一个消息不处理,如何生活?"
严格顺序:锁定密钥流(SQS FIFO:整个组)。解决方案是按键DLQ:我们只将问题密钥/组转换为单独的队列/手动解析。
灵活顺序:允许通行/赔偿;我们计算并继续(不适用于金融/关键总量)。
Retrais Policy:有限的"max-deliver"+backoff+avidemential效果。
9)多区域和全球系统
Cluster-linking/复制 (Kafka)不能保证跨区域的全球秩序。优先考虑局部按键顺序和等效弯曲。
对于truly-global订单,请使用音序器(中央日志),但这会影响可用性(CAP:网络中断时减去A)。
另一种选择:某些域(计数器、集合)的causal order+CRDT-不需要严格的顺序。
10)顺序可观察性
Метрики: `out_of_order_total`, `reordered_in_window_total`, `late_events_total`, `buffer_size_current`, `blocked_keys_total`, `fifo_group_backlog`.
11)反模式
一个队列+许多不按键行驶的求和器-顺序立即断裂。
在没有偶然性的情况下,通过笔式公共场所的回溯是双打+顺序。
"以防万一"的全球秩序是潜伏期和成本的爆炸,没有真正的好处。
SQS FIFO每个组都是完整的头线。使用MessageGroupId按键。
忽略"热键"-一个"钱包"会抑制一切;尽可能将密钥分成副密钥。
在单个队列/组中混合关键流和布尔克流-相互影响和顺序损失。
12)实施支票
- 确定了保修级别:per-key/per-partition/causal/global?
- 设计了针对"热键"的排序密钥和策略。
- 已配置路由器:partization/MessageGroupId/ordering密钥。
- Concumers是通过密钥隔离的(粘性路由,shard-workers).
- 包括指针上的幂和/或Inbox/UPSERT。
- 已实现sequence/version和reordering缓冲区(如果需要)。
- DLQ by key和retrai with backoff.
- 顺序和异序度量:从顺序开始,blocked_keys,late_events。
- 游戏日:重建,节点丢失,"有毒"消息,网络延迟。
- 文档:顺序不变性,窗口边界,对SLA的影响。
13)配置示例
13.1 Kafka消费者(最小化秩序障碍)
properties max.poll.records=500 enable.auto.commit=false # коммит после успешной обработки батча isolation.level=read_committed
13.2 RabbitMQ(以并发为代价)
每个队列一个concumer +'basic。qos(prefetch=1)`
对于并发-多队列和hash-exchange:bash rabbitmq-plugins enable rabbitmq_consistent_hash_exchange публикуем с хедером/ключом для консистентного хеша
13.3 SQS FIFO
设置MessageGroupId=key。并发=组数。
MessageDeduplicationId用于双倍保护(在提供程序窗口中)。
13.4 NATS JetStream(ordered consumer,草图)
bash nats consumer add ORDERS ORD-KEY-42 --filter "orders.42.>" --deliver pull \
--ack explicit --max-deliver 6
14) FAQ
问:我需要全球秩序吗?
答:几乎从来没有。几乎总是有足够的按键。全球秩序-价格昂贵,并且影响了可用性。
问:如何以严格的顺序使用"有毒"信息?
答:仅将其密钥/组转换为DLQ,其余部分继续。
Q:可以同时获得顺序和比例吗?
答:是的,按键顺序+很多键/分期付款+在需要的地方进行偶数操作和重新排序缓冲。
问:更重要的是什么:顺序还是唯一的?
答:对于大多数域,按键+有效的异常效果(等效性/UPSERT)排序。运输可以是空运。
15)结果
订单是围绕业务密钥而不是昂贵的全球纪律的本地保证。设计密钥和批次,限制"热"密钥,使用等效性,并在需要时使用sequence+reordering缓冲区。监视"出订单"和"锁定密钥"指标,测试故障-并且在性能和可用性方面无需牺牲即可获得可预测的处理。