数周前,吾撰文记一事,乃于.NET中所建之事件驱动订单流水也。有CTO名Andrew Tan者,留评指吾之出箱模式存隙——轮询间隔以延迟易数据库负载,且无御多轮询实例相践之策。
吾于后续之文,已弥缝发箱之隙。然安德鲁复指二事,亦当理会。此乃经手诸事之后,管路之状也。
吾始所行之境
原管道有运作之出盒模式。订单与出盒记录,同书于 PostgreSQL 之交易。一后台服务,每五秒轮询,而发布于 Kafka。消息既成发布,则标为已处理。
是事可行。然安德鲁察得三隙:
- 无保护于横向扩展——二轮询实例,或取同消息。
- 卡夫卡宕机时无后备机制——唯五秒一重试
- 无死信队列——屡败之讯永滞原地
修一——FOR UPDATE SKIP LOCKED
旧询但取未处之讯。若启二例此务,俱揽同讯,欲发之再
其治乃生SQL询,具FOR UPDATE SKIP LOCKED:
SELECT * FROM "OutboxMessages"
WHERE "Processed" = false AND "RetryCount" < 3
ORDER BY "CreatedAt"
FOR UPDATE SKIP LOCKED
FOR UPDATE锁定行于事务期间。SKIP LOCKED意味着其他轮询实例将跳过已锁定的行,而非等待。两实例并行运行,永无争抢同一消息之虞。随心所欲横向扩展。
交易横贯取用、处理、存贮之全周期而未尝阖。惟及讯息既标为已处理,交易亦既成,锁钥乃释。
修二——对卡夫卡失事施以指数退避
旧式服务,无论何事,每五秒必复试。卡夫卡若陷,则恒率频击经纪,求连之试。
新设之务,察其发布或败,遂调其待时之度:
if (anyKafkaFailure)
{
_logger.LogWarning("Kafka publish failed. Backing off for {Seconds}s", _currentBackoff);
await Task.Delay(TimeSpan.FromSeconds(_currentBackoff), stoppingToken);
_currentBackoff = Math.Min(_currentBackoff * 2, MaxBackoffSeconds);
}
else
{
_currentBackoff = BaseBackoffSeconds;
await Task.Delay(TimeSpan.FromSeconds(BaseBackoffSeconds), stoppingToken);
}
初败,待五秒,继而十,再二十,终限于六十。凡成者,复归五秒。当Kafka困顿之际,此务能从容退避,不致加剧其弊。
不良之负载若未能反序列化,则增重试之数,然不启回退——惟 Kafka 之连接失败者方为之。此别有深意,盖不欲一腐之讯迟滞其余之处理也。
修正三——死信之路
原实作止重试于三度,而置讯于待发之匣,任其滞留。RetryCount = 3,其形虽寂,而不可见也。
今若信息至而重试之限已竭,则移于DeadLetterMessages之表:
if (message.RetryCount >= MaxRetries)
{
var deadLetter = new DeadLetterMessage
{
OrderId = message.OrderId,
EventType = message.EventType,
Payload = message.Payload,
OriginalCreatedAt = message.CreatedAt,
DeadLetteredAt = DateTime.UtcNow,
FailureReason = message.Error ?? "Max retries exceeded",
RetryCount = message.RetryCount
};
context.DeadLetterMessages.Add(deadLetter);
context.OutboxMessages.Remove(message);
_logger.LogWarning(
"Message {MessageId} for order {OrderId} moved to dead letter after {RetryCount} retries",
message.Id, message.OrderId, message.RetryCount);
}
发箱则净。死讯有所可见。复有GET /api/deadletters之端,使司者得察其败因,而无需直触数据库。
现今全貌何如
今之发件处理器已能净理四境:
吉途 - 消息既取,播于Kafka,标为已理。五秒后复询。
Kafka倾颓 - 播失,重试计增,延数倍。服务渐待久时,Kafka复振则再试。
多实例 - 更新跳过锁定,确保每讯必属一例。无复本之刊行.
恒久之败 - 三次重试之后,讯移于死信。发信箱洁净无瑕。司者可察而手复.
诚之映照
无安德鲁之评,此诸善皆不可成。原实之施,试之则效。三缺仅现于特制之产境——横扩之时,中继之败,恒久之恶讯是也。
此乃公码评审之重也。新目之观者,尝遇此困者,其值胜独审之量。
源码:github.com/aftabkh4n/order-pipeline
若尔构建事件驱动之系统,此篇当与原文并读。出箱模式乃其根基。此三者,方使其堪于生产。












