1. 大结局为什么要写"纠偏"——这个系列走到终章的一次彻底复盘
我当初开"1999点科技树"这个系列,设定其实很朴素:把自己拽回90年代末那个技术荒原上,沿着一条模拟的"科技树"一路点到今天,边点边把沿途的关键原理、工程实践讲透。这个玩法听起来很有画面感,可真写到第十一、十二合辑,也就是大结局时,我意识到最该做的事反而不是继续往前堆新技术,而是停下来做一次彻底的"纠偏"。
为什么要纠偏?因为这个系列写到中后期,我发现自己埋了好几个隐患。为了把微服务的戏剧性讲足,我在前面几篇里有意无意地把"微服务"塑造成了一副"包治百病"的样子:仿佛上了微服务,弹性就有了、扩展性就有了、团队协作效率也就有了。甚至有一篇的结尾我写过类似"只要按业务域把服务拆开,所有架构问题都会迎刃而解"的话。后来我在真实项目里被结结实实教育了一通:架构问题的解决从来不是靠"拆"这个动作,而是靠拆之前的设计判断和拆之后的长线治理。这个教训非常典型,所以我把它放在大结局的开篇,作为整篇纠偏的引子。
这一篇不是给纯小白看的基础教程,而是面向两类人写的:一类是跟着这个系列一路读到现在的老读者,你们需要在收官时把前面十篇里散落的知识点重新拧紧一遍;另一类是在真实的微服务项目里已经摔过跟头、正在反复追问"当初为什么这么设计"的实践者。我会把微服务实践中最容易跑偏的认知、拆分与架构设计的关键判断、以及一套可落地的Spring Cloud微服务最小闭环重新过一遍,再把"数据通信网络如何影响服务拆分""第三方API对接怎么设计才不拖垮链路"这类高频问题一次性讲透。
另外,为什么第十一、十二篇要合并成大结局?就是这个系列到了这个位置,再往下写单体拆分、服务治理这些增量内容,边际收益已经很低了。真正剩余价值最大的,是对前面所有内容的"校准"。就像你写代码写了一千行,最后最值钱的未必是再加一百行功能,而是回头重构掉那几百行让你睡不着觉的坏味道。这篇就是我给整个系列做的"重构"。
2. 认知纠偏:这三个根深蒂固的观念,坑过太多人
2.1 微服务不是拆分越细越好
在前面某篇里我提过一个词叫"服务粒度",但当时讲得比较含糊,这次先把结论放在最前面:微服务的核心价值不是"拆",而是"边界的确定性"。一个服务如果边界清晰、职责单一、能够独立发布、独立扩展,那它就是一个合格的微服务。至于代码有多少行、接口有几个、是不是用了Spring Boot,这些都不构成判断它是不是微服务的标准。
很多团队容易走极端,上来就把一个用户服务拆成"用户基本信息服务""用户地址服务""用户积分服务"。听起来很微服务,实际维护起来就是一场灾难。每次要渲染一个用户详情页,前端要聚合三四个服务的接口;每次用户注册的流程要跨服务保持状态一致。这种拆法本质上就是把一个高内聚的业务流程强行切碎,然后把这堆碎片用网络调用重新粘起来——除了把链路变长、把故障点变多,没有带来任何架构收益。
我自己判断要不要拆一个边界,实际上只看三个问题。
- 这个功能的变更频率和调用方,是不是真的和旁边那些功能明显不一样?
- 拆出去之后,它能不能做到独立的部署、独立的扩容、独立的故障恢复?
- 它的数据归属能不能从根上划清楚,而不是跟别的服务藕断丝连?
三个问题里但凡有一个回答不上来,我就不拆。微服务的粒度判断,永远以"业务能力域"为基准,再叠加团队组织规模的适配,而不是拍脑袋按功能菜单切。
注意:判断边界时最容易骗自己的是"它变化少所以拆出去不影响别人"。但真正的边界判断核心,其实是"数据能不能割裂"。数据割不开的拆分,最后都会变成分布式系统的灾难现场,这个问题在第4节讲数据通信时我会再展开。
2.2 微服务不等于Spring Cloud全家桶
这个系列前期花了相当篇幅讲Spring Cloud,导致不少读者形成了一个错觉:做微服务,就必须把Nacos、Gateway、OpenFeign、Sentinel、Seata这些组件全铺上去。这套东西确实能跑,但它的学习成本、运维成本、资源开销都不是一般团队扛得起的。
我亲身接触过一个案例:一个日活几千的系统,硬是上了微服务全家桶,光注册中心、配置中心、网关、认证中心这四个基础组件就占了八台生产机器,业务代码还没怎么铺开。这种项目就是典型的"为了微服务而微服务",每月的服务器账单和运维工单能把人逼疯。
微服务本质上是一种架构决策,而不是一种软件清单。如何落地、用什么工具链,完全取决于团队现状和业务场景。如果你在Java生态内,Spring Cloud确实是最成熟的选择;但如果你更偏云原生,完全可以走更轻的路线——服务直接通过容器平台的负载均衡对外暴露,注册发现交给Kubernetes原生的Service机制,链路追踪交给云厂商托管的可观测套件。如果你的团队技术栈是Go,那Kratos、Go-kit甚至纯gRPC+etcd也是一条完全成立的技术路线。
技术选型这块,我这些年总结下来就一句话:组件服务于架构决策,而不是反过来。架构目标决定了需要哪些能力,这些能力和团队技术栈交叉出选型,选型再落到具体开源项目。顺序一颠倒,后面全是坑。很多人一上来先选一把"瑞士军刀",再硬把业务往里按——这就是典型的认知偏差,而且这种偏差在技术圈里传播得特别快,因为工具栈越"豪华",看起来越像资深架构师。
2.3 分布式事务不是默认方案,能避免就避免
这算老生常谈了,但既然是大结局,必须把它放在显眼位置。很多新手一接触微服务,第一反应就是"分布式事务怎么办",紧接着就是Seata、TCC、Saga这些名词。我先说一个反常识但越来越被验证的判断:分布式事务是微服务里最昂贵的奢侈品之一,能不用就尽量不用,能局部用就不要全局用。
怎么避免?核心思路两条。第一,把真正需要强一致的业务圈进同一个服务里,用本地事务解决。第二,接受最终一致性,用可靠消息加补偿操作,把跨服务的数据状态在时间轴上对齐。
用一个经典场景来说清楚:下单扣库存。很多团队第一反应是拆成订单服务和库存服务,然后上分布式事务。但如果换个思路,把"创建订单并扣减库存"这件事设计成下单服务的一个本地事务动作,库存的扣减通过事务消息或本地消息表投递给库存服务去执行,配合幂等和重试,用户感知上没有任何差别,但系统复杂度低了一个量级。库存服务依然可以独立部署,只是它的数据写入由消息驱动,而不是由强一致协议驱动。
如果确实有几个场景绕不开强一致,那再做分布式事务。而且我强烈建议优先考虑Saga或者TCC这类允许业务自定义补偿动作的方案,而不是无脑上基于XA的全局锁方案。XA那种持有全局锁的做法,在高并发场景下几乎必然会出性能事故,这是我在真实项目里踩过最痛的坑之一。说白了,分布式事务解决的从来不是"数据怎么一致"的问题,而是"业务怎么在失败后收场"的问题——先想清楚补偿流程,再决定要不要上事务框架。
3. 架构设计与拆分实操:先把图画对,再谈写代码
3.1 一张好的微服务架构图,信息量被大多数人低估了
"微服务架构图"这个热词常年挂在搜索栏里,说明大量同学都在找架构图做参考。但我在各种平台观察到一个普遍问题:网上流传的架构图,大多是照着教科书画的"标准图",画得很整齐,把服务、网关、注册中心、中间件都标得清清楚楚,但你真拿它去指导一个项目的改造落地,会发现根本不够用。
一张真正有用的微服务架构图,至少要能回答四个问题:
- 有哪些服务,每个服务的核心业务职责是什么?
- 服务间的调用关系长什么样,哪条链路最热,哪条链路最脆弱?
- 数据归属于哪个服务,服务之间有没有互相穿透去读对方数据库的路径?
- 消息队列、缓存、搜索、对象存储这些中间件,分别由谁生产、谁消费、谁依赖?
我画架构图现在完全不追求美观,更看重信息密度。一张全系统总览图,加上每个核心子链路的局部图,远比一张塞满微服务组件图标、却看不出业务流向的"装饰画"有用得多。工具上用draw.io、ProcessOn、Visio都可以,真正的门槛不是工具,而是画之前你有没有把上面四个问题想明白。架构图本质上是设计文档的可视化投影,不是美术作品,这点必须想清楚。
3.2 一个值得参考的拆分案例:从单体后台到微服务的渐进改造
我接手过最典型的一个改造项目,是一个老旧的单体后台管理平台,模块包括用户权限、机构管理、商品管理、订单管理、微信公众号粉丝管理、消息推送。整个系统耦合很深,用户表和订单表在同一个库里,微信公众号模块还通过定时任务去扫描数据库再发消息。系统勉强能维持运行,但每次发版本都心惊胆战,改一个模块就要全量回归。
我当时没有采取"一步到位拆成十个微服务"的策略,而是分两步走。第一步把"微信公众号粉丝管理"和"消息推送"这两个与核心业务耦合最低的模块拆出去,做成独立的公众号服务和消息服务。这一步动刀最小、收益最大,因为这两个模块的变更频率和商城核心业务完全不在一个节奏上,拆出去之后,核心业务发版再也不用被推送逻辑的修改拖着走。
第二步才轮到核心域。围绕商品和订单,我保留了"商品服务"和"订单服务"两个大的服务边界,把用户权限相关的通用能力沉淀为独立的认证服务,通过网关统一鉴权。而库存这个数据,当时是最让人头疼的,我没有盲目把它拆成独立的库存服务,而是留在订单服务里,用本地事务加消息补偿去协调。原因很简单:以当时的业务体量,库存和订单的一致性需求,优先级远高于库存服务的独立扩展性。如果当时为了追求"标准微服务",硬把库存拆出去,等于给团队塞了一个分布式事务的大麻烦。
这个案例想传递的核心判断是:微服务拆分不是"一步到位"的艺术,而是"渐进演进"的手艺。任何宣称能在短期内把单体彻底粉碎成微服务、并且线上无痛交付的方案,我都会打一个大大的问号。
3.3 数据通信网络:服务间通信与数据交互的关键设计
"数据通信网络与微服务"这个热词讲的就是服务间通信,这块是我踩坑最密集的领域,值得单独复盘。
服务间通信只有两大类,同步与异步。 同步通信以HTTP/RPC为主,优点是语义简单、结果实时,代价却是强耦合、耗时叠加、故障传播快。我的经验准则是:只有"必须立刻知道结果"的调用才适合走同步,比如登录鉴权、核心信息查询、下订单前的预校验。 异步通信以消息队列为主,代价是要额外维护事件模型和中间件,但换来了解耦、削峰、故障隔离这些更值钱的能力。所以凡是可以延迟、可以补偿、可以重试的动作,都应该优先设计成异步。
之前讲微信公众号对接的场合我也反复推过一套方案:用户下单成功后,如果业务要求"立刻推送公众号通知",很多人第一反应是同步调微信公众号API。公众号接口响应又慢又不稳定,经常一秒多才回来,整个下单链路就被这个无关紧要的推送卡住。我后来改成下单成功后只发布一个订单创建事件,由消息服务异步订阅,再调公众号API,推送结果回写状态表。下单链路从700毫秒降到不到100毫秒,推送那边挂了也不影响主流程,还有重试和补偿报表兜底。这就是异步设计最典型的收益。
这里有一条铁律我必须再敲一次黑板:服务之间禁止直连对方的数据库。很多团队为了"微服务化",把代码拆了,数据库却还是所有服务共享一个大库。这种状态比不拆还危险——你获得了微服务的故障复杂度,却完全丢掉了微服务的隔离能力。数据层面,至少要做到每个服务一个独立数据库;历史包袱太重的话,至少也要独立Schema加严格的访问控制。复述一遍可能会被嫌啰嗦,但我在咨询中见过太多因为穿透数据库导致的事故,这条再强调多少遍都不为过。
4. 收官实战:从零手搭一套最小的Spring Cloud微服务闭环
4.1 环境与版本选型
大结局光讲道理没有说服力,我重新完整过一遍当前最常用、踩坑最少的一套组合:Spring Boot 3.x + Spring Cloud 2023.x + Spring Cloud Alibaba 2023.x + Nacos 2.x。这套组合目前基本是Java微服务生态的一条成熟基线。
为什么特意强调用新版本,而不是翻出网上还流传的一大堆老教程?因为Spring Boot 3基于Jakarta EE和Java 17+,无论是内置的性能优化、安全基线还是社区活跃度,都远非Spring Boot 2时代的存量方案可比。如果你还在用Spring Cloud 2021那批版本,很多依赖已经进入维护尾声,新特性基本与你无关,出了问题能查到的社区答案也会越来越少。
环境准备阶段有几个务必注意的点:
- JDK统一用17或21,除非历史项目锁死了Java 8,否则不要再开新坑用旧版
- Maven建议用3.8以上独立版本,不要依赖IDE内置的那个,否则不同项目切来切去容易踩版本坑
- Nacos务必选2.x,不仅性能更好,控制台和配置管理能力也明显比1.x时代成熟得多
4.2 核心搭建流程
我直接按步骤写,把关键代码和配置贴出来。完整项目骨架给大家一个可参考的落地方式。
第一步:创建父POM,统一管理版本。
<dependencyManagement> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2023.0.1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencyManagement>第二步:创建网关服务gateway,引入Spring Cloud Gateway和Nacos服务发现。
spring: application: name: gateway-service cloud: nacos: discovery: server-addr: localhost:8848 gateway: discovery: locator: enabled: true routes: - id: user-service uri: lb://user-service predicates: - Path=/user/**这一步最常见的新手坑是:网关注册进Nacos之后一直显示没注册成功,或者路由转发不生效。绝大多数情况是Nacos版本和Spring Cloud Alibaba版本不匹配,或者是pom里同时引入了spring-boot-starter-web——Spring Cloud Gateway基于WebFlux,跟Spring Web MVC是冲突的,加进去直接启动不起来。这个坑我当年搭第一个Gateway时踩了一整个下午,核心表现就是启动日志报一堆莫名其妙的循环依赖。
第三步:创建业务服务user-service,注册到Nacos,提供基本查询接口。业务服务里的依赖很轻,只需要web和nacos-discovery两块。
第四步:服务间调用使用OpenFeign。注意新版本里OpenFeign还需要额外引入spring-cloud-starter-loadbalancer,否则运行时会一直报找不到服务实例。这个问题极其常见,属于"不会导致启动报错但一定会在运行时报错"的经典陷阱。
@FeignClient(name = "order-service") public interface OrderFeign { @GetMapping("/order/{id}") OrderDTO getOrder(@PathVariable("id") Long id); }第五步:接入Sentinel做熔断降级,给调用方兜底。这一步看起来是"附加功能",但我强烈建议第一版就加上。微服务没有熔断机制,等于电路没有保险丝,最终一定会有慢接口把整个网关线程池拖成雪崩。这不是危言耸听,我经历过一次真实事故:一个第三方接口从300毫秒退化到10秒,结果整个网关的线程池被打满,所有业务接口全部超时。
如果是本地学习,走到这里已经跑通了"网关->注册中心->服务调用->熔断保护"的最小闭环。这四件事就是微服务的最小内核,剩下所有组件都是在这个内核之上做加法。
4.3 踩坑记录与排查技巧速查
做了这么多年微服务,我可以把话放在这里:绝大多数启动问题,根源都在依赖版本或配置项的匹配关系上。我把高频问题整理成速查表,方便大家直接按表排查。
| 现象 | 最常见原因 | 解决办法 |
|---|---|---|
| 服务启动后没有注册到Nacos | application.yml里没配spring.cloud.nacos.discovery.server-addr,或namespace不一致 | 检查服务发现地址与namespace配置 |
| Gateway路由规则不生效 | 路由uri漏了lb://前缀,或predicates路径匹配错误 | 路由uri统一改成lb://服务名 |
| OpenFeign调用报"找不到实例" | 缺少spring-cloud-starter-loadbalancer | 在调用方pom里补依赖 |
| 服务间调用频繁超时 | 默认重试机制和服务端稳定性的矛盾 | 关闭OpenFeign重试,改用Sentinel熔断 |
| Nacos配置中心热更新不生效 | 类上没有加@RefreshScope | 给配置类补上@RefreshScope并核对dataId |
| 网关启动报循环依赖 | pom里同时引入了web starter和gateway | 网关服务去掉spring-boot-starter-web |
最后单独说说"微信公众号测试号服务API对接"这个高频需求。很多团队在做微服务时都不把第三方API对接当回事,直接同步调用,结果就是前面讲的链路被拖垮。凡是这种第三方接口,不管对方是微信、短信通道还是别的什么,一律设计成异步任务处理,独立线程池或消息队列,并单独配置超时、重试和日志落库。这条经验适用于所有需要对接外部服务的场景,适用范围远超公众号,属于微服务集成层的通用避坑法则。
5. 未来之光:踩过坑之后,我对微服务走向的真实判断
5.1 微服务没有落幕,只是换了形态
每次看到"微服务已死""微服务过时"这类标题,我都觉得它把表象当成了实质。以这些年的一线感受,微服务压根没有退场,而是溶进了云原生的大背景。容器成了服务部署的标准单元,Kubernetes接管了服务发现和编排的底层能力,服务网格把流量治理、熔断、限流从应用代码里抽剥出来,下沉到了基础设施层。
对普通开发者来说,最明显的变化是:以前得自己维护Feign调用、写熔断规则、调限流参数,现在很多团队已经把这些能力交给了平台层。业务代码越来越干净,跨语言的服务通信也越来越标准。用一句话形容:微服务走了十年,终于从一个需要精心伺候的"婴儿",长成了一个可以自理的"成年人"。这哪里是落幕,分明是成熟。
5.2 可观测性和成本治理,是未来真正值得投入的方向
站在这个大结局的时间节点往回看,我认为未来几年微服务领域最值得投入的,不是发明新的拆分方法,而是把可观测性做透。分布式系统最大的灾难从来不是故障本身,而是故障发生了你定位不到。链路追踪、日志聚合、指标监控、拓扑分析,这些能力加在一起,才构成分布式系统的"全息地图"。没有这张地图,你拆得再多的服务,都只是把单点故障变成了多点故障,反而更难排查。
另外一个方向是成本治理。微服务化之后资源利用率偏低是普遍存在的现象。以前一台机器跑一个大单体,现在十个小服务分散在十台机器上,大量服务实例的CPU常年用不满。这几年我看到成熟团队在做的方向,包括服务密度优化、自适应扩缩容、混部调度,本质上都是在解决"微服务带来的资源离散化"问题。这些问题比纠结某个服务到底拆不拆有价值得多。
5.3 给后来者的几句实在话
最后换个角色,如果有人现在问我"微服务到底该怎么学、怎么用",我给的建议非常朴素,就三条。
第一,先把单体写好。这句话听上去像废话,但微服务里的很多问题,本质上是单体阶段就埋下的——模块耦合、数据边界模糊、接口设计混乱。这些问题不解决,强行拆成微服务只会被进一步放大,不会自动消失。
第二,在真实业务中感悟架构。不要纯粹为了技术炫技引入微服务。当你发现一个服务因为团队协作、性能瓶颈、发布节奏的原因撑不住了,拆分是自然发生的,那时候的拆分才是有生命力的。被人为催熟的微服务化项目,我见过太多半途夭折的了。
第三,永远保持纠偏能力。当发现演讲是偏的、方案是有缺陷的时候,敢于承认、敢于调整,才是技术人最可贵的品质。这和我为什么把收官之作写成"纠偏"是同一套逻辑。
我在这个系列里反复说的一句话,在收尾时再认真重复一遍:技术方案永远是场景的函数,没有银弹,只有不断逼近正确的迭代。微服务实践这一课,到这篇算真正收官了;而关于架构学习的下一课,永远存在于你正在面对的那个真实系统的现场里。