Spring Cloud 从零开始:一个真实项目中的微服务实践之路

代码评审刺客
2025-06-28 21:07
阅读 818

引言:为什么会选择用 Spring Cloud 搞微服务?

引言:为什么会选择用 Spring Cloud 搞微服务?

记得去年刚接手一个中型电商系统重构任务的时候,我整个人是懵的。原来的单体应用已经膨胀到十几个模块,打包一次要两分钟不说,部署一出错就得全站下线。团队越来越大,代码越来越乱,开发效率直线下降。我们几个核心开发在技术方案会上讨论了快一周,最终达成一致:拆!

拆成微服务。这是我们能找到的最合理也最熟悉的方案。

当时我虽然写过几年 Java 后端,但真正接触 Spring Cloud 还是第一次。为了快速上手、少走弯路,整个团队一边学习一边摸索实践。现在回过头来看这段经历,踩过的坑、吃过的苦都成了宝贵的经验。今天这篇文章,就想和大家分享我在实际项目中使用 Spring Cloud 构建微服务的心得,尤其是那些只有亲身经历过才会知道的细节和教训。

项目背景:从“单体怪兽”到微服务架构

项目背景:从“单体怪兽”到微服务架构

我们的目标是一个电商平台,包括用户中心、商品中心、订单系统、支付系统、库存系统以及运营后台等主要模块。原系统是一个典型的 Spring Boot 单体应用,所有代码在一个仓库里,通过 Maven 多模块管理。

问题很明显:

  • 部署困难:每次上线都要停机,风险极高
  • 扩展性差:某一个模块负载高了也没法单独扩容
  • 开发协同困难:多组并行开发时经常出现冲突
  • 维护成本高:日志分散,问题定位难

于是我们决定使用 Spring Cloud 来进行服务拆分,目标如下:

  1. 实现服务注册与发现
  2. 提供统一的 API 网关入口
  3. 支持配置中心统一管理
  4. 实现服务间通信与负载均衡
  5. 保证服务调用链可追踪
  6. 提升系统的可观测性和稳定性

遇到的问题和挑战

刚开始搭建微服务架构的时候,我们遇到了一系列现实问题:

问题一:服务怎么注册和发现?

一开始我们试用了 Eureka,后来尝试了 Consul、Zookeeper 和 Nacos。Nacos 是最终的选择,因为它不仅支持服务注册发现,还自带配置中心,这对运维同学来说很友好。

问题二:服务之间怎么通信?HTTP 还是 RPC?

我们早期选的是 Feign + Ribbon 的方式,简单方便。但随着服务间调用量增加,RPC 方案(比如 Dubbo)的优势就显现出来了。不过因为前期技术栈都是基于 HTTP,就没大动干戈改掉。

问题三:API 网关该不该做?怎么做?

初期我们没有引入网关,所有的服务暴露自己的接口。结果很快出现了权限校验重复、路由混乱、跨域频繁等问题。后来果断加了 Zuul 做网关层,统一鉴权、限流、转发等功能才逐渐稳定下来。

问题四:配置怎么集中管理?

早期每个服务自己维护 application.yml 文件,本地+测试+预生产环境切换起来特别麻烦。后来我们接入了 Nacos 的 Config Server,彻底解决了这个问题,运维也能实时更新配置而不用重启服务。

问题五:调用链怎么监控?

服务多了以后,A 调 B、B 调 C,出了问题根本不知道在哪一步卡住了。于是引入了 Sleuth + Zipkin,实现了请求链路追踪,大大提升了排查效率。

还有性能瓶颈、数据库事务一致性、分布式锁等等问题,这里先不展开讲,后续我会结合具体实战来细说。

技术选型与架构设计概览

我们最终采用的技术栈如下:

模块 技术选型
服务注册中心 Alibaba Nacos
配置中心 Alibaba Nacos
API 网关 Spring Cloud Gateway
服务间通信 Feign Client
负载均衡 Ribbon
分布式链路追踪 Sleuth + Zipkin
日志收集 ELK(Elasticsearch、Logstash、Kibana)
消息中间件 RabbitMQ / RocketMQ

数据库方面采用了 MySQL 为主,Redis 作为缓存,MongoDB 存储部分非结构化数据。数据库分库分表在后期也陆续做了,后面再细说。

整体架构图大概是这样:

Client(Web / App)
    │
    ▼
Spring Cloud Gateway (鉴权、限流、路由)
    │
    ▼
多个微服务实例(user-service, product-service, order-service...)
    │
    ▼
Nacos 注册中心 + 配置中心
    │
    ▼
Sleuth + Zipkin 链路追踪
    │
    ▼
ELK 日志系统

接下来我重点分享几个关键点的实际落地过程。

关键模块实现详解

一、服务注册中心选型——为什么最终选择了 Nacos?

API接口文档-2

我们一开始试用的是 Eureka,Netflix 的老牌注册中心。但在实际使用中发现:

  • 更新慢:服务下线后还需要一定时间才能清理节点
  • 可视化界面不够友好
  • 缺乏配置管理功能

