裸辞后在家撸代码,我用Spring Cloud搭了个微服务练手项目

Commit写错了
2026-07-28 09:43
阅读 592

开篇:为什么我要折腾Spring Cloud

先说下背景吧。去年年底从某大厂离职了,说好听点叫"主动寻求职业突破",说难听点就是被优化了。干了五年Java后端,从SSM写到Spring Boot,再写到Spring Cloud全家桶,自认为对微服务这套东西已经烂熟于心了。结果面试的时候被问到一些底层原理和架构选型的问题,居然答得磕磕巴巴,这才意识到自己这几年一直在吃老本。

现在每天在家撸代码,顺便用ChatGPT和Claude辅助学习,顺便写写博客记录一下。今天这篇就来聊聊Spring Cloud微服务入门的那些事儿。

说实话,微服务这个东西,在大厂里用得太多了,多到你根本不需要关心它是怎么搭起来的。每天就是改改业务代码,写写CRUD,偶尔处理下线上告警。但当你真正想从零开始搭一套微服务架构的时候,才会发现这里面的坑是真的多。

微服务到底解决了什么问题

在聊具体技术之前,先说说我为什么觉得微服务值得学。

之前在公司的时候,我们有个核心交易系统,代码量大概有80多万行,全塞在一个单体应用里。每次发版都是噩梦——编译要十几分钟,测试回归要两天,上线更是得挑凌晨两三点。有一次我改了一个订单模块的小bug,结果因为代码耦合太严重,连带影响了支付模块,上线后直接炸了,凌晨四点被叫起来回滚。

后来团队决定拆分微服务,用了Spring Cloud。拆分之后确实爽了不少,每个服务独立部署,独立发版,出了问题也好定位。但随之而来的问题也不少——服务治理、配置管理、链路追踪、熔断限流……一套东西搞下来,运维复杂度直接翻倍。

所以我的建议是,如果你所在的公司业务规模还没到一定程度,真的没必要上微服务。但作为技术人,学一下Spring Cloud的架构思想还是很有必要的,毕竟现在稍微大点的项目都在用。

技术选型:Spring Cloud的几大流派

Spring Cloud这个东西,说简单也简单,说复杂也复杂。最大的坑就是版本和组件选型。

目前主流的Spring Cloud实现有两套:

对比维度 Spring Cloud Netflix Spring Cloud Alibaba
注册中心 Eureka(已停更) Nacos
配置中心 Spring Cloud Config Nacos
服务网关 Zuul 1.x Spring Cloud Gateway
熔断降级 Hystrix(已停更) Sentinel
分布式事务 无官方方案 Seata
社区活跃度 低,基本维护状态 高,阿里在持续投入
中文文档 较少 丰富,对国内开发者友好

可以看到,Netflix那套组件基本都已经进入维护模式了,Eureka和Hystrix都停更了。现在新项目基本都是选Alibaba这套,毕竟国内用的人多,遇到问题也好搜解决方案。

不过话说回来,如果你是想深入学习微服务的原理,建议还是把Netflix那套也了解一下。毕竟Spring Cloud的设计思想是相通的,组件可以换,但架构模式不变。

从零搭建:我的实战过程

环境准备

先说下我的开发环境:

  • JDK 17(别用8了,都2024年了)
  • Spring Boot 3.2.x
  • Spring Cloud 2023.0.x
  • Spring Cloud Alibaba 2023.0.x
  • Maven 3.9.x
  • Docker(用来跑Nacos、MySQL这些中间件)

这里有个坑要提醒大家:Spring Boot 3.x要求JDK 17+,而且很多组件的API都有变化。如果你还在用JDK 8,建议先升级JDK,不然后面会踩很多兼容性的坑。

父工程搭建

微服务项目一般会有一个父工程来统一管理依赖版本。我的pom.xml长这样:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.myproject</groupId>
    <artifactId>microservice-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>common</module>
        <module>gateway</module>
        <module>service-user</module>
        <module>service-order</module>
    </modules>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>3.2.4</spring-boot.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Spring Cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Spring Cloud Alibaba -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
            </plugin>
        </plugins>
    </build>
</project>

这里有个小细节:dependencyManagementdependencies的区别。前者只是声明版本,不会真正引入依赖;后者才会真正引入。这个搞混了的话,子模块的依赖管理会很乱。

Nacos:注册中心 + 配置中心

