先说结论:我把核心交易系统改成事件驱动,上线三个月就出事了。去年年中,我们团队接手了一个订单系统,老板要求支撑双十一量级的峰值,据说每秒能到八千笔下单。原本的同步 RPC 调用链已经毛了,数据库连接池动不动就报警。我那时候刚从一本书里读到 event-driven 的种种好处,一拍脑袋,觉得用 Kafka 解耦一切,订单服务发个事件,库存、支付、积分各听各的,总吞吐肯定能上去。说实话,第一版跑压测的时候数字确实漂亮,TPS 从一千二跳到了五千多,我心里还挺得意——然后真实流量一进来,问题就来了。
最疼的是订单状态对不上账。同步改异步之后,用户下单成功,但库存扣减可能延迟三秒,而支付回调又来得更快,结果订单状态已经变成已支付,库存那边还在阻塞重试。更离谱的是,因为事件是 at-least-once,我加了去重表,但去重逻辑依赖订单号,而同一个订单的支付成功事件和退款事件并发过来时,时序错乱,导致用户的钱退了两次。我熬了两个通宵,日志翻得眼睛都快瞎了,才发现事件顺序根本没法在分布式环境下保证。反观同步请求,至少有一个清晰的事务边界,要么全成功要么全失败,出了问题直接报错给用户,而事件驱动让错误变得悄无声息,等对账系统发现的时候,损失已经发生了。
这次经历让我彻底反思那种“异步化 = 高性能 = 先进”的迷信。很多鼓吹事件驱动优越性的文章,都在说解耦、削峰、可用性,但没人跟你说:解耦的代价是失去了调用链的直观性,削峰的前提是消费者真要能扛得住峰值,而可用性,呵呵,一旦消息堆积,整个系统的行为就变成混沌实验。后来我做了个对比:如果还保留同步接口,但把数据库连接池调大、加缓存、做读写分离,其实也能扛到两千 TPS,更重要的是出了问题能随手定位到具体是哪个下游超时了。当然,我不是说事件驱动没用,而是它适用的场景根本不是核心交易,而是那些可以容忍延迟、需要广播、或者跟写路径无关的旁路逻辑,比如发通知、做推荐、更新搜索索引。
现在我把订单链路改回了同步+补偿事务,只在非关键环节用事件。比如支付成功后发一个订单已支付事件,用来触发发票生成和用户积分更新,这俩即使丢了也不影响主流程。而且我加了一个事件追踪的中间表,每次消费都要记录 traceId,才能勉强睡个安稳觉。如果你想用事件驱动,我真心建议先回答三个问题:这个业务能承受百毫秒以上的延迟吗?消息丢失了用户会发现吗?你能为每个事件准备幂等和重放机制吗?如果有一个答案是否定的,那它就不适合放在主链路。架构选型永远是 trade-off,不是看谁听起来高级。