从零开始构建微服务架构:我在Spring Cloud实战中的踩坑与成长
引言:为什么选择 Spring Cloud?

2019年,我加入了一家正在快速扩张的互联网金融创业公司,当时我们的单体应用已经逐渐无法支撑不断增长的业务需求。系统响应缓慢、代码臃肿、团队协作困难等问题日益突出。
我们决定进行微服务化改造,而作为后端负责人之一,我带领团队选择了 Spring Cloud 这个主流技术栈来搭建新的服务体系。这一路走来,踩过很多坑,也积累了不少经验。
今天我想和大家分享一下,我们是如何从零开始搭建起基于 Spring Cloud 的微服务架构体系的。希望对正准备或正在微服务化的朋友有所帮助。
项目背景与挑战

我们的核心业务包括用户账户管理、风控审核、交易处理等模块,这些都在一个几十万行的单体应用中耦合在一起。随着业务增长,每次上线都要小心翼翼,测试周期长,部署慢,而且一个小问题就能导致整个服务不可用。
因此,我们在2020年初启动了微服务拆分计划,目标是:
- 将原有的单体应用按业务域拆分成多个独立的服务
- 实现服务间的高效通信和统一治理
- 提升系统的可维护性、可扩展性和稳定性
但理想很丰满,现实却很骨感。
拆分初期遇到的问题
刚开始时,我们对微服务的理解还停留在表面,以为“把功能分开就是微服务”,结果很快遇到了一系列问题:

- 服务调用混乱:A服务调B服务的时候不知道该调哪个地址,IP变动频繁,经常出现找不到服务的情况。
- 接口版本不一致:两个团队各自维护服务,更新接口不沟通,导致调用方频繁出错。
- 配置难以管理:每个服务都有自己的 application.yml,环境配置切换困难,一到测试/预发布环境就各种报错。
- 日志和监控缺失:出了问题不知道去哪里查日志,服务崩溃也不知道谁负责。
- 高并发场景下性能瓶颈明显:某些核心服务在压测时直接扛不住,响应时间暴涨。
这些问题都指向了一个事实:我们需要一套完整的微服务治理体系。
解决思路与技术选型:Spring Cloud 全家桶上场
我们调研了很多方案,最终确定以 Spring Cloud Netflix + Spring Cloud Alibaba 为核心来搭建整个微服务体系,以下是我们的主要选型和技术分工:
| 模块 | 技术选型 | 作用 |
|---|---|---|
| 注册中心 | Eureka / Nacos | 服务发现与注册 |
| 网关 | Zuul / Gateway | 统一路由与权限控制 |
| 负载均衡 | Ribbon | 客户端负载均衡 |
| 服务调用 | Feign | 声明式服务调用 |
| 配置中心 | Config / Nacos | 统一管理配置文件 |
| 日志聚合 | ELK (Elasticsearch, Logstash, Kibana) | 集中查看日志 |
| 分布式链路追踪 | Sleuth + Zipkin | 追踪服务调用路径 |
| 限流熔断 | Hystrix / Sentinel | 防止雪崩效应 |
| 数据库设计 | ShardingSphere / MyCat | 分库分表 |
| 接口文档 | Swagger UI / Knife4j | 接口调试与文档生成 |
接下来我会结合我们项目实际使用的几个关键组件,分享具体的落地过程和经验教训。
实战细节:如何一步步搭建服务治理体系
1. 服务注册与发现 —— Eureka 初探
最初我们使用的是 Eureka 做服务注册中心,每台服务器启动时都会注册自己到 Eureka Server 上,其他服务通过 Service ID 来查找对方。
# eureka-server 的 application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
然后各个服务只需添加如下配置即可完成注册:
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
但到了生产阶段,我们发现 Eureka 在集群同步和服务剔除机制上有明显的延迟问题,特别是在节点宕机或者网络不稳定时,容易出现“已下线的服务仍被调用”的情况。于是后来我们转向了更稳定的 Nacos,并且它自带了配置中心的功能。
2. 服务间通信 —— Feign + Ribbon
为了简化服务之间的通信逻辑,我们使用了 OpenFeign + Ribbon 的组合,实现了声明式的远程调用。
定义一个远程调用接口:
@FeignClient(name = "account-service")
public interface AccountServiceClient {
@GetMapping("/accounts/{userId}")
Account getAccountByUserId(@PathVariable("userId") Long userId);
}
这样只要在本地注入这个接口,就可以像本地调用一样实现跨服务调用了:
@Service
public class UserService {
@Autowired
private AccountServiceClient accountServiceClient;
public void doSomething() {
Account account = accountServiceClient.getAccountByUserId(1L);
// do something with account
}
}
不过我们也吃过不少亏,比如 Feign 默认没有启用超时重试机制,导致某些网络抖动情况下请求失败率飙升,直到后来我们加上了 Hystrix 和 Retryer 才有所缓解。
3. 配置中心 —— 从 Config 到 Nacos
早期我们用 Spring Cloud Config 来集中管理配置,将所有的配置 push 到 Git 中,服务启动时通过 /actuator/refresh 动态加载。
但这种方式有几个痛点:
- Git 更新要手动触发 refresh,否则需要重启
- 部署复杂,还需要额外部署 Config Server
- 多环境区分配置不够直观
于是我们换成 Nacos 作为配置中心,它支持动态配置推送、多命名空间管理、历史版本回滚等功能。只需要简单配置即可自动刷新:
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
extension-configs:
- data-id: user-service.yaml
group: DEFAULT_GROUP
refresh: true

