Spring Cloud从零开始:微服务实战手记

开源路边摊
2025-06-16 10:42
阅读 316

初衷与背景

初衷与背景

2019年我刚加入一家中型互联网创业公司,当时我们整个后台是一个典型的单体架构(Monolith),部署在一台云服务器上。随着业务增长,系统频繁出现接口响应变慢、上线影响全功能、数据库锁竞争等问题。技术负责人果断决定重构,目标是引入微服务架构,用 Spring Cloud 来搭建一套可扩展的后端体系。

那时候,我对 Spring Cloud 的理解还停留在“听说过”、“学过一些 Demo”的阶段,真正动手做项目完全是摸着石头过河。本文就结合那段真实的项目经历,谈谈我是怎么一步步从零开始搭建基于 Spring Cloud 的微服务体系的。


问题描述:传统架构的瓶颈

问题描述:传统架构的瓶颈

我们的原始系统结构非常简单,一个 Java Web 应用,集成了支付、用户中心、商品管理等多个模块。随着功能增加和并发量上升,主要出现了以下几个问题:

  • 部署复杂:每次上线都要全量更新,风险大。
  • 模块耦合严重:改一个功能可能影响另一个完全无关的功能。
  • 性能瓶颈明显:一个接口卡住,整个应用都拖慢。
  • 数据库压力大:所有请求都打在一个数据库上,索引爆炸。

最让我印象深刻的一次,是我们上线了一个商品推荐的新算法逻辑,结果把整个数据库表加锁了,造成首页加载超时。那一次事故之后,老板说:“必须拆微服务。”


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

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

我们选择了 Spring Cloud + Spring Boot 的组合来实现微服务架构,原因很简单:团队对 Spring 比较熟悉,生态完整,社区活跃,文档丰富,而且可以跟已有的代码较好兼容。

架构设计概览

整体上我们采用的是经典的 Spring Cloud 分布式架构模式,主要包括以下组件:

  • Eureka:服务注册与发现
  • Feign / OpenFeign:服务间通信
  • Zuul / Gateway:API 网关
  • Config Server:统一配置管理
  • Sleuth / Zipkin:链路追踪(后来才加上)
  • Ribbon:负载均衡(配合 Feign 使用)

我们先将原来的几个核心模块拆分成独立的服务:

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

这些服务通过 Eureka 注册到同一个服务注册中心,使用 Feign 完成跨服务调用,外部请求则走 API Gateway 统一入口。


实践落地:关键代码片段

下面是我印象比较深的一些核心代码片段,都是实际生产项目中用过的,不是什么官方示例。

1. 服务注册(以商品服务为例)

# application.yml
server:
  port: 8082

spring:
  application:
    name: product-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
// 启动类添加 @EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class ProductServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductServiceApplication.class, args);
    }
}

启动后,可以在 Eureka 控制台看到注册成功的信息。

2. 跨服务调用(用户服务调用商品服务)

我们早期用的 Feign,后来切换到了 OpenFeign,这里给个基础的例子:

// ProductClient.java
@FeignClient(name = "product-service")
public interface ProductClient {

    @GetMapping("/products/{id}")
    Product getProductById(@PathVariable("id") Long id);
}

这个接口被注入到 UserService 中使用:

@Service
public class UserService {

    private final ProductClient productClient;

    public UserService(ProductClient productClient) {
        this.productClient = productClient;
    }

    public void doSomething(Long productId) {
        Product p = productClient.getProductById(productId);
        // ...
    }
}

注意:为了启用 Feign Client,需要在启动类或配置类上加 @EnableFeignClients

3. API 网关路由(Gateway 示例)

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

这样设置之后,外部访问 /api/users/123 就会转发到 user-service 的 /users/123 接口,路径中的 /api/users 前缀会被自动去掉。


开发过程中的坑与解法

微服务听起来很美好,但在真实开发中还是踩了不少坑,这里分享几个典型问题。

问题1:服务调用超时与失败传递

