从零开始构建微服务架构:我在Spring Cloud实战中的踩坑与成长

何庆华
2025-06-28 12:58
阅读 407

引言:为什么选择 Spring Cloud?

引言:为什么选择 Spring Cloud?

2019年,我加入了一家正在快速扩张的互联网金融创业公司,当时我们的单体应用已经逐渐无法支撑不断增长的业务需求。系统响应缓慢、代码臃肿、团队协作困难等问题日益突出。

我们决定进行微服务化改造,而作为后端负责人之一,我带领团队选择了 Spring Cloud 这个主流技术栈来搭建新的服务体系。这一路走来,踩过很多坑,也积累了不少经验。

今天我想和大家分享一下,我们是如何从零开始搭建起基于 Spring Cloud 的微服务架构体系的。希望对正准备或正在微服务化的朋友有所帮助。


项目背景与挑战

项目背景与挑战

我们的核心业务包括用户账户管理、风控审核、交易处理等模块,这些都在一个几十万行的单体应用中耦合在一起。随着业务增长,每次上线都要小心翼翼,测试周期长,部署慢,而且一个小问题就能导致整个服务不可用。

因此,我们在2020年初启动了微服务拆分计划,目标是:

  • 将原有的单体应用按业务域拆分成多个独立的服务
  • 实现服务间的高效通信和统一治理
  • 提升系统的可维护性、可扩展性和稳定性

但理想很丰满,现实却很骨感。


拆分初期遇到的问题

刚开始时,我们对微服务的理解还停留在表面,以为“把功能分开就是微服务”,结果很快遇到了一系列问题:

负载均衡配置-2

  1. 服务调用混乱:A服务调B服务的时候不知道该调哪个地址,IP变动频繁,经常出现找不到服务的情况。
  2. 接口版本不一致:两个团队各自维护服务,更新接口不沟通,导致调用方频繁出错。
  3. 配置难以管理:每个服务都有自己的 application.yml,环境配置切换困难,一到测试/预发布环境就各种报错。
  4. 日志和监控缺失:出了问题不知道去哪里查日志,服务崩溃也不知道谁负责。
  5. 高并发场景下性能瓶颈明显:某些核心服务在压测时直接扛不住,响应时间暴涨。

这些问题都指向了一个事实:我们需要一套完整的微服务治理体系。


解决思路与技术选型: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

服务器部署方案-1


4. 接口设计规范 —— 自定义通用封装 + 版本控制

微服务之间接口的稳定性至关重要。为了避免因一方改动而导致调用方异常,我们制定了以下几条规范:

  • 统一返回包装类

    public class Response<T> {
        private int code;
        private String msg;
        private T data;
        // getter/setter
    }
    
  • 错误码统一定义(例如 1000 表示成功,2001 表示参数错误等)

  • 接口版本控制:在 URL 加入 version,如 /api/v1/users

  • Swagger 接口文档自动生成:每个服务必须开启 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

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