从零开始搭建微服务:我的Spring Cloud实战之路

周志华
2025-06-16 22:43
阅读 202

开篇:为什么是 Spring Cloud?

开篇:为什么是 Spring Cloud?

事情得从去年初说起。那时候我们团队负责一个电商平台的重构项目,原本的单体架构已经扛不住业务增长了。订单服务、用户服务、库存服务混在一起,代码臃肿,部署缓慢,出问题时定位困难。面对越来越高的并发请求和更频繁的需求变更,我们意识到必须拆分服务、拥抱微服务架构。

作为后端负责人,我带着团队开始了微服务化改造,而我们的选择正是 Spring Cloud

为什么选它?因为它生态完善、社区活跃、文档齐全,而且团队成员大多熟悉 Spring Boot,转型成本低。更重要的是,我们希望快速搭建起一套高可用、易维护的服务架构,而不是重新造轮子。


背景与挑战:从0到1搭建微服务体系

背景与挑战:从0到1搭建微服务体系

项目背景

这次重构的目标是将原有的大单体拆分成四个核心服务:

  • 用户服务(User Service)
  • 商品服务(Product Service)
  • 订单服务(Order Service)
  • 库存服务(Inventory Service)

这些服务之间存在复杂的调用关系,比如下单需要查询商品信息、扣减库存、记录订单、通知用户等操作,同时还要考虑认证、限流、日志追踪等问题。

初期挑战

  1. 如何管理多个服务之间的通信?
  2. 如何统一配置和服务发现?
  3. 如何实现统一的权限控制和API网关?
  4. 开发效率如何保证?调试是否复杂?
  5. 生产环境如何运维?服务挂掉怎么办?

这些问题如果不解决好,微服务反而会成为“分布式地狱”。


解决方案:基于 Spring Cloud 的微服务架构搭建

解决方案:基于 Spring Cloud 的微服务架构搭建

数据库设计模型-2

我们选择了 Spring Cloud Alibaba 套件作为基础技术栈,因为它在国内生态中非常成熟,配合 Nacos 做注册中心和配置中心特别顺手。

整体架构图大致如下:

+-----------------+     +--------------------+
| API Gateway     | --> | Authentication     |
| (Spring Cloud   |     | (OAuth2/JWT)       |
| Gateway + JWT)  |     +--------------------+
+-----------------+
        |
        v
+-----------------+     +------------------+
| Service A       | <-> | Service B        |
| User Service    |     | Product Service  |
+-----------------+     +------------------+
        |                     |
        v                     v
+---------------------------------------------+
| Common Component                            |
| - FeignClient / LoadBalancer / Config/Nacos |
| - Sleuth/Zipkin                             |
| - Sentinel                                   |
+---------------------------------------------+

这套体系帮助我们很好地解决了上述几个问题。


实践细节:一步步搭建起来的微服务架构

第一步:搭建服务注册中心 —— Nacos

我们选用 Nacos 作为注册中心和配置中心。它是阿里巴巴开源的一个动态服务发现、配置管理和服务管理平台,非常适配 Spring Cloud Alibaba。

在启动类上加个 @EnableDiscoveryClient 注解,服务就能自动注册到 Nacos:

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

application.yml 中配置 Nacos 地址即可:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

这样,其他服务就可以通过服务名来调用你的服务,比如:

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    // 其他服务调用这个接口时,只需要传入服务名即可
    return userService.getUserById(id);
}

第二步:服务间通信 —— OpenFeign + Ribbon

为了让服务之间能相互调用,我们选择了 OpenFeign 搭配 Ribbon 做客户端负载均衡。

定义一个 FeignClient 接口就完成了远程调用:

@FeignClient(name = "product-service")
public interface ProductServiceClient {
    @GetMapping("/products/{id}")
    Product getProductById(@PathVariable("id") Long id);
}

然后直接注入使用即可:

@Autowired
private ProductServiceClient productServiceClient;

@GetMapping("/order/{id}")
public OrderDetail getOrderDetail(@PathVariable Long id) {
    Order order = orderService.getOrderById(id);
    Product product = productServiceClient.getProductById(order.getProductId());
    return new OrderDetail(order, product);
}

是不是很简洁?不过背后也有不少踩坑的地方,后面会讲到。

第三步:统一配置管理 —— Nacos Config

为了方便维护不同环境的配置,我们把所有配置都迁移到了 Nacos Config 上。

在 Spring Boot 项目中引入依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

然后在 bootstrap.yml 中指定 config server 地址:

spring:
  application:
    name: user-service
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        extension-configs:
          - data-id: user-service.yaml
            group: DEFAULT_GROUP
            refresh: true

这样我们就可以在 Nacos 页面上管理每个服务的 YAML 配置,并且可以区分 dev/test/prod 环境。

第四步:API 网关 —— Spring Cloud Gateway + JWT

网关是对外暴露服务的统一入口,我们用了 Spring Cloud Gateway,并集成了 JWT 进行身份验证。

关键配置如下:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - AuthFilter

自定义了一个 AuthFilter,用于校验 Token 是否合法:

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private final JwtUtil jwtUtil;

    public AuthFilter(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = extractToken(exchange.getRequest());

        if (token == null || !jwtUtil.validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }

    // 提取token的方法略...
}

第五步:链路追踪 —— Sleuth + Zipkin

为了排查分布式系统中的调用问题,我们集成 Sleuth 和 Zipkin,实现了全链路追踪。

添加依赖:

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

配置 zipkin server 地址:

spring:
  zipkin:
    base-url: http://localhost:9411

启动本地 zipkin server 很简单:

docker run -d -p 9411:9411 openzipkin/zipkin

这样我们在 Zipkin UI 中就能看到完整的调用链了。

第六步:熔断限流 —— Sentinel