Nacos是阿里开源的一个组件,同时承担了服务注册发现和配置管理的职责。用Docker跑起来很方便:

docker run -d \
  --name nacos \
  -e MODE=standalone \
  -e PREFER_HOST_MODE=hostname \
  -e VIRTUAL_HOST=nacos.local \
  -p 8848:8848 \
  -p 9848:9848 \
  nacos/nacos-server:v2.3.1

启动后访问http://localhost:8848/nacos,默认账号密码都是nacos

然后在各个微服务里引入Nacos依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

配置文件bootstrap.yml(注意是bootstrap,不是application):

spring:
  application:
    name: service-user
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml
        namespace: dev
        group: DEFAULT_GROUP

这里有个坑:Spring Boot 3.x之后,bootstrap.yml默认不会加载了,需要额外引入spring-cloud-starter-bootstrap依赖:

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

我当时搞了半天才发现这个问题,差点把电脑砸了。

Spring Cloud Gateway:统一网关

网关是微服务的统一入口,负责路由转发、鉴权、限流等。Spring Cloud Gateway基于WebFlux,性能比Zuul好很多。

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

配置路由规则:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://service-user
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
        - id: order-service
          uri: lb://service-order
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

lb://表示使用负载均衡,后面跟的是Nacos里的服务名。StripPrefix=1表示去掉路径的第一层。

OpenFeign:服务间调用

微服务之间怎么通信?RESTful HTTP是最常见的方案。Spring Cloud OpenFeign让服务间调用变得像调用本地方法一样简单。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

定义Feign接口:

@FeignClient(name = "service-order", fallbackFactory = OrderFeignFallbackFactory.class)
public interface OrderFeignClient {
    
    @GetMapping("/api/order/{id}")
    Result<OrderDTO> getOrderById(@PathVariable("id") Long id);
    
    @PostMapping("/api/order")
    Result<Long> createOrder(@RequestBody OrderCreateRequest request);
}

注意这里我加了fallbackFactory,这是为了配合Sentinel做熔断降级。后面会详细说。

Sentinel:熔断限流

之前Netflix的Hystrix停更后,Sentinel成了最佳替代。它不仅能做熔断降级,还能做流量控制、系统保护等。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置开启Sentinel:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
      eager: true

Sentinel Dashboard也用Docker跑:

docker run -d --name sentinel -p 8080:8858 bladex/sentinel-dashboard:1.8.7

然后在代码里配置降级规则。我一般用注解的方式,比较灵活:

@SentinelResource(value = "getOrderById", 
                  blockHandler = "handleBlock", 
                  fallback = "handleFallback")
public Result<OrderDTO> getOrderById(Long id) {
    // 业务逻辑
    return orderService.getById(id);
}

public Result<OrderDTO> handleBlock(Long id, BlockException ex) {
    return Result.fail("系统繁忙,请稍后再试");
}

public Result<OrderDTO> handleFallback(Long id, Throwable throwable) {
    log.error("调用订单服务异常,降级处理", throwable);
    return Result.fail("服务暂时不可用");
}

Seata:分布式事务

微服务最头疼的问题之一就是分布式事务。比如用户下单,需要同时操作订单服务和库存服务,如果其中一个失败了,怎么保证数据一致性?

Seata是阿里开源的分布式事务解决方案,支持AT、TCC、SAGA、XA四种模式。我用的是AT模式,对业务代码侵入最小。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

使用非常简单,加个@GlobalTransactional注解就行:

@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Result<Long> createOrder(OrderCreateRequest request) {
    // 1. 创建订单
    Order order = new Order();
    // ... 设置订单信息
    orderMapper.insert(order);
    
    // 2. 调用库存服务扣减库存
    inventoryFeignClient.deductStock(request.getSkuId(), request.getQuantity());
    
    // 3. 调用用户服务扣减余额
    userFeignClient.deductBalance(request.getUserId(), request.getAmount());
    
    return Result.success(order.getId());
}

如果任何一步失败,Seata会自动回滚所有操作。不过要注意,AT模式要求数据库支持本地事务,而且需要创建undo_log表。

踩过的坑和性能优化

坑1:Feign调用超时

刚开始搭好的时候,服务间调用经常超时。排查了半天发现是Feign的默认超时时间太短了,只有1秒。

解决方案是调整超时配置:

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000
  sentinel:
    enabled: true

但更重要的是分析为什么会超时。我后来用Arthas排查了一下,发现是数据库查询慢导致的。加了索引之后,接口响应时间从800ms降到了50ms。

