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

数据迁移苦工
2025-12-15 08:19
阅读 223

凌晨2点,深圳南山科技园的写字楼里,还有几盏灯没灭。我叼着半包快过期的雀巢咖啡糖,盯着屏幕上密密麻麻的日志——又一个服务调用超时了。这已经是本周第三次线上告警,而明天就是产品上线deadline,PM已经在群里@我三次:“哥,稳住啊!”

我是创业公司里那种“全栈开发”,说好听点是技术多面手,说难听点就是“啥都得干、啥都得会一点”。前端崩了?我上。数据库慢?我查。K8s部署失败?还是我来。上周五晚上,老板突然把我叫到会议室,扔下一句:“咱们单体应用撑不住了,下个版本必须上微服务。”我当时差点把手中的枸杞茶喷出来——这不是逼我现学Spring Cloud吗?

但没办法,创业公司节奏快,资源少,人手紧。你不往前冲,没人替你扛。于是,熬了几个通宵,踩了一堆坑,终于把第一个微服务架子搭起来了。今天这篇文,就当是给同样被“赶鸭子上架”的兄弟们留个路标——Spring Cloud从零开始,其实没那么玄乎


单体之痛:不是想微,而是不得不微

我们原来的系统是标准的Spring Boot单体应用:一个application.jar打天下,前后端分离,MySQL + Redis + RabbitMQ,看着挺美。可随着业务量猛增(去年双11期间QPS直接翻了5倍),问题全暴露了:

  • 一个模块出Bug,整个服务挂掉;
  • 发布一次要停机10分钟,运营天天投诉;
  • 新功能开发互相阻塞,前端等后端,后端等DBA;
  • 最致命的是——性能瓶颈没法局部优化。比如商品查询慢,你总不能为了它把整个应用重构一遍吧?

运维老王有次在晨会上吐槽:“你们这代码,跟一锅粥似的,改一行,全家抖三抖。” 虽然扎心,但确实是事实。

于是,微服务成了唯一出路。而Spring Cloud,作为国内最主流的微服务解决方案(尤其在深圳这片腾讯系扎堆的地儿,连楼下咖啡店老板都知道Eureka是干啥的),自然成了首选。


技术选型:别一上来就搞全套

很多教程一上来就给你甩Nacos + Gateway + Sentinel + Seata + Sleuth……兄弟,咱是创业公司,不是阿里云实验室!微服务不是堆组件,而是解决问题

我最终定了一个“最小可行微服务架构”(MVP版):

组件 作用 是否必要
Spring Boot 基础框架 ✅ 必须
Eureka 服务注册与发现 ✅ 核心
Feign 声明式HTTP调用 ✅ 简化通信
Hystrix(或Resilience4j) 熔断降级 ⚠️ 可选但推荐
Config Server 配置中心 ❌ 初期可用本地配置

注:虽然Spring官方已将Hystrix标记为维护状态,但在小团队里,它的学习成本低、效果立竿见影。等业务稳定了再迁移到Resilience4j也不迟。


动手实操:从两个服务开始

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

新建一个Spring Boot项目,加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

主启动类加注解:

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

配置文件 application.yml

server:
  port: 8761

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

启动后访问 http://localhost:8761,看到那个熟悉的蓝色界面,心里踏实了一半。


Step 2:拆出第一个业务服务(比如 user-service)

再建一个Spring Boot项目,引入:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

配置 application.yml

spring:
  application:
    name: user-service  # 服务名,关键!

server:
  port: 8081

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/  # 指向注册中心

写个简单Controller:

@RestController
public class UserController {

    @GetMapping("/user/{id}")
    public Map<String, Object> getUser(@PathVariable Long id) {
        // 实际应查DB,这里简化
        return Map.of("id", id, "name", "张三", "service", "user-service");
    }
}

启动后刷新Eureka页面,看到 USER-SERVICE 出现在Instances列表里——搞定!


Step 3:服务间调用(Feign)

现在假设有个 order-service 需要调用 user-service 获取用户信息。

先在 order-service 中引入Feign:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动类加 @EnableFeignClients

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

定义Feign接口:

@FeignClient(name = "user-service")  // 对应user-service的spring.application.name
public interface UserClient {

    @GetMapping("/user/{id}")
    Map<String, Object> getUser(@PathVariable("id") Long id);
}

在OrderController中注入使用:

@RestController
public class OrderController {

    @Autowired
    private UserClient userClient;

