“各位掘友大家好!别人的清明小长假都在踏青赏花,而我的假期,是在 IDEA 的控制台和成堆的报错日志中度过的。短短三天,我经历了一场从‘前端泥潭’战略撤退,转身死磕后端微服务的‘渡劫’之旅。今天就来和大家盘点一下,这个硬核的清明假期,我都踩了哪些坑,又填了哪些坑?”
Day 1:告别苍穹外卖,Mac 党微服务基建的“血泪史”
前言:从外卖小哥到架构师的转身 假期的第一天,我终于给《苍穹外卖》画上了句号(至于总结博客嘛,下次一定!)。告别了单体项目,我的微服务进阶之旅正式发车。由于之前已经点亮了 MyBatisPlus 的技能树,我决定直奔主题,从 Docker 和微服务架构拆分开卷。
第一回合:Mac 系统 vs 视频教程,环境搭建的“渡劫”
刚上来,环境配置就给我结结实实上了一课。视频里老师用的是现成的虚拟机镜像,但我用的是 Mac(大概率还是 ARM 架构的芯片),老师给的 CentOS 7 镜像根本跑不起来。
- 寻找 OS 系统: 在网上疯狂寻找 Mac 适配的 CentOS 7 未果,最后无奈“翻箱倒柜”,找出了以前学 Linux 时配好的 CentOS 9 镜像,总算勉强把系统点亮。
- SSH 工具的妥协: 搞定了系统,连接工具又成了问题。Windows 党的“神器” MobaXterm 在 Mac 上根本没有完美的平替。搜刮了一圈,最后只能向老牌工具 FinalShell 妥协,虽然界面有些年代感,但好歹能用。
第二回合:Docker 镜像之痛
系统搞定后,开始了 Docker 的安装与基础学习。本来以为一帆风顺,结果拉取镜像(Image)时又卡壳了——国内很多 Docker 镜像源失效了,一直 Timeout。好在最后借助 AI 大哥的力量,临时找了几个可用的加速镜像源,才算度过难关。 (关于 Docker 的基础命令和数据卷等知识点,篇幅有限就不在此赘述了,黑马微服务课程里讲得很透彻。)
第三回合:重头戏!单体拆分与踩坑实录
下午进入了真正的核心:从单体架构向微服务架构拆分。回想起来,这部分的报错真是五花八门。在这里给大家盘点一下拆分初期最容易踩的几个大坑(也是我被折磨过的地方):
- 包扫描盲区 (Bean 找不到): 单体架构时所有代码都在一个包下,Spring Boot 启动类默认就能扫到。拆分成独立的微服务(比如
item-service,order-service)后,如果公共类或者 Feign 客户端放在了别的包(比如com.hmall.api),启动时就会报UnsatisfiedDependencyException,必须要加@ComponentScan或@EnableFeignClients(basePackages = "...")才能解决。 - 公共依赖抽取遗漏 (ClassNotFound): 把实体类(POJO)、通用工具类抽取到一个单独的
hmall-common模块后,其他微服务的pom.xml里如果忘记引入这个 common 依赖,代码直接一片爆红。 - 配置文件的“薛定谔”: 服务拆开了,但每个服务对应的
application.yaml里的数据库连接池(URL、账号、密码)没改对,或者忘了配置 MybatisPlus 的 Mapper 扫描路径,导致服务能启动,一查数据库就报 500。
第四回合:拥抱 Nacos 与 OpenFeign (真香警告)
熬过了拆分的阵痛,终于尝到了微服务组件的甜头。
- Nacos 注册中心: 以前服务之间调用还要硬编码写死 IP 和端口,现在直接把服务注册到 Nacos,通过服务名就能互相发现,优雅!
- OpenFeign 远程调用: 自己写
RestTemplate发起 HTTP 请求又长又丑,换上 OpenFeign 之后,只需要写一个接口加上注解,远程调用微服务就像调用本地方法一样丝滑。
Day 1 总结: 虽然大部分时间都在和 Mac 的环境作斗争,但当看到服务成功注册进 Nacos,且 OpenFeign 调用跑通的那一刻,一切折腾都值了。
Day 2:拒绝“屎山”,OpenFeign 的性能进阶与架构整顿
第一回合:从“能用”到“好用”,给调用加个“加速器”
在系统学习了 OpenFeign 之后,我发现这玩意儿虽然写起来像调用本地方法一样爽,但默认的 HttpURLConnection 实在是有点“老古董”,不支持连接池,每次请求都要新建连接,在高并发面前简直是性能瓶颈。
- 反手就是一个 OkHttp: 毫不犹豫,直接把底层换成了
OkHttp。引入依赖、开启配置,一套连招下来,成功给微服务间的通信加了层“性能 Buff”。
第二回合:架构大整顿,API 模块的“大挪移”
随着模块(Trade、Pay、Cart)越写越多,我发现很多查询逻辑极其重复。作为一个有追求的后端,我坚决不能忍受重复代码。
- 重构思路: 抽出一个独立的
hmall-api模块,专门存放 Feign 客户端和通用的 DTO。 - 避坑指南: 以后哪个模块要用,直接在
pom.xml里引入这个api依赖即可。不过这里有个细节:引入依赖后,记得在启动类上手动扫描 Feign 客户端所在的包,否则 Spring 找不到这些 Bean。
第三回合:被 IDEA “背刺”的作业时刻
下午写老师布置的作业时,真正的折磨来了。我陷入了无限的 Debug 循环,查了半天逻辑都没问题,结果发现竟然是 IDEA 自动导包导错了!
- 血泪教训: 有时候“人工智能”也会变成“人工智障”。在微服务多模块环境下,很多类名可能重复(比如不同包下的
Order),IDEA 默认选的那个可能根本不是你要的。 - 最终绝招: 删掉自动导入,手动逐个 Import。虽然慢一点,但胜在稳准狠。从此以后,我再也不敢百分之百信任 Alt+Enter 了。
技术答疑:用户 ID 怎么在组件间传递?
这就是分布式系统中的 “用户信息透传” 。通常我们不会在每个接口参数里都写个 userId,那样太丑了。
主流方案:ThreadLocal + Feign 拦截器
- 网关层解析: 用户登录后,Gateway 校验 JWT,解析出
userId,然后放入 HTTP Header(比如叫user-info)转发给下游。 - 服务拦截(ThreadLocal): 在微服务里写一个
HandlerInterceptor,从 Header 拿到userId,存入ThreadLocal(通常封装成一个UserContext类)。这样你在当前服务的任何地方都能直接通过UserContext.get()拿到 ID。 - Feign 透传(核心): 当 A 服务要调用 B 服务时,
ThreadLocal里的 ID 是传不过去的。这时候需要实现 Feign 的RequestInterceptor接口。
Day 2总结 虽然被导包折磨了半天,但搞定了 API 抽取和 OkHttp 之后,感觉整个项目的骨架清晰了很多。至于 userId 的传递,明天实操一下拦截器,这篇微服务总结就算齐活了!
这一天的经历简直是微服务学习路线上的**“高潮”**!你不仅完美闭环了 Day 2 留下的“用户 ID 怎么传递”的悬念,还一头撞上了底层原理的“叹息之墙”,最后又在 Nacos 那里体验到了架构瘦身的爽感。
你觉得 Gateway 和 Feign 拦截器难,这绝对不是你的问题! 因为 Spring Cloud Gateway 底层用的是 WebFlux(响应式编程),里面充斥着 ServerWebExchange 这种你以前在传统 JavaWeb 里根本没见过的对象。没学过底层原理直接盘它,确实就像在听天书。
这段经历写进博客绝对是整篇文章的**“灵魂所在”**(极具真实感)。我帮你把这段“渡劫”的经历进行了梳理和拔高,加入了底层逻辑的解释,让它看起来既真实又有技术深度:
Day 3:网关地狱与 Nacos 瘦身大法,底层原理的“降维打击”
如果说前两天的痛点在于环境配置,那第三天就是纯粹的代码与底层原理的脑力激荡。
第一回合:初探 Gateway,跌入“网关鉴权地狱”
一开始接触网关(Gateway),觉得还挺简单:不就是个大门卫嘛,配置一下路由(Route)和基本的过滤规则就能跑。但当真正的业务需求—— “网关统一校验 JWT 登录状态” 摆在面前时,地狱模式开启了。
- 过滤器大挑战: 为了在请求到达微服务前完成校验,我深入研究了 Gateway 的
Filter机制。我们将校验逻辑写在了GatewayFilter的pre阶段。校验逻辑本身不难,解析 JWT 就行。 - 令人头秃的头信息传递: 难点在于,网关解析出用户 ID 后,怎么塞给下游微服务?这里遇到了一堆底层对象。由于 Gateway 底层是 WebFlux 架构,不能像以前那样直接改 HttpServletRequest,必须得通过
ServerWebExchange拿到 request 对象,再mutate()(变异)出一个包含user-info请求头的新对象往下传。一番折腾,总算是把用户信息塞进 Header 里了!
第二回合:OpenFeign 拦截器,与底层原理的“相爱相杀”
网关把 ID 传给了第一个微服务(比如 Trade),Trade 的拦截器顺利拿到了 ID。但当 Trade 还要通过 OpenFeign 调用 Pay 服务时,Header 里的 ID 又丢了!这就是我昨天留下的疑问,今天终于破案了。
- 解决方案: 需要实现一个
RequestInterceptor,在里面使用RequestTemplate重新把user-info塞进请求头里,然后通过DefaultFeignConfig让它生效。 - 破防时刻: 代码虽然抄...哦不,写出来了,但老师讲到这背后的 Spring Boot 自动装配原理和大量的 SpringMVC 底层逻辑时,我听得直接戴上了痛苦面具。毕竟我没系统学过 SpringMVC,以前学的 JavaWeb 自动配置也快忘光了。在这里奉劝各位掘友:出来混,底层原理迟早是要还的,有空必须得补补 Spring 源码!
第三回合:Nacos 配置中心,application.yaml 的“极限瘦身”
带着被底层原理蹂躏的疲惫,我开启了下午的 Nacos 配置管理学习。随着微服务变多,我发现每个服务的 jdbc、日志、swagger、OpenFeign 配置全是一模一样的废话,改一个地方要动全身。
- 配置抽取与动态覆盖: 我们把这些公共配置全部抽离到了 Nacos 里。这里学到了一招极其优雅的写法:
${hm.db.host:192.168.150.101}。代码里不写死 IP,而是给个默认值,同时允许通过 Nacos 动态注入${hm.db.host}来覆盖它,灵活性拉满! - 鸡生蛋还是蛋生鸡? 这里踩了个认知坑:微服务启动需要去 Nacos 拉配置,但拉配置的前提是得知道 Nacos 的地址(路由)。这可咋办?
- 答案是
bootstrap.yaml: 引入这个优先级极高的配置文件,专门用来存放 Nacos 的地址。程序启动时,先读bootstrap.yaml连上 Nacos,拉取公共配置,然后再和本地的application.yaml合并。 - 战果: 看着原本又长又臭的
application.yaml瞬间瘦身成短短几行,那种架构解耦的爽感,真是一扫之前的阴霾。
Day 3 结语(清明假期总结): 正准备继续往“动态配置”和“动态路由”深挖的时候,晚自习的铃声响了,我的清明三天微服务“闭关”也暂告一段落。
回顾这三天,从放弃老旧前端、死磕 Mac 环境,到重构 API 模块、玩转网关拦截,再到 Nacos 的配置瘦身。虽然中途被环境报错和底层原理折磨得不轻,但每一次成功启动、每一次接口调通,都让我深刻感受到了后端架构的魅力。
微服务的路还很长,明天,我们再战!大家如果也在踩同样的坑,欢迎在评论区抱团取暖~