Spring Cloud从零开始:微服务入门指南

代码里的小宇宙
2025-12-13 19:55
阅读 471

上周五晚上,我坐在深圳南山科技园的办公室里,窗外是熟悉的腾讯滨海大厦灯光。时间刚过9点,工位上还剩我和两个后端兄弟——没错,又是被线上告警拉回来救火的一天。事情起因是某个老单体应用在大促期间直接崩了,CPU飙到100%,数据库连接池打满。运维小哥在群里@我们:“兄弟们,这玩意儿再挂一次,明天老板就要请大家喝喜茶(其实是HR约谈)了。”

那一刻,我这个前端老油条突然意识到:不懂微服务,迟早要被时代甩下车

虽然我主攻前端,但咱公司最近搞“全栈能力建设”,领导说“前端也要懂后端架构”。再加上最近在研究Rust(对,就是那个号称内存安全又高性能的新贵),让我对系统级设计越来越感兴趣。于是,我决定从头啃一啃Spring Cloud——毕竟,咱公司后端全是Java系,而Spring Cloud几乎是国内微服务的事实标准。


为啥不是Go或Python?

有朋友可能会问:现在不是流行用Go写微服务吗?Python不也能搞?我一开始也这么想。

但现实很骨感。咱们二线互联网公司,技术栈切换成本极高。你让一个跑了5年的Java单体应用,说切就切到Go,DBA第一个不同意。而且我们团队后端清一色Java老兵,Go对他们来说等于重新学语言,PM听了都摇头:“排期?至少半年。”

所以,务实的选择是:在现有Java生态里升级架构。Spring Boot + Spring Cloud 这套组合拳,学习曲线平缓,社区成熟,文档齐全,还有大量现成的starter——对我们这种既要快速上线又要稳如老狗的团队,简直是救命稻草。

顺便提一句,最近面试好几个候选人,问到微服务拆分原则、服务注册发现机制,一半人支支吾吾。看来“微服务”这三个字,真成了高频面试题


从单体到微服务:我的“血泪”拆分实践

我们的老系统叫 order-center,集成了订单创建、支付回调、库存扣减、物流跟踪……所有功能塞在一个WAR包里。每次发版,测试都要跑300+用例,上线窗口必须卡在凌晨2点。

目标很明确:拆!

第一步:识别边界上下文

我拉着后端大佬和产品开了三天会(期间喝了8杯瑞幸),用DDD的思想画出业务子域:

  • 订单服务(Order Service):管创建、查询、状态机
  • 库存服务(Inventory Service):管商品库存扣减与回滚
  • 支付服务(Payment Service):对接第三方支付,处理异步回调

每个服务独立数据库,彻底解耦。这里有个坑:不要按技术层拆,要按业务能力拆!曾经有新人把“所有DAO”抽成一个服务,结果调用链爆炸,事务无法保证。

第二步:搭建Spring Cloud骨架

我们选了Spring Boot 2.7 + Spring Cloud 2021.0.5(兼容性好,别问我怎么知道的,踩过版本地狱的坑)。

核心组件清单:

组件 作用 替代方案
Eureka 服务注册与发现 Consul, Nacos
Feign 声明式HTTP客户端 RestTemplate, WebClient
Ribbon 客户端负载均衡 已集成进Feign
Hystrix 熔断降级 Resilience4j(新项目推荐)
Config Server 配置中心 Apollo, Nacos

注:Hystrix官方已停止维护,但老项目还在用。新项目建议上Resilience4j + Micrometer监控。

搭建Eureka Server(服务注册中心)

# eureka-server/src/main/resources/application.yml
server:
  port: 8761

eureka:
  client:
    register-with-eureka: false  # 自己不注册自己
    fetch-registry: false       # 不拉取注册表
  server:
    enable-self-preservation: false  # 关闭自我保护(开发环境)

启动后访问 http://localhost:8761,看到那个熟悉的红色警告页面——别慌,这是Eureka的默认UI。

订单服务注册自己

# order-service/application.yml
spring:
  application:
    name: order-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

加上注解:

@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

启动后,刷新Eureka页面,看到 ORDER-SERVICE 出现在Instances列表里——那一刻,我仿佛看到了微服务世界的曙光。


服务间调用:Feign真香!

以前在单体里,调库存直接@Autowired InventoryDao。现在库存是独立服务,怎么办?

答案:Feign + 接口定义

先在 inventory-service 里暴露REST API:

@RestController
@RequestMapping("/api/inventory")
public class InventoryController {

    @PostMapping("/deduct")
    public ResponseEntity<Boolean> deduct(@RequestBody DeductRequest request) {
        // 扣减逻辑
        return ResponseEntity.ok(inventoryService.deduct(request.getProductId(), request.getAmount()));
    }
}

