从零开始搭建微服务:Spring Cloud实战全记录

Debug到怀疑人生
2025-06-20 15:52
阅读 225

引言:为什么选择Spring Cloud?

还记得第一次接触微服务架构是在我工作的第二年,那会儿公司业务规模逐渐扩大,传统的单体应用越来越难以支撑快速迭代和高并发的需求。我们开始尝试拆分模块,但面对各个模块之间的通信、配置管理、服务注册与发现等问题,一度陷入困境。

那时候,我和团队翻遍了各种资料,试过Dubbo,也研究过gRPC,但最终还是选择了Spring Cloud——因为它成熟、生态完善,而且上手门槛相对较低,特别适合从传统项目向分布式系统过渡的场景。

今天这篇文章,我想结合自己过去几年在实际项目中使用Spring Cloud的经验,带你一步步从零开始搭建一个完整的微服务架构,并分享我在实战中踩过的坑和收获的心得。


问题描述:我们在做什么项目?遇到了哪些挑战?

2022年初,我们接到了一个新项目:为一家大型连锁超市设计并开发一套全新的线上商城系统,涵盖商品管理、订单处理、库存控制、会员体系等多个子系统。

最开始我们用的是Spring Boot单体架构,一切看起来都很顺利。但随着功能越来越多,代码越来越臃肿,每次发布都提心吊胆,数据库压力陡增,接口响应时间越来越慢。

更糟糕的是,当某一模块出现故障(比如库存服务超时)时,整个系统都有可能被“连带影响”,这在生产环境是绝对不允许的。

主要痛点总结如下:

  1. 部署困难:所有功能打包在一个jar里,改动小功能也要全量更新。
  2. 性能瓶颈明显:数据库和某些核心接口成为瓶颈。
  3. 维护成本高:多人协作时,版本冲突频发,上线风险大。
  4. 可扩展性差:新增功能只能加模块,无法做到模块级别的横向扩展。

这时候,我们意识到,必须进行服务拆分,走微服务化路线。


解决方案:选型与整体架构设计

我们决定采用Spring Cloud作为微服务框架,基于Netflix开源的一整套组件来构建系统。初期规划的服务包括:

  • 用户中心(UAA)
  • 商品中心(PMS)
  • 订单中心(OMS)
  • 库存中心(IMS)
  • 支付中心(PAY)
  • 网关服务(API Gateway)
  • 配置中心(Config Server)
  • 注册中心(Eureka Server)

整体结构如下:

[外部请求] --> [Zuul网关] --> [各微服务]
                     |
          [Eureka服务注册中心]
                     |
             [Config配置中心]

这样做的好处:

  • 各个服务之间通过Rest或Feign通信
  • 所有服务注册到Eureka,实现服务发现
  • 使用Zuul作为统一入口,实现路由、权限校验等功能
  • 配置文件集中管理,便于维护
  • 模块独立部署,互不影响,易于横向扩展

代码实践:从零搭建Spring Cloud项目

第一步:初始化Eureka注册中心

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

application.yml 配置如下:

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动后访问:http://localhost:8761 即可看到服务注册页面。


第二步:创建一个简单的微服务(以商品服务为例)

添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

启动类加上注解:

@SpringBootApplication
@EnableDiscoveryClient
public class PmsServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(PmsServiceApplication.class, args);
    }
}

配置 application.yml:

spring:
  application:
    name: pms-service

server:
  port: 8081

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

启动之后,就可以在Eureka页面看到这个服务已经成功注册。


第三步:服务间调用(使用Feign)

例如在订单中心调用商品中心查询商品信息:

Feign客户端定义:

@FeignClient(name = "pms-service")
public interface ProductFeignClient {
    @GetMapping("/products/{id}")
    ResponseEntity<Product> getProductById(@PathVariable("id") Long id);
}

在OrderService中注入这个客户端即可完成远程调用。


第四步:引入Zuul网关做统一入口

Zuul负责:

  • 请求转发
  • 路由配置
  • 权限校验
  • 日志追踪

启用Zuul只需要添加以下依赖和注解:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

启动类:

