从零开始搭建Spring Cloud微服务:一位后端工程师的实战分享

Markdown诗人
2025-06-27 15:49
阅读 730

背景介绍

背景介绍

大家好,我是一名有五年开发经验的后端工程师。在过去几年里,我在多个项目中深度参与了微服务架构的设计与落地。今天想和大家分享的是我们团队在一个电商平台重构过程中,如何从零开始一步步搭建基于 Spring Cloud 的微服务架构体系的真实经历。

这次项目的背景是一个中型电商系统,原本是单体结构,随着业务量增长,出现了接口响应慢、部署频率低、维护成本高、新功能迭代困难等问题。于是公司决定将系统拆分为多个独立的服务,采用微服务架构来解耦各业务模块,并提升可扩展性和可维护性。

遇到的挑战

遇到的挑战

刚开始接触微服务时,说实话我还是挺懵的。虽然以前在学习和Demo项目中了解过 Spring Cloud,但真正在实际工作中上手的时候才发现,它远不是“加个@EnableEurekaServer”这么简单。我们遇到的主要问题包括:

  • 服务注册与发现:多个服务如何快速找到彼此?
  • 负载均衡:用户请求如何平均分配给多个实例?
  • 配置管理:不同环境下的配置文件如何集中管理?
  • 分布式事务:用户下单时库存扣减和订单创建如何保证一致性?
  • 日志追踪:一个请求跨多个服务,怎么定位问题?
  • 部署运维复杂度陡增:本地跑得好好的,线上出问题怎么办?

这些看似是技术细节,实则是微服务落地中最基础也最关键的点。

解决方案:从零构建Spring Cloud微服务架构

解决方案:从零构建Spring Cloud微服务架构

第一阶段:搭起骨架 —— 注册中心 + 网关 + 基础服务

我们首先使用 Eureka 做服务注册中心,所有服务启动时都向 Eureka 注册自己的信息(IP、端口、健康状态等),其他服务通过 Eureka 获取目标服务地址。

然后引入 Zuul 作为 API 网关,统一接收外部请求,并根据路由规则转发到对应的业务服务。这不仅统一了入口,也方便后续添加限流、鉴权等功能。

初期我们只拆分了三个基础服务:

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

每个服务都是独立的 Spring Boot 应用,拥有自己的数据库表结构,服务之间通过 RESTful API 通信。

举个例子,在商品详情页需要展示用户是否已收藏该商品。这个时候,前端调用网关 /product/detail 接口,网关会转调 Product Service,而 Product Service 再调用 User Service 的 /user/favorite?productId=xxx 来判断收藏状态。

这种调用关系一开始还可以接受,但随着服务数量增加,问题就来了。

第二阶段:引入 Feign 和 Ribbon 实现客户端负载均衡

我们最初直接使用 RestTemplate 来发起 HTTP 请求,但在服务实例增多之后发现一个问题:如果某台机器上的 User Service 挂了,请求还是会发过去,导致超时甚至失败。

后来我们切换为 Feign + Ribbon,这样就可以实现客户端的负载均衡。Feign 本质上是对 RestTemplate 的封装,提供声明式调用接口:

@FeignClient(name = "user-service")
public interface UserServiceClient {
    @GetMapping("/user/favorite")
    Boolean isFavorite(@RequestParam("userId") Long userId, 
                       @RequestParam("productId") Long productId);
}

Ribbon 则负责从 Eureka 获取 user-service 的多个实例,并根据策略选择一台健康的节点发送请求。同时支持重试机制,大大提升了稳定性。

第三阶段:统一配置中心 - Config Server

服务多了之后,每个服务都有自己的 application.yml,测试环境、生产环境的数据库连接池、日志级别、第三方API密钥等等都不一样,维护起来非常麻烦。

我们随后引入了 Spring Cloud Config,把所有的配置文件集中放在 Git 仓库中,格式如下:

config-repo/
├── dev/
│   ├── user-service.yml
│   └── product-service.yml
├── prod/
│   ├── user-service.yml
│   └── order-service.yml

各个服务通过 bootstrap.yml 指定要拉取哪个环境的配置:

spring:
  cloud:
    config:
      uri: http://config-server:8080
      name: user-service
      profile: dev

这样一来,更新某个服务的配置只需要提交 Git 并触发刷新(通过 /actuator/refresh 接口),不再需要重新打包上线,运维同学也轻松了不少。

第四阶段:链路追踪 - Sleuth & Zipkin

前面提到,微服务环境下一次请求可能横跨多个服务,出现错误时很难快速定位根源。我们曾经遇到过这样一个问题:用户支付成功后订单状态迟迟不更新。

当时查看日志发现 Order Service 没有问题,Product Service 也没有异常,但就是没更新状态。最后发现是在 Payment Service 中调用了一个第三方回调通知接口,对方返回了超时,但没有做补偿处理。整个流程缺乏跟踪机制,排查过程花了好几个小时。

