Spring Cloud从零开始:微服务入门指南
上周五晚上十点半,我坐在工位上盯着屏幕上一串红色的Connection refused: connect错误,差点把咖啡杯砸了。这已经是这个月第三次因为单体应用部署问题导致线上回滚了。产品经理还在群里@我:“哥,双11大促就剩两周了,这个需求必须上!” 我默默打开日历,发现自己通勤一小时到公司的时间,居然比写代码的时间还多——北京的早高峰地铁,真的能榨干人最后一丝精力。
作为一个在北京卷了快六年的后端开发,最近被领导“温柔”地安排了一个新任务:把我们那个30万行代码的Spring Boot单体应用,拆成微服务。理由很充分:“你看人家阿里、腾讯都微服务化了,咱们也得跟上时代啊。” 虽然内心一万只草泥马奔腾而过,但为了年终奖,只能硬着头皮上。
其实我对微服务并不陌生。之前用Python写过一些小工具,也试过Django + Celery的异步架构,但真正在生产环境搞Spring Cloud,还是头一回。各种AI编程工具我都试过,GitHub Copilot、CodeWhisperer、Tabnine……最后还是选了Cursor,主要是它对Java生态的理解更深入,特别是在处理Spring配置的时候,能直接给我生成符合最佳实践的代码,省了我不少查文档的时间。
为什么是Spring Cloud?
可能有人会问:“现在都2024年了,干嘛不用Go或者Rust重写?”(没错,我最近确实在研究Rust,感觉所有权机制简直太优雅了!)但现实是,我们团队的技术栈主要还是Java,而且业务逻辑已经非常复杂,重写成本太高。Spring Cloud作为Java生态里最成熟的微服务解决方案,成了最稳妥的选择。
我们的目标很明确:
- 拆分用户服务、订单服务、商品服务三个核心模块
- 实现服务注册与发现
- 配置中心统一管理
- API网关统一入口
- 保证高可用和容错
说白了,就是不想再因为一个服务挂了,整个系统瘫痪。
项目实战:从零搭建微服务架构
第一步:服务注册与发现 - Eureka
首先得让各个服务能找到彼此。Spring Cloud Netflix Eureka虽然Netflix已经停止维护了,但在国内还是有很多公司在用,社区支持也足够。我新建了一个eureka-server项目:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
配置文件application.yml也很简单:
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
enable-self-preservation: false
这里有个坑:enable-self-preservation一定要设为false,否则在网络抖动时Eureka不会剔除失效的服务实例,导致请求打到已经挂掉的服务上。
然后在用户服务里加上客户端注解:
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
启动之后,访问http://localhost:8761就能看到注册的服务了。那一刻的感觉,就像第一次成功跑通Hello World一样爽!
第二步:配置中心 - Spring Cloud Config
以前改个配置要重新打包部署,测试环境、预发环境、生产环境的配置文件混乱不堪。有了Config Server,一切都变得井井有条。
创建Config Server:
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
配置指向Git仓库(我们用的是公司内部的GitLab):
spring:
cloud:
config:
server:
git:
uri: https://gitlab.company.com/config-repo
username: ${GIT_USERNAME}
password: ${GIT_PASSWORD}
服务端只需要在bootstrap.yml中指定Config Server地址:
spring:
application:
name: user-service
cloud:
config:
uri: http://localhost:8888
这样,所有配置都集中管理了。再也不用担心测试同学抱怨“为什么我的配置和你不一样”。
第三步:API网关 - Spring Cloud Gateway
微服务拆分后,前端不可能直接调用各个服务的接口,需要一个统一的入口。Spring Cloud Gateway基于WebFlux,性能比Zuul好很多。
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
路由配置:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
这里的lb://前缀表示使用负载均衡,会自动从Eureka获取服务实例。
第四步:服务间调用 - OpenFeign
服务之间怎么通信?直接用RestTemplate?太low了!OpenFeign让你像调用本地方法一样调用远程服务。
在订单服务中定义用户服务的客户端:
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
然后直接注入使用:
@Service
public class OrderService {
@Autowired
private UserClient userClient;
public OrderDetail getOrderDetail(Long orderId) {
Order order = orderRepository.findById(orderId);
User user = userClient.getUserById(order.getUserId());
return new OrderDetail(order, user);
}
}
是不是很优雅?不过要注意超时配置,不然一个服务慢了,整个调用链都会被拖垮。
生产环境的那些坑
理论很美好,现实很骨感。上线过程中踩了不少坑:
坑1:服务启动顺序问题
Eureka Server必须最先启动,然后是Config Server,最后才是业务服务。我们用Docker Compose编排时,加了depends_on,但发现这只能保证容器启动顺序,不能保证应用就绪。后来用了健康检查:
services:
user-service:
depends_on:
eureka-server:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
坑2:配置刷新问题
修改配置后,需要手动触发/actuator/refresh端点。后来集成了Spring Cloud Bus + RabbitMQ,实现配置自动刷新。
坑3:熔断降级 某个第三方接口响应慢,导致整个服务线程池耗尽。引入Hystrix(虽然已停更,但够用):
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
@HystrixCommand(fallbackMethod = "getUserFallback")
User getUserById(@PathVariable("id") Long id);
default User getUserFallback(Long id) {
// 返回默认用户或抛出友好异常
return new User(id, "Unknown");
}
}
性能对比:单体 vs 微服务
上线一个月后,我们做了详细的性能对比:
| 指标 | 单体应用 | 微服务架构 |
|---|---|---|
| 部署时间 | 15分钟 | 用户服务3分钟,订单服务4分钟 |
| 故障隔离 | 全站挂掉 | 单个服务故障不影响其他 |
| 扩容粒度 | 整个应用 | 按需扩容特定服务 |
| 开发效率 | 多人协作冲突多 | 团队按服务划分,独立开发 |
| 内存占用 | 2GB | 总计3.5GB(但可精细化控制) |
虽然总体资源消耗增加了,但稳定性和开发效率提升非常明显。特别是双11期间,订单服务压力大的时候,我们只扩容了订单服务,其他服务保持不变,节省了不少服务器成本。
关于Python和代码人生的思考
说到这里,可能有人疑惑:标题里提到Python,但全文都是Java?其实我在做技术选型时,认真考虑过用Python的FastAPI重构部分服务。Python在数据处理、脚本编写方面确实无敌,我平时用Python写自动化脚本、数据分析都很顺手。
但微服务的核心在于稳定性、性能和团队协作。Java的强类型、完善的监控体系、丰富的生态,在企业级应用中还是不可替代的。我的“代码人生”哲学就是:用合适的工具解决合适的问题。写爬虫用Python,做高并发服务用Java,最近研究Rust也是想在系统编程层面有更深的理解。
给初学者的建议
如果你也准备踏上微服务之路,我的建议是:
- 不要为了微服务而微服务:先评估业务复杂度,简单的应用强行拆分只会增加运维成本
- 监控和日志先行:没有完善的监控,微服务就是一场灾难。我们集成了Prometheus + Grafana + ELK
- 自动化测试很重要:服务多了,手工测试根本来不及,CI/CD流水线必须跟上
- 团队沟通成本会增加:微服务不仅是技术问题,更是组织架构问题
最后,分享一个小技巧:用Cursor这样的AI工具时,不要完全依赖它生成的代码。特别是涉及到安全配置、数据库事务这些关键地方,一定要自己review。我之前就遇到过AI生成的Feign客户端没有处理超时,差点造成生产事故。
现在回头看,从那个周五晚上的崩溃时刻,到现在系统稳定运行,虽然过程痛苦,但收获满满。微服务不是银弹,但它确实帮我们解决了单体应用的痛点。下次技术分享会上,我打算聊聊Spring Cloud Alibaba的实践,毕竟Nacos比Eureka在国内用得更多。
对了,今天又是通勤一小时的日子,地铁上写完这篇文章,希望对你有所帮助。记住,在这个充满Bug的世界里,我们都是在不断debug中成长的程序员。

评论 0