清明假期的微服务闭关

28 阅读10分钟

“各位掘友大家好!别人的清明小长假都在踏青赏花,而我的假期,是在 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 的基础命令和数据卷等知识点,篇幅有限就不在此赘述了,黑马微服务课程里讲得很透彻。)

第三回合:重头戏!单体拆分与踩坑实录

下午进入了真正的核心:从单体架构向微服务架构拆分。回想起来,这部分的报错真是五花八门。在这里给大家盘点一下拆分初期最容易踩的几个大坑(也是我被折磨过的地方):

  1. 包扫描盲区 (Bean 找不到): 单体架构时所有代码都在一个包下,Spring Boot 启动类默认就能扫到。拆分成独立的微服务(比如 item-service, order-service)后,如果公共类或者 Feign 客户端放在了别的包(比如 com.hmall.api),启动时就会报 UnsatisfiedDependencyException,必须要加 @ComponentScan@EnableFeignClients(basePackages = "...") 才能解决。
  2. 公共依赖抽取遗漏 (ClassNotFound): 把实体类(POJO)、通用工具类抽取到一个单独的 hmall-common 模块后,其他微服务的 pom.xml 里如果忘记引入这个 common 依赖,代码直接一片爆红。
  3. 配置文件的“薛定谔”: 服务拆开了,但每个服务对应的 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 拦截器

  1. 网关层解析: 用户登录后,Gateway 校验 JWT,解析出 userId,然后放入 HTTP Header(比如叫 user-info)转发给下游。
  2. 服务拦截(ThreadLocal): 在微服务里写一个 HandlerInterceptor,从 Header 拿到 userId,存入 ThreadLocal(通常封装成一个 UserContext 类)。这样你在当前服务的任何地方都能直接通过 UserContext.get() 拿到 ID。
  3. 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 机制。我们将校验逻辑写在了 GatewayFilterpre 阶段。校验逻辑本身不难,解析 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 的配置瘦身。虽然中途被环境报错和底层原理折磨得不轻,但每一次成功启动、每一次接口调通,都让我深刻感受到了后端架构的魅力。

微服务的路还很长,明天,我们再战!大家如果也在踩同样的坑,欢迎在评论区抱团取暖~