|
二〇一四年,大型我参与了一个电商平台的网站订单系统重构。那时用户量刚过千万,系统数据库还是中间单主库,每天凌晨的实践批处理要把全量订单同步到报表库。一次大促前的大型压测,数据库连接池直接被打穿,网站页面上的系统“提交订单”按钮点了没反应,用户反复刷新,中间结果重复下单。实践那晚我和DBA一起盯着监控屏,大型看着CPU曲线像心电图一样起伏,网站凌晨三点,系统我们决定把所有订单写入先放到消息队列里,中间让后端慢慢消化。实践 这个决定改变了我的技术认知。中间件不是“用了就高级”的装饰品,而是系统在流量冲击下能不能活下来的分水岭。所谓大型网站,本质上是把单机做不到的事情,拆成多台机器协作完成。可机器一旦多了,网络会断、时钟会漂、进程会挂,一切原本在单机内默认成立的前提,全部失效。这时候,中间件就是那个帮你处理“分布式带来的麻烦”的层。 拿消息队列来说,它解决的不只是削峰填谷。更关键的是,它把“同步调用”变成“异步通知”,让系统之间不再互相等待。我见过一个团队把订单创建后的所有后续操作——发短信、更新库存、生成发票——全部改成异步消费,下单耗时从800毫秒降到120毫秒。但代价是,你必须接受最终一致性。用户下单后可能晚几秒才看到发票,这需要产品层面有心理准备。技术上,消息可能丢失,可能重复,所以消费端必须设计成幂等的。有一次我们消息中间件升级,老版本的消息堆积了半小时,等新版本起来,积压的消息像潮水一样涌向下游,下游系统瞬间被冲垮。后来我们加了限流和背压机制,才稳住局面。 分布式事务是另一个坑。传统数据库的ACID在分布式环境下基本是个奢望。我记得有个支付团队,坚持用两阶段提交协议来保证跨库一致性,结果一旦某个参与方响应超时,整个事务就卡在那里,数据库连接被占着不放,很快连接池就满了。后来他们改用TCC(Try-Confirm-Cancel)模式,把事务拆成三个阶段,每个阶段都记录日志,靠补偿机制来兜底。这中间的取舍很微妙——你不能既要强一致,又要高可用,还要低延迟。CAP理论说了,三者只能取其二。实际业务里,大部分场景都选了AP(可用性和分区容错性),用最终一致性换体验。 缓存中间件看起来简单,用起来全是细节。Redis快,可一旦缓存雪崩,所有请求直扑数据库,那场面比没有缓存还惨。我见过一次事故:缓存里的商品信息设置了统一的过期时间,零点一到,所有key同时失效,数据库瞬间收到几千个并发查询,CPU直接飙到100%。后来我们给过期时间加了随机偏移量,让失效时间散开分布。这个改动很小,但效果立竿见影。缓存击穿也一样,某个热点key失效的那一瞬间,大量请求直接穿透到数据库。解决办法是加互斥锁,或者把热点key的过期时间设成永不失效,后台异步更新。 中间件实践到最后,考验的是你对业务的理解深度。技术选型没有绝对的对错,只有适不适合当前阶段。我见过创业公司一上来就上全套微服务,结果运维成本比业务开发还高。也见过大厂的核心系统,十年了还在用单体架构加消息队列,照样扛住双十一的流量。关键不在于用了什么中间件,而在于你是否清楚每个中间件解决了什么问题,又带来了哪些新问题。系统规模增长的过程,就是你不断和这些“新问题”较劲的过程。那些看似枯燥的中间件配置、参数调优、故障演练,其实都是在为系统的每一次平稳运行买单。没有银弹,但有迹可循。 |