谷粒商城第6阶段实战:微服务拆分、Nacos网关与分布式事务核心解析 1. 谷粒商城第6阶段从单体到微服务的核心跨越谷粒商城这个实战项目前5个阶段基本把单体架构下的CRUD、权限、前端页面都走通了。到了第6阶段项目真正进入了一个分水岭——从传统的单体应用向微服务架构演进。这个阶段涉及的内容不再只是简单的增删改查而是要开始处理分布式环境下的一系列复杂问题服务怎么拆、接口怎么调、数据怎么保持一致性、请求怎么路由到正确的服务。如果你正在通过这个开源项目学习微服务或者打算把谷粒商城的代码拉下来跑一遍那么第6阶段的内容一定要当作一个独立的、完整的微服务实战案例来对待而不是零零散散地去拼凑技术点。这一篇我根据自己的理解和实际操作踩过的坑把第6阶段涉及的核心内容做一个彻底拆解从架构设计思路到具体的代码实现细节再到后期调试排错尽量把每一步都讲透。1.1 第6阶段到底在解决什么问题先说一个比较反直觉的结论谷粒商城这套代码真正值钱的地方并不是那套花里胡哨的前端页面也不是某个单独的组件用法而是它以一种接近真实生产环境的方式把微服务架构中那些常见组件和业务场景串在了一起。第6阶段的核心任务可以归纳为三件事第一件事是服务拆分实践。也就是说需要把原来一个大的单体应用按照业务边界拆分成用户、商品、订单、库存、支付等多个独立的服务模块。这个拆分不是简单地把代码放到不同的文件夹里而是要真正理解每一个服务该怎么独立部署、独立伸缩、独立演进。拆分的粒度太大服务内部依然耦合严重拆分得太细服务数量暴增运维成本反而压垮团队。第二件事是微服务基础组件的引入和集成。包括注册中心、配置中心、网关等基础组件。这些组件是微服务架构的地基贯穿整个第6阶段及以后所有模块的开发。注册中心负责服务发现和健康检查配置中心负责把散落在各个服务中的配置集中管理网关统一接收外部请求并做路由转发和鉴权过滤。整套链路搭好之后后端的服务对前端和外部调用方来说都是透明且无感的。第三件事是分布式环境下的几个经典问题。这也是第6阶段最让人头疼、也最值得反复咀嚼的地方。比如多个服务之间的远程调用要通过什么方式做一个业务操作涉及多个服务比如下单要扣库存、减积分、生成订单其中一步失败怎么回滚本地缓存和分布式缓存怎么配合使用以及当大量用户同时访问时哪些环节会成为单点瓶颈又该如何通过集群、MQ削峰等手段来化解可以这样理解第6阶段之前你是一个“单体架构高级工程师”第6阶段之后你才有资格讨论“分布式系统设计”这件事。这两者的思维模式是完全不同的。1.2 这套方案适合什么人参考如果你符合下面任意一个特征我建议你认真对待这个阶段的内容第一类是学过Spring Boot但没接触过微服务的人。Spring Boot帮我们把Web开发的门槛降得非常低但是微服务带来的挑战和Spring Boot中的单机玩法完全是两个层面。第6阶段是跨越这个知识层级最好的垫脚石。第二类是准备求职面试的开发者。坦白讲谷粒商城这套项目在Java社招面试中的出场率极高它是很多人简历上“项目经验”的主要素材。而第6阶段刚好覆盖了面试中最常被追问的分布式部分——服务拆分粒度、Feign远程调用、网关路由、分布式事务、缓存一致性。把这些内容吃透远比你背一百道八股文有效。第三类是想把课堂或文档中学到的微服务理论知识落地成代码的学习者。Spring Cloud Alibaba这条技术栈的学习曲线不陡但分支非常多。你自己对着官方文档玩很容易陷入“配置完一个Hello World就不知道该干嘛”的困境。谷粒商城第6阶段通过一个完整的电商业务场景把Nacos、Gateway、OpenFeign、Sentinel、Seata等一系列组件串联起来使用这是任何书籍和文档都替代不了的。提示这一阶段的学习成本主要不在代码量而在思维转变。你可能花费大量时间排查的问题最后往往只是某个注册地址没写对、某个配置没生效的小问题。放平心态排错本身就是微服务开发中最重要的能力。2. 服务拆分的整体设计与链路分析这个阶段的代码组织方式整体上是围绕“按业务领域拆分按调用链路串联”来展开的。理解这种设计方式比单纯看代码更有用。2.1 商品的SPU与SKU模型为什么要拆到独立服务商品数据是电商系统中最基础、也最繁琐的数据。很多初学者在看谷粒商城第6阶段之前对商品模型的理解还停留在一张简单的“商品表”上。但真实的电商商品域至少要拆成SPU标准化产品单元可以理解为同一个商品的抽象比如“iPhone 15”、SKU库存量单位比如“iPhone 15 白色 256G”、品牌、分类、属性、规格参数等多个维度。这件事和微服务的关系太密切了。商品的查询、管理是独立的业务域它的读写频率、数据量级和订单系统完全不同。商品数据主要是读多写少而且会有很强的缓存需求订单数据则要求极高的事务一致性要频繁写入。把这两者放在同一个服务里起初项目小似乎没问题一旦流量上来一次订单服务的数据库慢查询就可能会拖垮商品列表页的接口响应线上事故就是这么来的。所以谷粒商城第6阶段的做法是把商品服务独立出来用户、订单、库存等也各自独立。服务之间通过OpenFeign进行远程调用把“读商品”“扣库存”这种跨服务操作变成一次RPC调用。这样做带来的好处有三个第一每个服务可以独立扩展哪边访问压力大就只扩哪边第二每个服务可以独立发布和回滚不至于一次改版动全身第三每个服务的数据库是隔离的某个服务挂了不会直接拖垮整个数据库。2.2 从一次“用户下单”看请求要经过几个服务第6阶段中一条完整的业务链路设计得非常典型。我建议你跟着这个思路把整个流程画出来而不是直接扑到代码里去抠细节。一次用户从前端点击“提交订单”到最终订单生成成功通常要经历这样几步第一步用户请求先到达API网关。网关在这一阶段充当所有前端请求的唯一入口。它根据请求路径的前缀比如/api/member、/api/order将请求路由到对应的服务上。同时网关还承担登录状态校验、签名校验、限流等横切关注点的功能。你可以把网关理解成小区的门卫——所有的访客都要先在他这里登记和检查再由他指引去对应的楼栋。第二步订单服务接收请求后并不会自己处理所有业务而是通过OpenFeign发起远程调用获取当前用户的地址信息、购物车中选中的商品信息、结算时的价格信息等。这些远程调用在代码层面看起来几乎和本地方法一样但背后框架会帮你完成服务发现从Nacos中拿到目标服务的实例列表、负载均衡选择一台健康的实例发起调用等事情。第三步订单服务调用库存服务锁定商品库存。这一步有时候是同步调用有时候通过RocketMQ发送预扣减消息谷粒商城第7阶段还会专门深入讲最终一致性方案。第6阶段中住要是基于Seata来实现分布式事务的控制保证多个跨服务写操作要么全部成功、要么全部回滚。2.3 服务划分之后的“隐性成本”服务拆分从来不是银弹这个阶段你还会明显体验到微服务框架本身带来的额外复杂度。原本本地方法调用直接返回结果现在需要经历网络传输一次完整业务操作可能在多个服务之间来回调用多次整体响应时间必然拉长原本事务边界清晰一个方法加Transactional就完事现在事务要跨服务不得不引入分布式事务方案这又会牺牲一部分性能。谷粒商城第6阶段比较接地气的处理思路是不是所有场景都上分布式事务只用它来保护写入操作的核心链路而查询类的跨服务调用用缓存异步的方式来优化。这样既保证了核心数据的准确性又让查询性能不至于因为链路太长而拖垮体验。3. 核心环节实操Nacos、Gateway与OpenFeign的配合这一部分我结合自己跑通第6阶段代码时的实际过程来讲尽量把容易出错的地方标注出来。先强调一个观点不要把这三个组件当作三个孤立的工具去记配置要理解它们是如何协同工作的。3.1 Nacos的注册与配置双中心搭建Nacos在谷粒商城第6阶段中承担两个职责服务注册/发现和配置中心。安装和启动Nacos本身不难从官网下载解压Linux上执行startup.sh -m standaloneWindows上执行startup.cmd -m standalone访问8848端口进入控制台即可。但是有几个细节我强烈建议你在开始之前就搞清楚第一服务注册到Nacos时application.yml中的spring.application.name是必需项。Nacos会以这个名字作为服务名别的服务通过OpenFeign走RPC调用时引用的也是这个名字。如果你不确定可以在Nacos控制台的服务列表中直接查看调试非常方便。第二第6阶段通常会在本地调试时用bootstrap.yml来指定Nacos配置中心的地址而不是把它全写在application.yml里。因为bootstrap.yml的加载优先级比application.yml更高先加载配置中心地址再从配置中心远程拉取公共配置。这个顺序千万不要搞混否则会出现配置中心连不上、项目却还在用本地配置导致改动不生效的情况。第三集群环境要做服务间的隔离或分组通常会在nacos.config.import或namespace等参数上做文章。本地学习和测试阶段不用管这些但生产环境的逻辑和这里几乎一致所以先了解有这回事即可。实操时建议的启动顺序是这样先启动Nacos再启动基础服务比如权限、会员最后启动商品、订单这类业务服务。每启动一个服务就去Nacos控制台确认实例是否注册成功再继续下一个。这样万一出现服务列表为空你能很快定位是哪个环节出了问题。3.2 Gateway网关的路由配置与跨域解决Spring Cloud Gateway在谷粒商城第6阶段中扮演流量入口的角色。看代码时你会发现网关服务本身不会去依赖其他任何业务服务它只加载路由配置然后根据路径把请求分发到对应服务。它的核心配置是这样一段内容spring: cloud: gateway: routes: - id: product-route uri: lb://gulimall-product predicates: - Path/api/product/** filters: - RewritePath/api/(?segment.*), /$\{segment} - id: order-route uri: lb://gulimall-order predicates: - Path/api/order/** filters: - RewritePath/api/(?segment.*), /$\{segment}这里有一个非常容易踩的坑RewritePath的正则表达式。因为前端通过网关访问后端服务时路径是/api/product/...而真正的商品服务里接口路径并不包含/api前缀所以需要用到过滤器的重写功能把/api去掉再转发。如果你配上路由之后发现40490%是这里重写路径写错了。关于跨域问题在第6阶段一定会遇到。前端项目通常跑在localhost:3000Vite或Webpack的默认端口后端的网关跑在localhost:88这两个端口不一致浏览器的同源策略会直接拦截请求。网上很多方案是在每个服务里单独加CrossOrigin注解或配置CorsFilter这种做法在第6阶段的单体演进过程中还算能用但拆分成微服务后就要改成在网关层统一处理。在网关中加入一个全局的CorsWebFilter在前端发请求时网关先返回允许的跨域响应头这样就不会出现“Access-Control-Allow-Origin”这种报错了。很多人在第6阶段卡住其实就是因为漏了这个统一配置。3.3 OpenFeign的声明式调用与相关参数OpenFeign在谷粒商城中被用来代替传统的RestTemplate和Dubbo。它最舒服的一点是调用远程接口时只需要定义一个接口写上接口的方法签名再在方法上标注对应的Spring MVC注解Feign客户端就能把你的本地方法调用变成一次HTTP请求。举一个商品服务调用库存服务的例子FeignClient(gulimall-inventory) public interface InventoryFeignService { RequestMapping(/stock/{skuId}) R getSkuStock(PathVariable(skuId) Long skuId); }这里FeignClient(gulimall-inventory)中的字符串就是目标服务注册到Nacos中的spring.application.name。如果你这里写错了或者目标服务没有成功注册到Nacos启动时大概率不会报错但一调用就会抛java.net.UnknownHostException非常隐蔽。第6阶段在调用OpenFeign时还有一个容易出现问题的场景服务间传递JSON数据时参数类型和返回类型如何处理谷粒商城引入了一个统一的返回体R也就是把业务数据、状态码和提示信息封装在一起。这个R类会被打成公共模块所有服务都依赖这个公共模块保证调用方和服务提供方对于返回数据结构的理解一致。还有个值得一提的细节OpenFeign的调用超时时间。默认的超时时间很短如果被调用的服务处理时间比较长比如商品服务查询数据库缓存中间有慢查询就会触发超时。这时候需要在配置中调整超时时间feign: client: config: default: connectTimeout: 5000 readTimeout: 10000注意排查Feign调用超时问题时一定要先确认是连接超时还是读超时。连接超时通常是网络不通或服务未启动读超时则是对方服务处理逻辑太慢或者数据库查询有性能问题需要分别对待。4. 第6阶段必须要掌握的分布式事务与缓存策略介绍完服务之间怎么通信接下来就是“跨服务后数据一致性怎么保证”这个硬骨头。第6阶段在这一部分的设计有两条清晰的线路一是通过Seata解决强一致场景二是通过缓存消息队列解决高性能场景下的最终一致问题。4.1 Seata的AT模式与分布式事务链路在单体应用里我们依赖Transactional来完成一个数据库事务。但是订单服务和库存服务各连各的数据库一个事务根本不可能同时管住两个数据库中的数据。谷粒商城第6阶段的分布式事务方案用的是Seata的AT模式。AT模式的核心思路是引入一个全局事务协调者TC所有参与分布式事务的服务RM在本地执行SQL的同时会额外记录undo_log回滚日志事务管理器TM发起全局事务如果链路中任何一个服务执行失败TC通知所有参与者反向执行undo_log中的SQL把数据恢复原样。听上去很美好但你要注意一个非常现实的问题AT模式的性能开销不小。每一次本地SQL执行步骤都要额外写undo_log所以在真实生产环境中很多高并发场景并不会无脑使用AT模式而是改用TCC或者基于消息队列的最终一致性方案。第6阶段让你接触Seata核心目的是理解分布式事务的运作逻辑而不是让你在每一个微服务接口上都套一层分布式事务。实际使用Seata时你需要保证每个参与分布式事务的数据库中都有undo_log这张表而且Seata ServerTC要独立部署并在application.yml中指定事务分组。启动过程中如果发现服务启动正常但事务一直没生效最常见的原因是service.vgroupMapping配置和Seata Server中配置的分组名不一致。4.2 缓存不一致的成因与解决套路商品信息的查询是整个商城的高频操作数据库不可能直接承受所有请求压力所以Redis缓存是绕不过去的一环。第6阶段用的是Spring Cache基于Cacheable、CacheEvict注解 Redis整合的方式。在商品服务中你可以看到很多类似这样的用法Service public class SkuInfoServiceImpl extends ServiceImplSkuInfoMapper, SkuInfo implements SkuInfoService { Cacheable(value {skuInfo}, key #skuId, unless #result null) Override public SkuInfo getSkuInfo(Long skuId) { // 从数据库查询 return this.getById(skuId); } }这个注解的逻辑很简单请求过来时先查Redis缓存如果再缓存中命中就直接返回不走进方法体如果没有命中则执行方法体从数据库查询并把结果自动写入Redis。但这个设计有一个经典的坑缓存穿透和缓存雪崩以及修改数据库后缓存如何失效。虽然Spring Cache的CacheEvict注解能实现“在调用写方法时删除对应缓存”但它天然无法处理缓存和数据库更新的先后顺序问题。生产上通常建议采用“先更新数据库再删缓存”的模式并且配合消息队列或订阅binlog来做兜底。第6阶段虽然没有把这块做到极致但你需要有这个意识面试时能把这些链路说清楚会很加分。4.3 高并发场景下如何用MQ做削峰填谷到第6阶段的后面部分代码中已经开始引入RocketMQ。引入消息队列的典型场景是订单创建后的后续处理比如发送短信通知、更新库存流水、给用户增加积分。这些操作有一个共同特点实时性要求没那么高但执行失败率不低比如调用第三方短信接口容易失败。如果在下单的主链路中同步执行这些操作一旦某个通知服务响应很慢用户的订单请求就会一直挂着转圈。所以谷粒商城会把这类非核心的依赖操作放到RocketMQ的消息队列中异步处理。这对第6阶段来说是一个很重要的思维转换并不是所有业务都追求“同步完成”。核心链路要短平快能异步的都异步能最终一致的就不要强一致。5. 常见问题速查表与避坑记录最后这部分是我根据自己调试第6阶段代码时遇到的问题以及经常在社群里看到新手提问的集中点整理出来的一份速查表。建议你在动手跑之前先看一遍能帮你省下不少排查时间。问题现象最可能的原因快速解决办法启动后服务注册不上Nacosspring.application.name未设置或Nacos地址写错检查配置文件和Nacos控制台服务列表通过网关访问接口报404Gateway的RewritePath正则配错先在浏览器直接访问目标服务的原始地址确认服务本身通Feign调用报UnknownHostExceptionFeignClient中的服务名和注册中心不一致对比目标服务的application.nameFeign调用超时默认超时时间太短或被调用服务有慢查询调整connectTimeout/readTimeout优化数据库SQL请求被跨域问题拦截网关层CorsWebFilter未配置或配置顺序错误统一在网关配置CorsFilter不要在业务服务里单个加Redis缓存管理没有生效实体类没有实现Serializable或JSON序列化器配置有误检查实体类序列化确认RedisTemplate使用的SerializerSeata事务一直不回滚数据库缺少undo_log表或事务分组配置不一致在每个业务库执行建表SQL检查Seata控制台配置接口返回的数据总是旧数据查询接口命中缓存而更新接口未清缓存在写操作上加CacheEvict或手动删除缓存还有一个几乎所有初学者都会至少踩一次的坑Maven模块之间依赖版本不一致。谷粒商城的子模块非常多公共模块里定义的版本号、依赖引用如果在其他地方手动写了具体的版本很容易出现依赖冲突——最常见的外在表现是项目能编译但启动后各种奇怪的ClassNotFoundException。如果你遇到类似问题先到根pom.xml检查依赖版本管理尽量保证组件版本统一。启动顺序也是老生常谈基础设施优先业务服务次之。Nacos、Sentinel、Seata这些中间件先起来确认没问题之后再启动业务服务。否则服务启动时连不上配置中心就是一个连环失败的尴尬局面。6. 我的一些实践心得谷粒商城第6阶段是我认为整套项目中含金量最高、也最适合反复回看的阶段。它第一次把微服务架构的多个核心组件“揉”进了一个业务闭环中让学习这件事从分散的“组件Demo”变成了完整的“系统设计”。有一点我想特别提醒你代码能跑通只是最低要求。如果你只是把项目拉下来、启动成功、页面能点就觉得自己掌握了微服务那大概率面试官追问两三个细节就会露馅。请在每个模块的代码中多停顿一下想一想“为什么这么设计”比如为什么要在订单创建后通过MQ异步发送通知为什么这里选择Redis而不是本地缓存为什么这个接口的远程调用要设置这么长的超时时间把这些“为什么”想清楚你的收获会比单纯敲一遍代码大得多。实战项目最大的价值不在于代码本身而在于它模拟的那种接近生产环境的设计权衡过程。第6阶段的这些内容哪怕放到真实的电商公司里也仍然是后端开发每天都绕不开的核心话题。