事件驱动架构并不适合小型企业—从我自己的体验出发
前言
先给还不太熟悉事件驱动架构的朋友们简单说下这个概念(我让 Claude 帮忙整理的,懒得自己写了😅):
事件驱动架构是一种通过事件进行组件通信的软件设计模式。当系统状态发生变化时,事件生产者会发布事件通知,通过中央事件总线传递给所有订阅的消费者。这种模式实现了组件间的松耦合,使系统更易于扩展,并支持异步处理。
虽然事件驱动架构允许各服务独立演进,新功能可以无缝添加为新的事件消费者,但同时也增加了系统的复杂性和调试难度。
flowchart LR A[用户下单] -->|1.触发事件| B[订单服务\n事件生产者] B -->|2.发布事件| C[事件总线/消息队列] C -->|3.订阅消费| D[库存服务\n事件消费者] C -->|3.订阅消费| E[支付服务\n事件消费者] C -->|3.订阅消费| F[物流服务\n事件消费者] C -->|3.订阅消费| G[通知服务\n事件消费者]
还有一种类似的架构叫消息驱动架构:
消息驱动架构通过消息传递实现组件间通信。与事件驱动不同,消息通常包含完整数据且有明确的目标接收者,更注重点对点通信和可靠交付。消息队列确保即使接收者暂时不可用,消息也不会丢失。
flowchart LR A[订单服务] -->|1.发送消息:处理订单#123| B[消息队列] B -->|2.消息排队等待处理| B B -->|3.投递消息| C[支付处理服务] C -->|4.确认消息已处理| B
为什么我当初选择了事件驱动架构
当时在做一个即时通讯(IM)的微服务项目,我选择了事件驱动架构,主要是被这些好处给忽悠了:
彻底解耦:系统各部分完全独立,服务间没有直接依赖。生产者只管把数据扔到消息队列里就完事了,后面谁爱用谁用。这对后期维护确实很方便。
弹性扩展:有了Kubernetes加持,哪个服务资源不够用就单独给它加,多爽啊!比如信件审计服务里的AI模型突然吃内存了,我可以只给它加资源,不像单体架构那样得整体扩容。
自动恢复:靠着RabbitMQ的死信队列和重投机制,就算服务一时半会挂了,消息也不会丢。不像以前那样,系统出了问题就只能带着”病”继续跑。
调试简单:每个服务只做一件事,出了问题沿着调用链一查就知道哪出错了,不用费劲猜测。
真实体验:没想到会这么难
结果实际干起来,我遇到了几个完全没预料到的坑:
团队适应成本高得离谱
代码审查时同事一脸懵:这架构咋回事啊…
刚开始我以为就我自己折腾折腾,结果发现团队其他人看着这一堆生产者、消费者和队列直接傻眼了。培训成本比我想象的高太多,新来的同事经常会问:“这个消息到底是从哪发出来的?谁会接收?”
代码冗余到怀疑人生
有句话叫”Overhead守恒定律”——性能开销不会凭空消失,只会从一个地方转移到另一个地方(好吧,这话是我编的)。
事件驱动架构看似提高了性能,实则把成本转嫁到了代码复杂度上:
1 | def serviceA(message: Message): |
你看,核心逻辑就那么4行,却要被20行辅助代码包得严严实实。写着写着我都想骂人了。
性能需求判断失误
最初设计这系统时,目标是支持每分钟10k消息。后来压测才发现,用Rust改造一下传统的Request-Driven架构(把消息持久化延后一点)就能轻松搞定这个量级,根本不需要搞那么复杂的EDA架构。
说真的,Rust这玩意儿安全、高效,tokio运行时和并发处理能力简直完美适合IM系统。早知道就直接上Rust了,省下好多事。
Tracing系统复杂得想哭
这是所有微服务系统的通病,每次追踪一个问题简直跟侦探一样,我就不多吐槽了…
后续改进 —— Event Driven Request 架构
回过头看,事件驱动架构的本质就是在任务之间加个中间件来提供重试机制:
flowchart LR
A[Task A] -.-> B[中间件<br>提供自动重试功能] -.-> C[Task B]
B -.->|错误时| B
思来想去,我最后采用了个折中方案,给它起名叫 Event Driven Request 架构(事件驱动请求架构):
- 先用常规Web开发的方式把业务逻辑写清楚
- 通过sidecar Pod把消息队列里的消息转成HTTP请求,再调用实际业务逻辑
- 把微服务对外的请求转成事件,扔到对应Topic里,让其他业务自己去消费
用图表示:
flowchart LR
A[Service A]
B[Service B]
SA[Sidecar A]
SB[Sidecar B]
MQ[消息队列]
A -->|试图请求 Service B| SA -->|提交消息| MQ
MQ -->|拉取消息进行消费| SB -->|转化为 HTTP 请求| B
MQ --> SA --> A
MQ --> SB --> B
这种混合架构既不会让团队抓狂,又能享受事件驱动的部分好处,感觉总算找到了平衡点。
说实话,技术选型真的得看团队实际情况,盲目追求大厂架构反而会害了自己…
我的感触——混合架构才是最好的
每种架构都有不同的擅长点,例如我上面提到的 EDA 架构有高性能和强稳定性,支付系统我可能会选用这个方案,但是对于一般的 CRUD 业务逻辑,实际上没有必要选择它,传统的 RDA(请求驱动架构) 已经足够了。
从现实出发来看,大的架构中其实也包含小的“架构”,就像我前面设计的这个 EDRA 一样。总体上是事件驱动架构,但是在更小的层级来看,与 RDA 的业务写法是一致的。
我们要善于分析各种架构的优缺点,最后才能做出正确的业务决策。