Spring Cloud微服务入门:从被逼重构到真香现场

极客生活家
2026-01-02 22:50
阅读 291

去年十一月,我们组接了个“祖传单体”重构的活儿。那系统上线快五年了,代码量超过三十万行,启动时间三分钟起步,每次发布都得挑凌晨三点——因为只有那时候用户最少,炸了也不至于上热搜。

作为刚入职三个月、还在试用期的小透明,我本来以为自己就是个打杂的,顶多修修前端动画卡顿的问题(毕竟简历上写了“熟悉交互优化”)。结果项目经理拍着我肩膀说:“小李啊,你不是研究过开源项目源码嘛?这次微服务拆分,你来搭基础框架!”

我当时心里一万只羊驼奔腾而过。我研究的是 Vue 的 transition 源码,不是 Spring Cloud 啊!但转念一想,我在上一家公司干了三年多,早就想换个环境了。要是能在这次重构里打出成绩,说不定转正后还能谈个好薪资,甚至为跳槽攒点硬核履历。

于是,我一边在深夜翻 Spring Cloud 官方文档,一边在 Slack 里疯狂请教隔壁 Java 组的老哥。这篇教程,就是我踩坑、掉头发、debug 到凌晨四点换来的血泪总结。如果你也正准备从零开始搞微服务,希望它能帮你少走点弯路。


为什么非得上微服务?

先说清楚背景。我们那个老系统,典型的“大泥球”架构:所有业务模块塞在一个 Spring Boot 应用里,MySQL 单库单表撑起整个平台。最离谱的是,一个商品详情页的接口,居然要查七八张表,连用户积分、优惠券、物流状态全塞进去——产品经理说“用户不想跳转”,我们就只能把逻辑全堆后端。

结果呢?

  • 部署一次要 15 分钟,测试环境经常挂
  • 一个模块出 bug,整个系统崩盘
  • 新人来了三个月还搞不清代码结构

运维老王每次看我们部署都摇头:“你们这哪是发版,这是给服务器做心肺复苏。”

所以,微服务不是为了追时髦,而是被逼到墙角了。我们决定按业务域拆:用户中心、商品服务、订单系统、支付网关……每个服务独立开发、部署、扩缩容。


技术选型:别被“全家桶”忽悠了

很多人一提 Spring Cloud,就直接上 Eureka + Ribbon + Feign + Hystrix + Zuul + Config Server……仿佛不装满就不叫微服务。但现实是:过度设计比不做设计更可怕

我们团队开了三次技术评审会,最后定了个“最小可行微服务栈”:

组件 候选方案 最终选择 理由
服务注册与发现 Eureka / Consul / Nacos Nacos 支持 AP/CP 切换,带配置中心,国产文档友好
服务调用 Feign / RestTemplate / WebClient OpenFeign + Resilience4j Feign 声明式调用简洁,Hystrix 已停更,Resilience4j 更轻量
API 网关 Zuul / Spring Cloud Gateway Spring Cloud Gateway 基于 WebFlux,性能高,支持动态路由
配置管理 Spring Cloud Config / Nacos Nacos 和注册中心统一,避免多组件维护
分布式追踪 Sleuth+Zipkin / SkyWalking SkyWalking 可视化强,自动探针,对代码侵入小

这里特别吐槽一下 Eureka。虽然它是 Netflix 出品,但社区活跃度越来越低,而且只支持 AP 模式。我们有一次网络抖动,Eureka 服务列表半天不同步,差点导致线上雪崩。换成 Nacos 后,可以在 CP(强一致)和 AP(高可用)之间切换,灵活多了。

另外,千万别迷信“官方推荐”。比如 Spring Cloud 官方早就不维护 Hystrix 了,但很多教程还在教。我们一开始照着老教程集成,结果发现断路器不生效,查了一天才发现版本冲突。后来果断切到 Resilience4j,代码清爽,熔断、限流、重试一把梭。


动手搭建:从第一个服务开始

第一步:注册中心 + 配置中心(Nacos)

先拉起 Nacos。本地开发用 Docker 最方便:

docker run -d \
  --name nacos-standalone \
  -e MODE=standalone \
  -p 8848:8848 \
  nacos/nacos-server:2.2.3

