Spring Cloud 从零开始:一个真实项目中的微服务实践之路
引言:为什么会选择用 Spring Cloud 搞微服务?

记得去年刚接手一个中型电商系统重构任务的时候,我整个人是懵的。原来的单体应用已经膨胀到十几个模块,打包一次要两分钟不说,部署一出错就得全站下线。团队越来越大,代码越来越乱,开发效率直线下降。我们几个核心开发在技术方案会上讨论了快一周,最终达成一致:拆!
拆成微服务。这是我们能找到的最合理也最熟悉的方案。
当时我虽然写过几年 Java 后端,但真正接触 Spring Cloud 还是第一次。为了快速上手、少走弯路,整个团队一边学习一边摸索实践。现在回过头来看这段经历,踩过的坑、吃过的苦都成了宝贵的经验。今天这篇文章,就想和大家分享我在实际项目中使用 Spring Cloud 构建微服务的心得,尤其是那些只有亲身经历过才会知道的细节和教训。
项目背景:从“单体怪兽”到微服务架构

我们的目标是一个电商平台,包括用户中心、商品中心、订单系统、支付系统、库存系统以及运营后台等主要模块。原系统是一个典型的 Spring Boot 单体应用,所有代码在一个仓库里,通过 Maven 多模块管理。
问题很明显:
- 部署困难:每次上线都要停机,风险极高
- 扩展性差:某一个模块负载高了也没法单独扩容
- 开发协同困难:多组并行开发时经常出现冲突
- 维护成本高:日志分散,问题定位难
于是我们决定使用 Spring Cloud 来进行服务拆分,目标如下:
- 实现服务注册与发现
- 提供统一的 API 网关入口
- 支持配置中心统一管理
- 实现服务间通信与负载均衡
- 保证服务调用链可追踪
- 提升系统的可观测性和稳定性
遇到的问题和挑战
刚开始搭建微服务架构的时候,我们遇到了一系列现实问题:
问题一:服务怎么注册和发现?
一开始我们试用了 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?

我们一开始试用的是 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.yml、application-prod.yml。但这种方式有几个缺点:
- 环境切换不方便
- 修改配置要重新打包部署
- 多服务共享配置难管理
于是我们转向了 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

同时,我们在网关层做了 JWT 鉴权逻辑。大致流程是:
- 所有请求必须携带 Token
- 网关拦截,验证 Token 合法性
- 合法则放行,否则返回 401
- 支持白名单绕过认证
这部分逻辑可以通过自定义 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 查询没加索引,优化完之后性能提升明显。
六、其他实践经验总结
数据库设计的几点建议:
- 按服务划分数据库:不要共用一张表,避免耦合
- 主键尽量使用 UUID 或雪花算法生成 ID
- 重要操作保留操作日志和快照数据
- 读写分离 + 分库分表提前考虑好策略
- 避免跨服务事务,必要时使用 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