刚开始,我们没配置超时参数,有时候一个服务挂掉,会导致整条链路瘫痪。比如:用户服务调订单服务,订单服务调支付服务,支付服务出问题,用户服务就跟着卡死。

解决方案:

我们逐步引入了 Hystrix 断路器,并设置了合理的超时时间:

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

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 8000

同时,每个 Feign Client 添加 fallback:

@FeignClient(name = "product-service", fallback = ProductClientFallback.class)

fallback 类处理异常情况下的降级逻辑。

问题2:数据库隔离不彻底

最初为了方便,多个服务共用了同一张数据库表。结果某个服务修改数据结构,导致其他服务报错。教训很大!

解决方案:

每个服务拥有自己的数据库实例,必要时同步数据用 Kafka 或者异步 Job 处理。例如:

  • 用户服务操作完用户数据后,发送事件到 Kafka。
  • 订单服务监听该事件,更新本地缓存。

这样保证了数据一致性的同时,也做到了数据库级别的隔离。

问题3:日志混乱 & 链路追踪缺失

前期我们没有统一的日志格式和链路追踪,出了问题很难定位。尤其跨服务的时候,根本不知道哪一步出错了。

解决方案:

引入 Sleuth 和 Zipkin:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

配置一下 Zipkin 地址:

spring:
  zipkin:
    base-url: http://localhost:9411

然后每个请求都会生成唯一的 traceId,我们日志里带上它就能快速串联所有服务。


效果总结:为什么值得?

微服务改造上线后,效果还是很明显的:

  • 部署灵活:不同服务可以独立部署、滚动升级。
  • 性能提升:拆分后热点接口的压力分散了。
  • 故障隔离:一个服务挂不影响整体系统。
  • 扩展能力强:后续加新功能只需要新增服务模块即可。

特别是当我们在双十一高峰期扩容时,得益于服务拆分,我们只针对高并发的订单和支付服务进行了水平扩容,而无需全局加机器,节省了不少成本。


经验分享:几点建议

如果你也在考虑入门 Spring Cloud,或者准备搭建第一个微服务项目,我有几点真心建议:

✅ 1. 不要盲目追求新技术

Spring Cloud 虽好,但不是万能的。你得先明确你的业务是否真的需要微服务。如果只是一个小项目,或者初期用户不多,不如先把单体架构优化好,等真正遇到瓶颈再拆也不迟。

✅ 2. 拆服务别太细,先从核心边界入手

不要一开始就按“每个人一个服务”去拆。我们一开始也犯了这个问题,导致服务太多反而难以维护。建议从业务边界清晰的地方拆起,比如用户、订单、支付这三个自然区隔的领域。

✅ 3. 提前规划好监控和日志体系

否则,线上一出问题,你就只能靠猜了。我见过太多团队微服务上线后,连哪个服务在哪台机器上运行都不清楚,排查效率极低。

✅ 4. 重视 DevOps 和自动化部署

微服务意味着部署频率提高,手动部署很容易出错。我们后期引入了 Jenkins + Docker + Kubernetes 自动化流程,效率提升了数倍。

✅ 5. 学会接受“最终一致性”

微服务之间不可能强一致,尤其涉及数据库时。你需要学会接受并处理“最终一致性”,合理使用消息队列、补偿机制等方式确保系统稳定。


结语:微服务是一种演化,不是终点

回头看,我们并不是一开始就做得很好,很多设计也是在一次次踩坑中打磨出来的。Spring Cloud 是个很好的工具集,但它不是银弹。真正的挑战从来不在框架本身,而是在如何设计合理的架构、组织服务、处理分布式事务、保障稳定性。

微服务不是一蹴而就的东西,更像是一种架构思维的演化。希望这篇文章能帮你少走些弯路。如果你现在正在学习 Spring Cloud 或者准备动手实践,不妨把它当成一次旅程——每解决一个问题,就会多一分底气。

最后送大家一句话,也是我当时写在项目笔记里的话:
“服务小了,心要更大。”

欢迎留言交流,一起进步!

评论 0

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