@EnableZuulProxy
@SpringBootApplication
public class ZuulGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulGatewayApplication.class, args);
    }
}

配置:

zuul:
  routes:
    pms:
      path: /pms/**
      serviceId: pms-service
    oms:
      path: /oms/**
      serviceId: oms-service

访问 http://localhost:8080/pms/products/123 即可自动转发到对应服务。


踩坑经验:那些年我们掉过的“陷阱”

坑1:Feign Client调用超时

现象: 有时候Feign调用会报错,提示ReadTimeoutException,特别是在负载较高时。

原因分析: 默认情况下Feign没有设置连接和读取超时时间,容易导致线程阻塞。

解决方案:

在application.yml中添加如下配置:

feign:
  client:
    config:
      default:
        connect-timeout: 5000
        read-timeout: 10000

或者通过自定义配置类实现:

@Configuration
public class FeignConfig {
    @Bean
    public Request.Options options() {
        return new Request.Options(5000, 10000);
    }
}

坑2:多个服务同时修改数据库导致数据不一致

这个问题出现在订单和库存服务的联动操作中。下单时要扣除库存,如果失败需要回滚。

解决思路: 我们最初尝试用本地事务+手动补偿的方式,但复杂且容易出错。

后来改为借助Saga模式,引入异步消息队列,保证最终一致性。

虽然Spring Cloud Sleuth + Zipkin 可以追踪链路,但在事务性问题上,仍然建议:

  • 小范围使用本地事务
  • 多服务协作使用事件驱动模型
  • 关键业务逻辑配合人工对账机制

坑3:生产环境数据库连接池爆满

上线初期,某次活动期间突然发现大量接口超时。

排查日志后发现是Hikari连接池被占满,SQL执行缓慢。

根本原因:

  • SQL未优化
  • 数据库索引缺失
  • 连接池大小配置不合理(默认只有10)

解决方案:

  • 添加慢SQL监控(Prometheus + Grafana)
  • 对关键表加索引
  • 修改连接池配置:
spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      idle-timeout: 30000
      max-lifetime: 1800000

效果总结:拆分后的收益

经过近3个月的改造,我们将原来的单体应用拆分为6个微服务,并逐步迁移到Kubernetes集群中运行。

最终效果如下:

项目 改造前 改造后
平均接口响应时间 800ms 250ms
发布频率 1次/月 1次/周
故障隔离能力 几乎无隔离 单服务故障不影响整体
横向扩展支持 不支持 支持
团队协作效率 冲突频繁 明显提升

更重要的是,系统的可维护性大大增强,运维同学也能轻松地通过Prometheus监控各个服务的状态。


经验分享:写给还在路上的你

如果你刚接触微服务,我的建议是:

✅ 从小项目入手,别一上来就想拆分成十几二十个服务

微服务不是银弹,它带来的复杂度远比你想像的多。先从两三个服务练手,逐步深入。

✅ 注重基础设施建设:监控、日志、链路追踪不能少

推荐组合:Spring Cloud Sleuth + Zipkin(链路追踪)、Prometheus + Grafana(监控)、ELK(日志收集)

✅ 合理划分服务边界,避免过度拆分

根据业务领域拆分,而不是技术组件。比如“用户”、“订单”可以单独作为一个服务,而不是把DAO、Service层分别拆出去。

✅ 接口设计尽量保持幂等性,防止重复请求导致状态混乱

尤其是在分布式环境下,网络不稳定可能导致请求重试,这时幂等机制非常重要。

✅ 安全永远是第一考虑:认证授权机制必须做好

我们使用了Spring Security + OAuth2做统一鉴权,并在网关中做了全局拦截。


总结

Spring Cloud是一把利器,但也是一把“双刃剑”。合理使用它可以极大提升系统灵活性和可维护性,但如果滥用或者理解不深,反而会带来很多麻烦。

这篇文章讲了很多细节,是我这几年一路踩坑走来的经验沉淀。希望它能帮你少走弯路,早点走上稳定、可扩展的微服务之路。

如果你有任何疑问,或者想一起讨论某个具体的技术点,欢迎留言或私信交流。微服务的世界很精彩,我们一起探索吧!

评论 0

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