裸辞后在家撸代码,我用Spring Cloud搭了个微服务练手项目
开篇:为什么我要折腾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>
这里有个小细节:dependencyManagement和dependencies的区别。前者只是声明版本,不会真正引入依赖;后者才会真正引入。这个搞混了的话,子模块的依赖管理会很乱。
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是个不错的选择。
数据库设计的一些思考
微服务架构下,数据库设计跟单体应用有很大不同。核心原则就是:每个微服务拥有自己独立的数据库,服务之间不能直接访问其他服务的数据库。
这带来了一个问题:跨服务的数据查询怎么办?比如订单列表需要展示用户信息,但用户数据在用户服务的数据库里。
常见的解决方案有几种:
数据冗余:在订单表里冗余一份用户的基本信息(用户名、头像等)。优点是查询简单,缺点是数据一致性维护麻烦。
API聚合:在BFF层(Backend For Frontend)调用多个服务,聚合数据后返回给前端。优点是不冗余数据,缺点是接口响应慢。
CQRS + 事件驱动:通过消息队列同步数据,读模型和写模型分离。适合复杂场景,但实现成本高。
我一般会根据业务场景来选择。如果是简单的关联查询,就用数据冗余;如果是复杂的聚合查询,就用API聚合;如果是高频查询且对实时性要求不高,就用CQRS。
总结
从零搭建一套Spring Cloud微服务架构,说难不难,说简单也不简单。关键是要理解每个组件解决的是什么问题,而不是盲目堆砌技术栈。
我的建议是:
先搞懂原理再动手:不要上来就抄配置,先理解服务注册发现、配置管理、熔断限流这些概念。
从小做起:不要一上来就搞十几个服务,先搭两三个服务跑通整个流程,再逐步扩展。
重视监控和链路追踪:微服务架构下,问题排查难度成倍增加。一定要接入SkyWalking或者Zipkin这类链路追踪工具。
不要过度设计:如果你的业务规模还不大,真的没必要上全套微服务。单体应用 + 模块化设计,可能更适合你。
好了,今天就聊到这里。裸辞在家的日子虽然有点焦虑,但能静下心来学习一些新技术,感觉还是挺充实的。希望这篇文章对想入门Spring Cloud的同学有所帮助。
如果有什么问题,欢迎在评论区交流。下次再聊!


评论 0