4. 接口设计规范 —— 自定义通用封装 + 版本控制
微服务之间接口的稳定性至关重要。为了避免因一方改动而导致调用方异常,我们制定了以下几条规范:
统一返回包装类:
public class Response<T> { private int code; private String msg; private T data; // getter/setter }错误码统一定义(例如 1000 表示成功,2001 表示参数错误等)
接口版本控制:在 URL 加入 version,如
/api/v1/usersSwagger 接口文档自动生成:每个服务必须开启 Swagger 或 Knife4j,在上线前进行接口校验
这样做不仅提升了接口调用的成功率,也为后续的自动化测试和 Mock 提供了基础。
5. 性能与安全上的考量
数据库方面:
- 我们采用了 ShardingSphere 来进行分库分表,提升查询效率;
- 使用 MyBatis Plus 替代原生 MyBatis,简化 CRUD;
- 对于热点数据加入了 Redis 缓存层,同时设置合理的失效策略以防缓存雪崩;
安全方面:
- 网关层增加 JWT 认证,验证 Token 合法性;
- 敏感操作全部记录审计日志,保留追踪路径;
- 对外暴露的 API 接口,统一采用 HTTPS 协议,防止中间人攻击;
- 每个服务只开放最小必要端口,避免横向渗透风险。
踩过的坑与经验总结
下面是一些我们在开发过程中遇到的真实问题及解决办法:
❗ 1. Feign 不兼容 GET 请求带实体类的问题
有时候我们会想在一个 @GetMapping 接口中传递一个复杂对象作为查询条件,结果发现传参丢失。
这是因为 GET 请求没有 request body,但 Feign 默认会尝试将对象转为 body,造成空指针。
解决方案:
要么手动拼接 URL 参数,要么改用 @PostMapping,并使用 QueryMap 注解:
@GetMapping("/users")
User findUsers(@SpringQueryMap User user); // 必须加注解
❗ 2. 配置中心更新后不生效
Nacos 更新配置后,有些服务迟迟没有生效,原来是忘记在主类加上 @RefreshScope。
解决方案:
所有配置监听类加上注解:
@RestController
@RefreshScope
public class UserController { ... }
❗ 3. 日志采集混乱
ELK 收集日志时,因为没有统一的日志格式,日志内容五花八门,给排查带来了很大麻烦。
解决方案:
我们统一了日志模板,所有服务必须使用统一的 MDC 格式记录 traceId、requestId、userId 等信息,便于链路追踪:
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%X{traceId},%X{spanId}] %m%n
并通过 Kafka 异步写入日志,减少磁盘 IO 压力。
效果与收益
微服务上线一年后,效果非常明显:
- 部署效率提升:每个服务独立打包部署,互不影响
- 问题定位更快:结合 Zipkin 和日志平台,几乎可以秒级定位问题
- 接口调用成功率提高:优化后调用失败率下降超过 70%
- 运维成本降低:借助自动化脚本和 Kubernetes 编排,运维人员工作量大大减少
目前我们已经稳定运行超过 80 个服务实例,平均 QPS 达到 10k+,核心服务均做到无状态部署,具备弹性伸缩能力。
给大家的一些建议
如果你也在考虑使用 Spring Cloud 构建微服务,下面是一些我在实战中总结的经验:
- 先做规划再动手:明确服务边界,设计好接口协议和调用顺序,不要上来就拆
- 服务粒度适中:太细了反而难维护,建议根据业务领域划分,而不是功能点
- 重视可观测性建设:没有日志、监控、链路追踪的微服务系统,早晚是个定时炸弹
- 逐步演进比一步到位更重要:可以从部分核心服务试点,成熟后再推广
- 别忽略安全性:认证授权、数据加密、访问控制这些都不能少
- 关注性能瓶颈:微服务不是银弹,网络开销、调用链深度、数据库连接池都需要仔细设计
写在最后:微服务之路,道阻且长
回头来看,这三年的微服务之旅并不轻松。有无数个加班调试的日子,也有线上故障应急处理的心惊胆战。但也正是这些经历让我对分布式系统有了更深的理解。
Spring Cloud 是一个很强大的工具,但它并不是“一键搞定”的魔法棒。真正让系统跑起来的,是我们每一个开发者背后的思考、权衡和坚持。
希望这篇实战分享对你有用。如果你正在这条路上,不妨多交流多实践,愿我们一起走出属于自己的“云原生之路”。

评论 0