跳到正文

事件驱动架构的可靠性:消息发出去,不等于事情完成了

桃总
发布日期:
1 min read
编辑此文

事件驱动架构听起来很优雅:服务之间不再互相调用,而是发布事件、订阅事件,各自完成自己的工作。

但很多系统从同步调用切到消息队列之后,只是把问题换了个地方。原来是接口超时,现在是消息丢失;原来是调用链太长,现在是排查链路断掉;原来是强耦合,现在是没人知道哪个消费者依赖哪个事件。

消息发出去,不等于事情完成了。

事件应该表达事实,不是命令

事件最容易被滥用成异步命令。

比如 SendEmail 更像命令,意思是”请你发邮件”;OrderPaid 更像事件,意思是”订单已经支付”。

两者差别很大。

命令关心执行者,事件关心事实。命令通常只有一个接收方,事件可以被多个系统订阅。命令失败后要明确重试责任,事件则应该让消费者基于事实自行决定要不要行动。

如果事件设计得像命令,系统表面上解耦了,实际只是把远程调用换成了队列调用。

可靠投递需要闭环

很多事故来自一个经典缝隙:数据库事务提交成功了,但消息发送失败了。

例如订单状态已经变成已支付,但支付完成事件没有发出去。下游库存、积分、通知系统都不知道这件事发生过。

解决思路通常是建立投递闭环:

这套东西不华丽,但很管用。可靠性经常不是靠某个神奇中间件,而是靠把每一步状态留下来。

消费者必须默认会收到重复消息

消息系统的可靠性常常建立在”至少一次投递”上。

这意味着消费者必须接受一个事实:同一条消息可能被处理多次。

所以消费逻辑要有幂等设计:

不要把”消息不会重复”当成系统假设。它迟早会重复,而且通常发生在你最不想看到它重复的时候。

顺序不是免费的

事件驱动系统里,顺序是一个昂贵资源。

如果你要求所有消息全局有序,吞吐量会被严重限制。更现实的做法是只在必要范围内保证局部顺序。

比如同一个订单的事件需要按顺序处理,但不同订单之间没有必要互相等待。这个时候可以用订单 id 作为分区键,让同一实体的事件进入同一个分区。

顺序设计要问清楚:

顺序越大,系统越慢;顺序越小,消费者越复杂。没有白拿的优雅。

可观测性决定你能不能救火

事件驱动架构最怕黑盒。

当一个用户说”我付款了但没收到通知”时,你需要知道:

这要求事件里带上 trace id、业务 id、事件 id、版本号和发生时间。日志、指标、告警也要围绕这些字段建立。

否则系统越解耦,排查时越像在雾里找开关。

最后

事件驱动架构的价值,是把系统从同步阻塞里解放出来,让不同模块围绕业务事实独立演进。

但它不是可靠性的免费午餐。可靠投递、幂等消费、局部顺序、版本兼容、死信处理、链路追踪,一个都少不了。

真正成熟的事件驱动系统,不是”用了消息队列”,而是每一条消息从产生到消费都有迹可循,失败之后有人知道怎么把它带回正轨。

上一篇
投资的第一性原理:少做、做对、做久
下一篇
技术债务管理:何时偿还、何时容忍