Spring Cloud从零开始:微服务入门指南
上周五晚上,我坐在深圳南山科技园的办公室里,窗外是熟悉的腾讯滨海大厦灯光。时间刚过9点,工位上还剩我和两个后端兄弟——没错,又是被线上告警拉回来救火的一天。事情起因是某个老单体应用在大促期间直接崩了,CPU飙到100%,数据库连接池打满。运维小哥在群里@我们:“兄弟们,这玩意儿再挂一次,明天老板就要请大家喝喜茶(其实是HR约谈)了。”
那一刻,我这个前端老油条突然意识到:不懂微服务,迟早要被时代甩下车。
虽然我主攻前端,但咱公司最近搞“全栈能力建设”,领导说“前端也要懂后端架构”。再加上最近在研究Rust(对,就是那个号称内存安全又高性能的新贵),让我对系统级设计越来越感兴趣。于是,我决定从头啃一啃Spring Cloud——毕竟,咱公司后端全是Java系,而Spring Cloud几乎是国内微服务的事实标准。
为啥不是Go或Python?
有朋友可能会问:现在不是流行用Go写微服务吗?Python不也能搞?我一开始也这么想。
但现实很骨感。咱们二线互联网公司,技术栈切换成本极高。你让一个跑了5年的Java单体应用,说切就切到Go,DBA第一个不同意。而且我们团队后端清一色Java老兵,Go对他们来说等于重新学语言,PM听了都摇头:“排期?至少半年。”
所以,务实的选择是:在现有Java生态里升级架构。Spring Boot + Spring Cloud 这套组合拳,学习曲线平缓,社区成熟,文档齐全,还有大量现成的starter——对我们这种既要快速上线又要稳如老狗的团队,简直是救命稻草。
顺便提一句,最近面试好几个候选人,问到微服务拆分原则、服务注册发现机制,一半人支支吾吾。看来“微服务”这三个字,真成了高频面试题。
从单体到微服务:我的“血泪”拆分实践
我们的老系统叫 order-center,集成了订单创建、支付回调、库存扣减、物流跟踪……所有功能塞在一个WAR包里。每次发版,测试都要跑300+用例,上线窗口必须卡在凌晨2点。
目标很明确:拆!
第一步:识别边界上下文
我拉着后端大佬和产品开了三天会(期间喝了8杯瑞幸),用DDD的思想画出业务子域:
- 订单服务(Order Service):管创建、查询、状态机
- 库存服务(Inventory Service):管商品库存扣减与回滚
- 支付服务(Payment Service):对接第三方支付,处理异步回调
每个服务独立数据库,彻底解耦。这里有个坑:不要按技术层拆,要按业务能力拆!曾经有新人把“所有DAO”抽成一个服务,结果调用链爆炸,事务无法保证。
第二步:搭建Spring Cloud骨架
我们选了Spring Boot 2.7 + Spring Cloud 2021.0.5(兼容性好,别问我怎么知道的,踩过版本地狱的坑)。
核心组件清单:
| 组件 | 作用 | 替代方案 |
|---|---|---|
| Eureka | 服务注册与发现 | Consul, Nacos |
| Feign | 声明式HTTP客户端 | RestTemplate, WebClient |
| Ribbon | 客户端负载均衡 | 已集成进Feign |
| Hystrix | 熔断降级 | Resilience4j(新项目推荐) |
| Config Server | 配置中心 | Apollo, Nacos |
注:Hystrix官方已停止维护,但老项目还在用。新项目建议上Resilience4j + Micrometer监控。
搭建Eureka Server(服务注册中心)
# eureka-server/src/main/resources/application.yml
server:
port: 8761
eureka:
client:
register-with-eureka: false # 自己不注册自己
fetch-registry: false # 不拉取注册表
server:
enable-self-preservation: false # 关闭自我保护(开发环境)
启动后访问 http://localhost:8761,看到那个熟悉的红色警告页面——别慌,这是Eureka的默认UI。
订单服务注册自己
# order-service/application.yml
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
加上注解:
@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
启动后,刷新Eureka页面,看到 ORDER-SERVICE 出现在Instances列表里——那一刻,我仿佛看到了微服务世界的曙光。
服务间调用:Feign真香!
以前在单体里,调库存直接@Autowired InventoryDao。现在库存是独立服务,怎么办?
答案:Feign + 接口定义。
先在 inventory-service 里暴露REST API:
@RestController
@RequestMapping("/api/inventory")
public class InventoryController {
@PostMapping("/deduct")
public ResponseEntity<Boolean> deduct(@RequestBody DeductRequest request) {
// 扣减逻辑
return ResponseEntity.ok(inventoryService.deduct(request.getProductId(), request.getAmount()));
}
}
然后在 order-service 中定义Feign Client:
@FeignClient(name = "inventory-service", fallback = InventoryClientFallback.class)
public interface InventoryClient {
@PostMapping("/api/inventory/deduct")
Boolean deduct(@RequestBody DeductRequest request);
}
别忘了开启Feign:
@EnableFeignClients(basePackages = "com.example.order.client")
调用时直接注入:
@Service
public class OrderService {
@Autowired
private InventoryClient inventoryClient;
public void createOrder(Order order) {
// ...校验、保存订单
boolean success = inventoryClient.deduct(new DeductRequest(order.getProductId(), order.getAmount()));
if (!success) {
throw new InsufficientStockException("库存不足");
}
}
}
注意:这里有个经典坑——Feign默认不支持传递请求头(比如用户Token)。解决方案是在配置里加 RequestInterceptor,否则下游服务拿不到认证信息,直接403。
熔断与降级:别让雪崩毁掉整个系统
去年双11,支付服务因为第三方接口超时,导致订单服务线程池被打满,最后整个系统雪崩。当时真的想砸电脑。
这次我们上了Hystrix熔断:
@Component
public class InventoryClientFallback implements InventoryClient {
@Override
public Boolean deduct(DeductRequest request) {
// 降级策略:记录日志 + 返回假成功(后续走补偿任务)
log.warn("Inventory service unavailable, fallback triggered for product: {}", request.getProductId());
return true; // 注意:这里只是示例,实际需谨慎设计
}
}
同时配置超时和熔断阈值:
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 # 超时3秒
circuitBreaker:
requestVolumeThreshold: 10 # 10秒内10次失败就熔断
errorThresholdPercentage: 50 # 错误率50%触发熔断
生产建议:降级逻辑一定要幂等且可追溯!我们后来加了“库存补偿任务”,定时扫描异常订单,人工介入处理。
配置中心:告别硬编码
以前改个数据库密码,要重新打包部署。现在用Config Server,配置集中管理。
# config-server/application.yml
spring:
cloud:
config:
server:
git:
uri: https://gitlab.company.com/config-repo
username: ${GIT_USER}
password: ${GIT_PASSWORD}
各服务通过 bootstrap.yml 拉取配置:
# order-service/bootstrap.yml
spring:
application:
name: order-service
cloud:
config:
uri: http://config-server:8888
这样,改个Redis地址,只需推一下Git,服务自动刷新(配合@RefreshScope)。
性能与监控:微服务不能只靠“感觉”
拆完服务,性能到底好不好?光看TPS不够,得看调用链。
我们接入了SkyWalking:
- 自动追踪Feign调用
- 可视化服务拓扑
- 慢接口告警
有一次发现订单创建平均耗时800ms,查调用链发现是库存服务查DB慢。原来是没加索引!赶紧让DBA加了个 (product_id, warehouse_id) 联合索引,耗时降到80ms。
微服务时代,可观测性就是生命线。
综合对比:微服务 vs 单体
| 维度 | 单体应用 | 微服务 |
|---|---|---|
| 开发效率 | 初期快,后期慢 | 初期慢,后期快 |
| 部署频率 | 低(全量发布) | 高(独立发布) |
| 故障隔离 | 差(一处崩全挂) | 好(熔断限流) |
| 技术栈 | 单一 | 多样(但咱没敢用) |
| 运维复杂度 | 低 | 高(需配套监控/日志) |
对我们这种中型团队,微服务是“不得不为”的选择——业务增长太快,单体已经扛不住了。
写在最后:前端视角的反思
作为前端,深入后端微服务架构后,我反而更理解API设计的重要性了。以前总吐槽后端接口字段命名不规范、没文档,现在自己参与设计,才知道好的接口契约是前后端协作的基石。
另外,微服务不是银弹。如果你的业务还没到一定规模,强行拆分只会增加复杂度。记住那句老话:“当你的单体应用还能跑,就别急着微服务。”
不过话说回来,要是哪天公司真用Go重写核心服务……我是不是该把Rust先放一放,去学Gin框架了?🤔
附:避坑清单(血泪总结)
- ❌ 不要在Feign调用里传大对象(容易OOM)
- ❌ 服务间不要循环依赖(A调B,B又调A)
- ✅ 所有对外接口必须有版本号(
/v1/order) - ✅ 数据库事务只能在单个服务内保证,跨服务用Saga或TCC
- ✅ 日志必须带TraceID,否则排查问题像大海捞针
好了,今天就写到这儿。凌晨1点了,该回家了。希望这篇实战笔记能帮到正在微服务路上挣扎的你。
P.S. 如果你们公司还在用Dubbo,评论区聊聊?我也好奇阿里系是怎么玩的。

评论 0