    @GetMapping("/order/{orderId}")
    public Map<String, Object> getOrder(@PathVariable String orderId) {
        Map<String, Object> user = userClient.getUser(123L);
        return Map.of("orderId", orderId, "user", user);
    }
}

注意:Feign默认用Ribbon做负载均衡,如果你只部署了一个user-service实例,它也会正常工作。但一旦你扩缩容,Feign+Ribbon会自动选择可用实例——这才是微服务的威力。


性能优化:别让微服务变“微龟速”

微服务拆分后,网络调用次数暴增。原来一次SQL搞定的事,现在可能要跨3个服务。我在压测时就发现,一个订单创建接口平均耗时从80ms飙升到320ms!

怎么办?几点经验:

  1. 合理聚合接口:避免“贫血模型”式调用。比如不要连续调用 /user/{id}/user/{id}/profile/user/{id}/wallet,而是提供一个 /user/{id}/full 接口。
  2. 异步化非关键路径:发短信、写日志这类操作,扔到MQ里异步处理。
  3. 缓存前置:在API Gateway层或Feign Client前加一层本地缓存(Caffeine),对读多写少的数据效果显著。
  4. 连接池调优:Feign底层用HttpURLConnection,性能一般。建议替换为OkHttp或Apache HttpClient,并调整连接池大小。
# feign + okhttp 示例
feign:
  httpclient:
    enabled: false
  okhttp:
    enabled: true

okhttp:
  connection-pool:
    max-idle-connections: 200
    keep-alive-duration: 300s

熔断降级:别让一个服务拖垮全家

曾经有一次,user-service因为DB慢查询卡死,导致order-service所有线程都在等它响应,最后整个系统雪崩。当时真的想砸电脑。

于是赶紧加上Hystrix熔断:

@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
    // ...
}

@Component
public class UserClientFallback implements UserClient {
    @Override
    public Map<String, Object> getUser(Long id) {
        // 返回兜底数据,避免级联失败
        return Map.of("id", id, "name", "未知用户", "fallback", true);
    }
}

同时开启Hystrix:

feign:
  hystrix:
    enabled: true

虽然简单,但在关键时刻能保命。后续我们会迁移到Sentinel,更细粒度的流控和熔断策略更适合生产环境。


数据库设计:每个服务独享DB

这点很多人忽略!微服务的核心原则之一是数据隔离。别再让多个服务共用一个MySQL库了。

我们拆分时做了:

  • user-service → user_db
  • order-service → order_db
  • product-service → product_db

跨服务数据一致性怎么办?初期用最终一致性 + 补偿机制。比如创建订单时,先扣库存(product-service),再创建订单(order-service)。如果第二步失败,发一个“回滚库存”消息到MQ,由product-service消费并恢复库存。

当然,这会带来复杂度。但比起强一致性的分布式事务(比如Seata),这种方案更轻量、更适合创业公司。


运维经验:微服务不是“部署完就完事”

上线后才发现,微服务对运维要求更高:

  • 日志分散:每个服务独立日志,排查问题得grep半天。解决方案:ELK统一收集,加TraceID透传。
  • 配置管理:不同环境(dev/test/prod)配置不同。初期用profile区分,后期上Apollo或Nacos Config。
  • 健康检查:必须暴露 /actuator/health,配合K8s Liveness Probe自动重启异常Pod。

我们在Jenkins里加了个脚本,每次发布自动检查所有服务的健康端点,有一个挂了就中断发布——再也不用半夜被电话叫醒救火了。


总结:微服务不是银弹,但值得尝试

折腾了一个月,我们的系统终于从“巨石”变成了“乐高”。虽然初期投入大、调试复杂,但好处也实实在在:

  • 用户服务升级,不再影响订单流程;
  • 商品模块性能优化,独立压测、独立扩容;
  • 新人入职,只需熟悉1-2个服务,上手快。

最重要的是——系统有了弹性。流量来了,我只扩瓶颈服务;出Bug了,只回滚相关模块。这种掌控感,在单体时代是不敢想的。

所以,如果你也在创业公司,被老板“逼”着上微服务,别慌。从两个服务开始,用Spring Boot打底,Spring Cloud搭桥,一步步来。微服务不是目的,敏捷交付和系统稳定才是

对了,刚写完这篇文章,PM又在群里@我:“那个……能不能顺便把支付模块也拆一下?”
我默默关掉IDE,打开招聘软件——或许,是时候招个专职后端了?

(完)

评论 0

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