后来我们尝试了 Consul,体验还不错,但配置中心这块还是需要额外工具配合。最终我们选择了阿里巴巴开源的 Nacos,它集成了服务注册发现和配置中心的功能,而且社区活跃,文档完善,适合国内企业使用。

启动 Nacos Server 非常简单:

git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos clean install -U
cd distribution/target/nacos-server-xxx/bin
sh startup.sh -m standalone # 开发环境单节点启动

然后在各个微服务的 pom.xml 中加入依赖:

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

配置文件中加上:

server:
  port: 8080
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

这样服务启动后就能自动注册到 Nacos 上了。

小贴士:如果你公司有自研的服务治理平台,也可以考虑直接对接。否则的话,Nacos 是一个非常实用的选择。


二、配置中心实践——让配置不再成为痛点

早期我们在不同环境中使用不同的 profile,比如 application-dev.ymlapplication-prod.yml。但这种方式有几个缺点:

  1. 环境切换不方便
  2. 修改配置要重新打包部署
  3. 多服务共享配置难管理

于是我们转向了 Nacos 的 Config 功能。在 bootstrap.yml 中添加以下配置:

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

Nacos 中新建一个 common-config.yaml,就可以为所有服务提供通用配置项。例如数据库连接池参数、日志级别等。

更强大的一点是:我们可以在运行时热更新配置,不需要重启服务!这在生产环境中特别有用。


三、API 网关设计——不只是个路由器

最初我们没有做网关,服务各自对外暴露接口。结果很快发现了以下几个问题:

  • 接口路径分散,不好统一管理
  • 用户鉴权逻辑重复
  • 没有统一的日志记录
  • 跨域问题频发
  • 无法统一限流熔断

后来我们使用了 Spring Cloud Gateway 做网关,它可以非常灵活地定义路由规则,并且支持 Filter 链来做认证、限流等工作。

配置示例:

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

API接口文档-1

同时,我们在网关层做了 JWT 鉴权逻辑。大致流程是:

  1. 所有请求必须携带 Token
  2. 网关拦截,验证 Token 合法性
  3. 合法则放行,否则返回 401
  4. 支持白名单绕过认证

这部分逻辑可以通过自定义 GatewayFilter 实现,就不贴代码了。总之,有了网关之后,服务层就专注于业务逻辑,权限、限流这些通识功能都在网关完成,极大简化了下游服务的负担。


四、服务间通信的设计考量

我们采用的是 HTTP + Feign 的方式,主要是考虑到 RESTful 接口易于调试、前后端友好兼容。Feign 的声明式客户端也非常方便。

示例代码:

@FeignClient(name = "order-service")
public interface OrderServiceClient {

    @GetMapping("/orders/{userId}")
    List<Order> getOrdersByUserId(@PathVariable Long userId);
}

调用的时候就像调本地方法一样:

List<Order> orders = orderServiceClient.getOrdersByUserId(userId);

但我们也注意到了几个问题:

  • 慢查询或失败可能导致雪崩效应
  • 依赖太多导致调用链复杂
  • 重试机制容易造成重复请求

因此我们加了 Hystrix 做熔断降级,对一些非关键调用设置了 fallback,比如:

@FeignClient(name = "analytics-service", fallback = AnalyticsServiceFallback.class)
public interface AnalyticsServiceClient {
    ...
}

不过 Hystrix 已经进入维护状态,我们也在评估 Resilience4j 或 Sentinel 作为替代方案。


五、调用链追踪实战——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-starter-zipkin</artifactId>
</dependency>

再加上一段配置:

spring:
  zipkin:
    base-url: http://zipkin-server:9411
  sleuth:
    sampler:
      probability: 1.0 # 100%采样率,正式环境建议调低

这样,每个请求都会带上唯一的 trace ID,Zipkin 界面可以清晰看到整个请求经过了哪些服务,耗时多少,哪个环节有问题。

这在定位性能瓶颈时非常有用。比如我们有一次发现某个接口变慢,用 Zipkin 发现原来是下游服务的某个 SQL 查询没加索引,优化完之后性能提升明显。


六、其他实践经验总结

数据库设计的几点建议:

  1. 按服务划分数据库:不要共用一张表,避免耦合
  2. 主键尽量使用 UUID 或雪花算法生成 ID
  3. 重要操作保留操作日志和快照数据
  4. 读写分离 + 分库分表提前考虑好策略
  5. 避免跨服务事务,必要时使用 Saga 模式或消息队列补偿机制

接口设计经验:

  • 所有接口统一 JSON 格式封装,包含 code、message、data
  • 使用 Swagger 自动生成 API 文档(推荐 OpenAPI + Knife4j)
  • 对外接口做输入校验(如 Hibernate Validator)
  • 分页尽量传 offset & limit,而不是 page & size,避免混淆

运维与部署经验:

  • 使用 Docker 容器部署服务,Kubernetes 管理编排
  • 镜像标签要带版本号和 Git 提交哈希,便于追溯
  • 日志统一打到 ELK,建立 Kibana 图表看板
  • Prometheus 监控 JVM 和接口响应指标
  • 自动化部署流水线 CI/CD 必不可少
  • 建议灰度发布,先推少量流量测试再全面上线

