微服务架构设计:从拆库到拆服务,我用一把辛酸泪帮你避坑
先说个真事儿。凌晨两点,手机狂震。我摸黑一看,支付服务全红。告警群里刷屏,用户开始骂娘。我强撑着爬起来,打开链路追踪,一顿查——支付服务调会员,会员调营销,营销又调支付。好家伙,循环依赖,死结。
这就是我参与过的某个微服务架构设计的杰作。十几个服务拆得漂漂亮亮,结果一到流量高峰就抛锚。你问我当时啥感受?想砸电脑。
做这一行久了,你会发现很多团队把微服务当成时尚。业务没想明白,先拆了再说。技术选型也没想清楚,注册中心、配置中心、网关一股脑全上。结果呢?维护成本翻倍,交付效率反而降了。
今天不聊那些长篇大论,就说几个我踩过的坑。如果能让你少熬夜,那就值了。
一、你是按业务拆,还是按心情拆?
我见过最讽刺的“微服务架构设计”,是把Controller、Service、DAO分别拆成三个服务。看起来垂直分层,实际上每次需求改动,要同时改三个服务,然后三个发布流程。这叫什么?这叫“分布式单体”。拆了个寂寞。
真正的拆分单位是什么?是业务能力。以电商为例,用户、商品、订单、库存、支付,每个都是独立的能力域。每个服务只干一件事,而且干得好。怎么识别边界?我建议你吃透领域驱动设计里的“界限上下文”概念。别一上来就啃那本蓝皮书,先明白一个道理:上下文之间通过事件交互,而不是直接调用共享表。
微服务拆分领域驱动设计界限上下文示意图
画一张图,把业务按动词分组。比如“用户下单”涉及用户、订单、库存,但每个动词都有清晰的归属。哪个微服务负责哪个动词,谁发起事件,谁消费事件,画完你就清楚了。我当时就是用这个办法,把原来30多个混乱的服务收敛成了8个核心服务。不是吹,画图比开会有用多了。
二、分布式事务:你绕不过的坎
拆服务最疼的地方,就是数据库。原来一个事务搞定的事情,现在要跨库。比如下单扣库存,你怎么办?有人张口就是2PC,我第一次试完,性能掉了一半,还各种锁表。后来才明白,强一致性在微服务里就是个奢侈品,你要是敢在生产环境用2PC,分分钟被DBA骂死。
那用啥?Saga模式。它不像2PC那样锁资源,而是把业务流程拆成一系列本地事务,每个事务都有补偿动作。比如订单创建成功,库存扣减失败,那就执行“取消订单”这个补偿。整个过程是最终一致性的。我们当时用事件驱动的Saga,配合消息队列,效果不错。
微服务分布式事务Saga模式流程图
不过这里有个坑:幂等性。消息队列存在“至少一次”投递,你如果不做去重,补偿就变成双倍补偿。我们处理支付回调的时候,就吃过这个亏。所以,每个消费端必须保证幂等,最简单的方式是数据库唯一键,或者业务上的状态机。
还有更狠的实战建议:能不用分布式事务就不用。比如“扣库存”这种操作,可以放在订单服务内,用本地事务解决。只有真正跨服务不可拆分时,才考虑Saga。别看我说的轻松,真写起补偿逻辑来,脑袋要炸。
三、没有可观测性,你就是瞎子
三、没有可观测性,你就是瞎子
微服务架构设计里,最容易忽略的就是可观测性。很多团队是服务拆完,日志还是各打各的。排查一个一秒钟的请求,你得上五台机器,grep半天的日志。那种感觉,就像在黑暗里摸象。
必须上链路追踪。我们用SkyWalking,给每个请求分配一个全局traceId,从网关开始的每一个调用都要带上。这样你在图上就能看到,这个请求经过了哪些服务,耗时多少,什么地方慢。我记得第一次看到链路图的时候,才发现原来一次下单竟然要调15次内部接口。那一刻,我梦碎了。
除了链路,还需要监控和告警,不只是CPU那些基础指标。JVM堆内存呢?线程池活跃数呢?数据库连接池水位呢?每个接口的RT、QPS、错误率,都要暴露出来。我们曾经漏了一个慢SQL,连接池被打满,整个服务雪崩,而当时我们还在盯着CPU发呆。没这些数据,微服务就是裸奔。
四、微服务架构设计不是一次定稿
四、微服务架构设计不是一次定稿
最后想说,微服务架构设计不是一锤子买卖。它甚至不是技术设计,而是组织边界的映射。康威定律听过吧?系统架构会模仿沟通结构。你的团队如果按后端、前端、运维切分,那微服务怎么拆都是一个鸟样。
还有一个经验:小团队刚开始别贪多。你总共五个人,非要全套微服务,代码还没写,基础设施先搞了两周。不如老老实实把单体写好,接口版本化,等真的并发上来了,再按域拆。我们后来把几个服务合并,不仅性能上去了,代码也清晰了。
真正的微服务架构设计,是跟着业务节奏一点点演进。别追求一步到位,那是不存在的。
好了,扯了这么多。最后就一句:微服务是一场马拉松,不是百米冲刺。动手之前,多花点时间在业务边界和技术支撑上。不然半夜叫醒你的不是闹钟,是告警。