我头一回被中间件坑惨是2016年。那会儿公司搞微服务,老板说要用消息队列解耦订单和库存。我们欢天喜地上了Kafka 0.8,当时那版本存offset还在ZooKeeper里,真是个噩梦。消费者一重启就重复消费,我们弄了张去重表,结果每天还是冒出几百条重复的订单消息。后来查了半天,发现是max.poll.interval.ms设置太短,消费者卡在数据库写入上,还没提交offset就被踢出组了。那会儿我真觉得中间件就是个骗子,什么问题都没解决,反而制造了更多新问题。
但年头久了,我才慢慢品出点意思。中间件不是骗子,它是个转移注意力的高手。你原本只有一个数据库,现在有了消息队列,就多了消息不丢失、不重复、顺序、积压这一大坨麻烦。你原来直接在代码里调微服务,现在加了API网关,你得担心网关过载、超时、限流策略怎么配。本质上所有中间件干的事都差不多:把一个问题翻译成另一个问题,然后让你在新问题上投入更多精力。这不是坏事,因为原始问题往往更难解,但你要是以为上了一个中间件就一劳永逸,那就太天真了。
拿消息队列和API网关做个对比,特别有意思。消息队列是异步的,它在时间上耍赖,说“现在不用管,待会儿再说”。你发出一个订单事件,它不保证什么时候到,只保证大概能到(如果你配置对了的话)。API网关则相反,它是同步的,在空间上耍赖,把所有请求拦在一个门口,说“我盯着你呢”。这两个中间件就像两种人生哲学,一个活在不确定的未来,一个活在僵硬的当下。我骨子里喜欢异步,因为觉得它潇洒,可两次生产事故教会了我:潇洒是需要付出代价的。一次是消费者没处理完就崩溃,消息在Redis里堆积到几个G,最后直接把缓存撑爆;另一次是上游系统改了消息体,下游找不到字段,反序列化报错,整条链路卡了一个多小时。同期,我们维护的另一个系统用的是同步RPC,虽然经常被慢接口拖累,但问题好找得多——一个trace ID就查完了。你说哪个更好?其实没有哪个更好,只有哪个更符合你愿意忍受的痛。RabbitMQ对开发者友好,但吞吐量上不去;Kafka吞吐量高,可重复消费和顺序问题能让你做梦都在写幂等;RocketMQ可靠性也高、事务消息很强大,但架包太重,运维复杂。选中间件不是选美,是选一种你半夜起来处理事故时不至于太崩溃的方式。
我后来想明白一个比喻。中间件就像船上的排水泵,它不能让你不落水,只是让进水速度慢一点,让你能撑到下一个港口。分布式系统天生就是熵增的,服务之间互相依赖,数据到处复制,调用链条长到一眼望不到头。中间件的作用就是把这个熵增过程包装得稍微体面一些:你用队列把突发的流量缓冲下来,用缓存把重复的查询挡回去,用网关把不怀好意的请求过滤掉。但你要清楚,它没有消灭混乱,它只是把混乱扩散到更大的尺度上,让每一个局部看起来还算有序。全局的复杂只会转移,不会消失。你以为加了中间件系统变得简单了?不,它只是把复杂性从代码挪到了运维,从开发时挪到了凌晨三点。
说白了,中间件是一条永远在漏水的船,可你不得不上。因为趟水的滋味更难受。这几年我见过太多团队,连业务都没摸清,张口就要上Service Mesh,就要搞Seata分布式事务,好像中间件能解决他们的管理问题。真上了之后,战战兢兢,每天盯着监控面板,像看心电图一样。我自己的经验是,中间件的选择其实是自我认知的过程。你得先承认自己系统很脆弱,才会心甘情愿地接受那些额外负担。比起那些宣称“让中间件为业务让路”的漂亮话,我更相信一个朴素的道理:你愿意为哪一类的头疼买单,直接决定了你架构的走向。中间件不会给你自由,它只给你一种更复杂的约束,而我们要做的,是在这个约束里找到自己的节奏。