为了防止类似情况再次发生,我们引入了 Sleuth 和 Zipkin。

Sleuth 会在每次请求中生成唯一的 trace ID,并在服务间传递,Zipkin 则收集所有服务的 Span 日志并展示完整的调用链路:

比如一次下单操作,调用了 Order -> Inventory -> Payment,最终可以看到每个步骤的耗时、是否有异常:

Trace ID: 4d1e57c2-3a6b-49f5-b6a7-e0f8e5d2c1f3
└─ POST /order/create (Order Service) [1s]
   ├─ GET /inventory/check (Inventory Service) [200ms]
   └─ POST /payment/process (Payment Service) [700ms]

有了这个工具之后,我们日常排查效率提高了至少50%。

第五阶段:服务熔断 - Hystrix

有一次大促期间,用户服务突然因为数据库压力过大导致响应变慢,进而影响到了订单服务无法获取用户信息,整个下单流程都被卡住,用户体验极差。

这个问题暴露了我们的服务调用链中缺乏容错能力。我们后来引入了 Hystrix 来设置熔断降级机制:

@HystrixCommand(fallbackMethod = "defaultUserInfo")
public UserInfo getUserInfo(Long userId) {
    return restTemplate.getForObject("/user/info/" + userId, UserInfo.class);
}

public UserInfo defaultUserInfo(Long userId) {
    return new UserInfo(userId, "未知用户", "***");
}

当服务不可用或超时时,自动切换为默认值或者缓存数据,虽然部分功能受损,但不至于整体瘫痪。

不过值得一提的是,Netflix 已经停止对 Hystrix 的维护,我们目前也在评估迁移到 Resilience4j 的计划。

效果总结

效果总结

缓存策略对比-2

经过几个月的持续优化和迭代,整个系统的稳定性和服务可维护性明显提升:

  • 单个服务的部署时间从原来的几十分钟缩短到几分钟
  • 接口响应速度从之前的几百毫秒下降到平均 100ms 内
  • 新功能模块开发变得更加轻便,服务边界清晰明确
  • 错误定位更快,运维干预更少

尤其是监控和日志方面,借助 Prometheus + Grafana + Zipkin 等工具,我们终于实现了对服务运行状态的实时掌控。

经验分享

负载均衡配置-1

如果你正准备或已经开始微服务之旅,结合我的亲身经历,这里有几个建议希望能帮到你:

1. 微服务不是银弹

很多同学一上来就想拆微服务,但其实微服务适合中大型项目或者长期发展的系统。小项目还是建议先用模块化设计+良好的接口隔离,等到真正出现瓶颈再考虑拆分。

2. 数据库设计要慎重

服务拆分时最容易忽视的就是数据库的划分。我们早期没有做好,导致两个服务共用一张表,最后不得不迁移数据、修改接口,浪费了很多精力。

原则:一个服务对应一个数据库,必要时可通过异步同步、消息队列来保证数据最终一致。

3. 接口设计要有前瞻性

服务间的通信不是本地方法调用,要考虑性能和版本兼容性。我们在订单服务和库存服务对接时,一开始设计得太随意,后面改字段、加字段都要两边同时发布,十分痛苦。

建议提前约定好:

  • 使用通用数据格式(如 JSON)
  • 版本号机制(如 v1/order/create)
  • 接口文档及时更新(推荐 Swagger UI)

4. 生产环境的坑比想象得多

  • 网络不稳定:服务A调用服务B失败不一定是因为代码问题,可能是网络波动或DNS解析出错。
  • 服务漂移:Kubernetes下Pod重启后IP会变,必须依赖注册中心来寻址。
  • 日志聚合:别再一个个登录机器看日志,ELK 是必备技能。
  • 自动化部署:CI/CD 必须配上,否则手动打 jar 包、上传服务器会让你怀疑人生。

5. 技术选型要有取舍

Spring Cloud 提供了非常丰富的组件,比如 Zuul 改成了 Gateway,Config Server 可能被 Nacos 替代,Hystrix 逐渐被抛弃……

我的建议是:

  • 对于中小团队来说,稳比新更重要,不要追求最新最炫的技术
  • 优先解决当前痛点,逐步演进,而不是一开始就堆料
  • 多关注社区活跃度和官方支持力度

写在最后

微服务这条路不好走,但我始终坚信一句话:“好的系统不是设计出来的,是一点一点折腾出来的。”

如果你刚开始入门 Spring Cloud,不妨从一个简单的 demo 开始,比如写一个“天气预报查询服务”,让它调用另一个“城市信息服务”,加上注册中心、网关、配置中心……你会发现越用越顺,越学越有意思。

希望这篇来自实战的经验分享对你有所启发。如果有任何问题,欢迎留言交流。我也会在后续的文章中继续分享我们在 Spring Cloud + Kubernetes 上的进一步实践,敬请期待!

评论 0

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