然后在 order-service 中定义Feign Client:

@FeignClient(name = "inventory-service", fallback = InventoryClientFallback.class)
public interface InventoryClient {

    @PostMapping("/api/inventory/deduct")
    Boolean deduct(@RequestBody DeductRequest request);
}

别忘了开启Feign:

@EnableFeignClients(basePackages = "com.example.order.client")

调用时直接注入:

@Service
public class OrderService {

    @Autowired
    private InventoryClient inventoryClient;

    public void createOrder(Order order) {
        // ...校验、保存订单
        boolean success = inventoryClient.deduct(new DeductRequest(order.getProductId(), order.getAmount()));
        if (!success) {
            throw new InsufficientStockException("库存不足");
        }
    }
}

注意:这里有个经典坑——Feign默认不支持传递请求头(比如用户Token)。解决方案是在配置里加 RequestInterceptor,否则下游服务拿不到认证信息,直接403。


熔断与降级:别让雪崩毁掉整个系统

去年双11,支付服务因为第三方接口超时,导致订单服务线程池被打满,最后整个系统雪崩。当时真的想砸电脑。

这次我们上了Hystrix熔断:

@Component
public class InventoryClientFallback implements InventoryClient {

    @Override
    public Boolean deduct(DeductRequest request) {
        // 降级策略:记录日志 + 返回假成功(后续走补偿任务)
        log.warn("Inventory service unavailable, fallback triggered for product: {}", request.getProductId());
        return true; // 注意:这里只是示例,实际需谨慎设计
    }
}

同时配置超时和熔断阈值:

feign:
  hystrix:
    enabled: true

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000  # 超时3秒
      circuitBreaker:
        requestVolumeThreshold: 10      # 10秒内10次失败就熔断
        errorThresholdPercentage: 50    # 错误率50%触发熔断

生产建议:降级逻辑一定要幂等且可追溯!我们后来加了“库存补偿任务”,定时扫描异常订单,人工介入处理。


配置中心:告别硬编码

以前改个数据库密码,要重新打包部署。现在用Config Server,配置集中管理。

# config-server/application.yml
spring:
  cloud:
    config:
      server:
        git:
          uri: https://gitlab.company.com/config-repo
          username: ${GIT_USER}
          password: ${GIT_PASSWORD}

各服务通过 bootstrap.yml 拉取配置:

# order-service/bootstrap.yml
spring:
  application:
    name: order-service
  cloud:
    config:
      uri: http://config-server:8888

这样,改个Redis地址,只需推一下Git,服务自动刷新(配合@RefreshScope)。


性能与监控:微服务不能只靠“感觉”

拆完服务,性能到底好不好?光看TPS不够,得看调用链

我们接入了SkyWalking:

  • 自动追踪Feign调用
  • 可视化服务拓扑
  • 慢接口告警

有一次发现订单创建平均耗时800ms,查调用链发现是库存服务查DB慢。原来是没加索引!赶紧让DBA加了个 (product_id, warehouse_id) 联合索引,耗时降到80ms。

微服务时代,可观测性就是生命线


综合对比:微服务 vs 单体

维度 单体应用 微服务
开发效率 初期快,后期慢 初期慢,后期快
部署频率 低(全量发布) 高(独立发布)
故障隔离 差(一处崩全挂) 好(熔断限流)
技术栈 单一 多样(但咱没敢用)
运维复杂度 高(需配套监控/日志)

对我们这种中型团队,微服务是“不得不为”的选择——业务增长太快,单体已经扛不住了。


写在最后:前端视角的反思

作为前端,深入后端微服务架构后,我反而更理解API设计的重要性了。以前总吐槽后端接口字段命名不规范、没文档,现在自己参与设计,才知道好的接口契约是前后端协作的基石

另外,微服务不是银弹。如果你的业务还没到一定规模,强行拆分只会增加复杂度。记住那句老话:“当你的单体应用还能跑,就别急着微服务。”

不过话说回来,要是哪天公司真用Go重写核心服务……我是不是该把Rust先放一放,去学Gin框架了?🤔


附:避坑清单(血泪总结)

  • ❌ 不要在Feign调用里传大对象(容易OOM)
  • ❌ 服务间不要循环依赖(A调B,B又调A)
  • ✅ 所有对外接口必须有版本号(/v1/order
  • ✅ 数据库事务只能在单个服务内保证,跨服务用Saga或TCC
  • ✅ 日志必须带TraceID,否则排查问题像大海捞针

好了,今天就写到这儿。凌晨1点了,该回家了。希望这篇实战笔记能帮到正在微服务路上挣扎的你。

P.S. 如果你们公司还在用Dubbo,评论区聊聊?我也好奇阿里系是怎么玩的。

评论 0

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