最后我们加了 Sentinel 来做服务熔断和流量控制,防止雪崩效应。

引入依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

然后配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080

我们还写了一个简单的 fallback 类来处理服务降级逻辑:

public class OrderServiceFallback {
    public static Order getDefaultOrder(Long id) {
        return new Order(id, 0L, 0L, BigDecimal.ZERO, "Service Unavailable");
    }
}

并在 Feign Client 中启用 fallback:

@FeignClient(name = "order-service", fallback = OrderServiceFallback.class)

这样当某服务不可用时,可以返回默认值,避免整个系统崩溃。


踩过的坑:开发过程中的真实问题与解决

坑一:FeignClient 在 Gateway 中无法使用

起初我们在 Gateway 层尝试直接使用 FeignClient 发起调用,结果一直报错,提示找不到 service instance。

后来才意识到:Gateway 不应该直接调用其他服务,而是应该通过 route 配置转发请求。

所以正确的做法是在 Gateway 内部不使用 Feign,而是在实际业务服务中使用。

坑二:Nacos 忘记配置 namespace 导致环境混乱

我们在测试环境和预发布环境共用了同一个 Nacos 服务器,结果因为没有设置 namespace,导致配置互相干扰,出了不少 bug。

教训就是:多环境部署时一定要给每个环境单独分配 namespace。

配置方法很简单,在 yaml 里加一行:

namespace: test-env-namespace

坑三:Zipkin 数据没保留,排查困难

一开始 Zipkin 用的是内存存储,默认只保存 100 条链路数据,导致很多历史问题查不到。

于是我们改成了 MySQL 存储:

docker run -d \
  -e MYSQL_HOST=localhost \
  -e MYSQL_TCP_PORT=3306 \
  -e MYSQL_USER=root \
  -e MYSQL_PASSWORD=yourpassword \
  -e STORAGE=mysql \
  -p 9411:9411 \
  openzipkin/zipkin

同时建表语句可以从官方 GitHub 获取。

这下不仅链路数据持久化了,还能通过 SQL 查询分析调用情况。

坑四:Sentinel 规则未持久化导致重启失效

Sentinel 控制台设置的限流规则如果不持久化,一旦应用重启就会丢失。

为此,我们采用了 Alibaba Sentinel 的 file 动态规则持久化机制,结合 Zookeeper 或 Apollo 可以做到集中式配置。

虽然当时时间有限,但我们还是先临时用文件保存规则:

sentinel:
  datasource:
    ds1:
      file:
        file: ./sentinel-rules.json
        data-type: json
        rule-type: flow

这样即使重启,规则也能加载回来。


效果总结:微服务改造后的收益

经过半年的微服务化实践,整个系统的架构变得更加清晰,主要收获如下:

  1. 服务解耦明显,模块职责更加清晰
  2. 部署效率提升,单个服务迭代更快
  3. 线上问题定位变得容易,通过链路追踪迅速发现问题点
  4. 通过熔断限流,稳定性显著提高,不再出现因某个服务异常导致全局瘫痪的情况
  5. 配置统一管理,环境切换更灵活,上线流程更加规范

最重要的是,团队的技术氛围和协作方式也发生了变化——大家开始真正思考服务设计、性能优化、容错机制,而不是只关注 CRUD。


经验分享:送给正在起步的你

缓存策略对比-1

如果你也正准备从头搭建一个微服务系统,这里是我的一些经验建议:

✅ 1. 技术选型要稳,不要追求新技术

Spring Cloud 生态很大,但并不意味着你要全部用上。根据实际需求,选成熟稳定的组件,例如 Nacos 比 Eureka 更适合国内场景,Sentinel 比 Hystrix 更新更活跃。

✅ 2. 微服务不是银弹,先想清楚再动

很多人认为微服务就是拆分服务,其实最难的部分是服务治理。如果没有服务发现、熔断限流、链路追踪等机制,微服务反而会更难维护。

✅ 3. 先搭基础设施,再上业务功能

建议花一周时间先把服务注册、配置中心、网关、监控等搭起来,然后再开始开发具体功能,否则后面补救成本太高。

✅ 4. 做好服务边界划分

服务划分不能过细也不能太粗,最好是按照业务领域来拆分,比如订单、用户、支付等独立服务。遵循单一职责原则,避免一个服务干太多事。

✅ 5. 多环境配置管理非常重要

推荐使用 Nacos 或 Apollo,让配置从代码中分离出来,方便管理和切换不同环境。

✅ 6. 日志、链路、监控都要有

微服务一大痛点就是看不到内部状态。因此建议尽早接入日志收集(ELK)、链路追踪(Sleuth + Zipkin)和监控报警(Prometheus + Grafana)。

✅ 7. 线上运维也要跟上

微服务上线后,运维也要相应升级。比如健康检查、自动化部署、灰度发布、滚动更新等功能都需要逐步具备。


小结与展望

这一路走下来,有困惑,有踩坑,也有成就感。从一开始对微服务概念的理解模糊,到逐渐建立起一整套可落地的架构,我深刻体会到:微服务并不是单纯的技术问题,更是工程能力、团队协作和架构思维的综合考验。

未来我们会继续往云原生方向演进,比如接入 Kubernetes、Service Mesh 等,进一步提升系统的弹性能力和自动化水平。

如果你也在微服务这条路上摸索前行,不妨停下来回顾一下自己的架构设计是否合理,是否真的服务于业务发展。别忘了,技术始终为业务服务,架构也应为团队和产品服务。

希望这篇文章能给你一些启发和参考,少走些弯路。如果你们在实践中遇到什么问题,也欢迎留言交流,我们一起成长。


如果你喜欢这样的实战分享,欢迎订阅我的公众号【码农进化论】,每周持续输出一线开发者的成长经验和技术干货。

评论 0

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