坑2:Nacos配置不生效

有一次改了Nacos上的配置,但是服务没有热更新。查了半天发现是配置文件的命名空间不对。Nacos的命名空间、分组、Data ID这三个要完全匹配才能加载到配置。

坑3:Gateway跨域问题

网关层需要处理跨域,不然前端调用会报错。配置CORS:

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        
        UrlBasedCorsConfigurationSource source = 
            new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        
        return new CorsWebFilter(source);
    }
}

性能优化:连接池调优

微服务架构下,服务间的HTTP调用非常多,连接池的配置直接影响性能。我一般会这样配置:

spring:
  cloud:
    openfeign:
      httpclient:
        enabled: true
        hc5:
          enabled: true
        max-connections: 200
        max-connections-per-route: 50

另外,数据库连接池也要调优。我用的HikariCP,配置如下:

spring:
  datasource:
    hikari:
      minimum-idle: 10
      maximum-pool-size: 50
      idle-timeout: 300000
      max-lifetime: 1800000
      connection-timeout: 30000

这些参数需要根据实际压测结果来调整,不能一概而论。

AI辅助开发的一些心得

说到这,不得不提一下现在AI工具对开发效率的提升。我现在重度依赖ChatGPT和Claude来辅助开发,真的是离不开了。

比如搭建Spring Cloud项目的时候,很多配置文件和样板代码,直接让AI生成,然后自己再微调一下,能省不少时间。还有遇到一些奇怪的报错,把错误信息丢给AI,基本都能快速定位到问题。

不过AI也不是万能的。它给出的方案有时候不够贴合实际业务场景,或者存在一些过时的API用法。所以用AI辅助开发,自己的技术功底还是要扎实的,不然很容易被带偏。

最近还试了通义灵码,这是阿里出的一个AI编程助手,在IDE里集成得比较好。写代码的时候它能实时给建议,体验还不错。不过跟ChatGPT比,在复杂问题的理解上还是差一些。

另外,如果你对大模型部署感兴趣,可以了解一下vLLM。这是一个高性能的大模型推理和服务引擎,支持PagedAttention等技术,推理速度很快。我最近在研究怎么把一些AI能力集成到微服务里,vLLM是个不错的选择。

数据库设计的一些思考

微服务架构下,数据库设计跟单体应用有很大不同。核心原则就是:每个微服务拥有自己独立的数据库,服务之间不能直接访问其他服务的数据库。

这带来了一个问题:跨服务的数据查询怎么办?比如订单列表需要展示用户信息,但用户数据在用户服务的数据库里。

常见的解决方案有几种:

  1. 数据冗余:在订单表里冗余一份用户的基本信息(用户名、头像等)。优点是查询简单,缺点是数据一致性维护麻烦。

  2. API聚合:在BFF层(Backend For Frontend)调用多个服务,聚合数据后返回给前端。优点是不冗余数据,缺点是接口响应慢。

  3. CQRS + 事件驱动:通过消息队列同步数据,读模型和写模型分离。适合复杂场景,但实现成本高。

我一般会根据业务场景来选择。如果是简单的关联查询,就用数据冗余;如果是复杂的聚合查询,就用API聚合;如果是高频查询且对实时性要求不高,就用CQRS。

总结

从零搭建一套Spring Cloud微服务架构,说难不难,说简单也不简单。关键是要理解每个组件解决的是什么问题,而不是盲目堆砌技术栈。

我的建议是:

  1. 先搞懂原理再动手:不要上来就抄配置,先理解服务注册发现、配置管理、熔断限流这些概念。

  2. 从小做起:不要一上来就搞十几个服务,先搭两三个服务跑通整个流程,再逐步扩展。

  3. 重视监控和链路追踪:微服务架构下,问题排查难度成倍增加。一定要接入SkyWalking或者Zipkin这类链路追踪工具。

  4. 不要过度设计:如果你的业务规模还不大,真的没必要上全套微服务。单体应用 + 模块化设计,可能更适合你。

好了,今天就聊到这里。裸辞在家的日子虽然有点焦虑,但能静下心来学习一些新技术,感觉还是挺充实的。希望这篇文章对想入门Spring Cloud的同学有所帮助。

如果有什么问题,欢迎在评论区交流。下次再聊!

评论 0

最热最新
暂无评论
Commit写错了Lv.1
0
影响力
0
文章
0
粉丝