踩过的坑和血泪教训

这一段可能是最有价值的部分,很多是书上找不到的东西。

坑一:Nacos 启动失败,反复报错 Connection refused

我们在一台 CentOS 服务器上部署 Nacos 的时候,一直报错无法注册服务,提示 connection refused。后来才发现是防火墙限制了端口访问,而且 SELinux 默认开启,导致网络受限。

解决方案:

  • 关闭 SELinux:

    setenforce 0
    sed -i 's/enforcing/disabled/g' /etc/selinux/config
    
  • 开放端口:

    firewall-cmd --permanent --add-port=8848/tcp
    firewall-cmd --reload
    

这种基础问题常常耗费大量时间,但只要排查到位其实很好解决。


坑二:Feign 调用超时,服务正常却无响应

有一天线上突然有多个服务调用超时,查看日志发现 Feign 报 ReadTimeout,但目标服务本身日志一切正常,CPU 和内存也都很平稳。

后来用 Zipkin 查看调用链,发现有些请求卡在了 Feign 的 connect 步骤。进一步排查发现是因为 DNS 解析异常,Nacos 返回的地址是容器名而非 IP 地址,在某些情况下解析失败。

解决方案:

  • 在网关和服务实例之间打通内部域名解析,或者直接指定 hosts 文件

  • Feign 客户端配置合理的超时时间:

    feign:
      client:
        config:
          default:
            connectTimeout: 5000
            readTimeout: 10000
    

坑三:配置中心更新未生效,服务依旧旧值

我们曾经遇到一个诡异的问题:修改 Nacos 中的配置后,服务端并没有感知到变化。排查发现是因为某些 Bean 的配置属性是静态的,加载后不会刷新,或者某些自定义配置类没有加 @RefreshScope 注解。

解决方案:

  • 确保动态配置的字段被 @Value@ConfigurationProperties 修饰
  • 对于需要热更新的类加上 @RefreshScope
  • 使用 Actuator /actuator/refresh 接口手动触发刷新(测试用)

坑四:服务下线后还在被调用?

有时候我们会优雅关闭服务,但发现网关仍然把请求发给已经停机的服务。这是因为在服务注册中心还没有及时清除节点。

解决方案:

  • 设置合理的健康检查周期和服务失效时间
  • 服务优雅下线前主动通知 Nacos 下线自身(可通过脚本实现)
  • 调用方做好超时和重试机制

坑五:日志埋点太杂,搜索费劲

我们初期没有规范日志格式,导致 Logstash 收集日志时难以提取有效字段。后来我们统一了日志输出模板,并加入 traceId 字段用于日志关联。

日志格式示例(logback.xml):

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{traceId} %msg%n</pattern>

这样就能在 Kibana 中根据 traceId 查看完整的请求日志了。


效果与收益总结

自从我们全面采用 Spring Cloud 微服务架构以来,系统发生了质的变化:

  • 部署效率提升 3 倍以上:服务独立部署,互不影响
  • 开发协作顺畅:每个服务职责单一,开发人员专注性强
  • 故障隔离能力增强:某服务崩溃不会导致全站瘫痪
  • 观测性提升:通过 Zipkin、ELK 快速定位问题
  • 横向扩展能力强:压力大的服务可以弹性扩缩容
  • 技术债务减少:各模块边界清晰,利于长期演进

当然,我们也付出了不少代价,比如基础设施投入加大、运维复杂度提高、分布式事务处理难度上升等。但从长远来看,这套架构支撑了我们未来 2~3 年的产品发展需求。

我的几点经验总结与建议

结合我过去一年的实战经验和踩坑教训,给正在准备微服务转型的朋友们几点建议:

1. 不要为了微服务而微服务

如果当前单体应用还能满足业务需求,没必要强行拆分成微服务。微服务带来灵活性的同时,也会带来更多运维和协调成本。能不拆就不拆,拆了就要拆彻底

2. 架构设计要有前瞻性

微服务不是一天建成的。你今天搭的基础设施,决定了未来的可扩展性和可维护性。比如服务注册中心、网关、配置中心这些基础组件,一开始就要规划好,不要想着后期补。

3. 注重可观测性,尽早接入监控体系

链路追踪、日志收集、监控告警这三大件越早接入越好。不然你会陷入“出了问题不知道哪坏”的痛苦境地。特别是当你的服务数量超过 10 个之后。

4. 团队沟通比技术选型更重要

微服务意味着多个服务之间的协同,接口变更、版本控制、相互依赖都需要明确的沟通机制。建议定期召开接口评审会,使用 OpenAPI 统一管理接口文档。

5. 持续集成/持续部署(CI/CD)必不可少

微服务数量一旦变多,人工部署成本就会激增。务必尽早搭建自动化构建、部署、测试流水线,推荐 Jenkins + GitLab + Harbor 的组合,

评论 0

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