然后创建两个服务:user-serviceorder-service

user-servicebootstrap.yml 里配置:

spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml

注意:必须用 bootstrap.yml 而不是 application.yml!因为 Nacos 配置需要在应用启动前加载。我第一次就搞错了,服务死活连不上配置中心,还以为是网络问题,重启了十几次 Docker……

第二步:服务间调用(OpenFeign + Resilience4j)

order-service 里调用用户信息:

@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
    @GetMapping("/users/{id}")
    User getUser(@PathVariable("id") Long id);
}

@Component
public class UserClientFallback implements UserClient {
    @Override
    public User getUser(Long id) {
        // 熔断降级返回兜底数据
        return new User().setName("默认用户");
    }
}

然后加上 Resilience4j 的配置(application.yml):

resilience4j:
  circuitbreaker:
    instances:
      userClient:
        failure-rate-threshold: 50
        wait-duration-in-open-state: 5s

这里有个坑:Feign 默认不启用熔断!必须在启动类加 @EnableFeignClients,并在配置里开启:

feign:
  circuitbreaker:
    enabled: true

否则 fallback 根本不会触发。我调试时模拟超时,结果服务直接 500,吓得以为 Resilience4j 不工作,其实只是没开开关……

第三步:API 网关(Spring Cloud Gateway)

前端不能直接调后端服务,得走网关。配置路由:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**

注意 lb:// 前缀,表示使用负载均衡。Gateway 会自动从 Nacos 拿服务列表。

我们还加了个全局过滤器,统一处理 JWT 鉴权:

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
}

上线第一天,测试同学反馈“所有接口 401”。我一查,原来是前端把 token 放在了 token 字段,而不是 Authorization 头里……和产品确认后才发现,PRD 写的是“Header 传 token”,但前端理解成了自定义字段。这种跨团队沟通的锅,后端背得最多。


生产环境那些“惊喜”

微服务上线后,并没有一帆风顺。分享几个真实事故:

1. 配置热更新失效

Nacos 支持配置动态刷新,但我们的 @RefreshScope 注解没生效。原因是:只有被 Spring 管理的 Bean 才能刷新。我们有个工具类用了 static 方法读配置,死活不更新。后来改成注入 @Value + @RefreshScope 的 Service,才解决。

2. 网关内存泄漏

Gateway 在高并发下 OOM。查了 SkyWalking 发现,每秒几千请求堆积在 Netty 的 Channel 里。原因是后端服务响应慢,网关连接池满了。解决方案:

  • 调大 spring.cloud.gateway.httpclient.max-pool-size
  • 给后端加超时:feign.client.config.default.read-timeout=3000

3. 分布式事务翻车

订单创建要扣库存、发消息、记日志。一开始用 @Transactional,结果服务一拆,事务跨不了 JVM。最后上了 Seata 的 AT 模式,但又遇到主键回滚问题……现在想想,其实简单场景用“最终一致性 + 补偿机制”更稳。


微服务不是银弹,但值得折腾

折腾三个月后,我们的新架构终于跑起来了:

  • 服务启动从 3 分钟降到 15 秒
  • 发布频率从月更变成周更
  • 单个服务崩溃不影响全局

最重要的是,我这个试用期员工,居然成了微服务落地的关键人物。上周五团建,CTO 还特意过来敬酒:“小李,干得不错,转正报告我批了。”

当然,我也深知微服务带来的复杂性:监控要跟上、日志要聚合、链路要追踪。但正如一位前辈说的:“单体应用的痛是钝刀子割肉,微服务的痛是手术刀划开——前者慢性死亡,后者阵痛重生。

如果你也在考虑微服务,我的建议是:

  • 先问清楚业务是否真的需要拆
  • 从核心链路开始,别一上来就全拆
  • 监控和治理能力要同步建设
  • 别怕踩坑,每个坑都是简历上的亮点

最后,这篇后端教程写得有点啰嗦,但全是实战血泪。希望你在跳槽或晋升的路上,能少熬几个夜,少砸几台电脑。

对了,我现在已经在更新简历了——“主导 Spring Cloud 微服务架构落地”这条,应该能帮我拿到下家的面试机会吧?

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