Spring Cloud从零开始:微服务入门指南
凌晨2点,深圳南山科技园的写字楼里,还有几盏灯没灭。我叼着半包快过期的雀巢咖啡糖,盯着屏幕上密密麻麻的日志——又一个服务调用超时了。这已经是本周第三次线上告警,而明天就是产品上线deadline,PM已经在群里@我三次:“哥,稳住啊!”
我是创业公司里那种“全栈开发”,说好听点是技术多面手,说难听点就是“啥都得干、啥都得会一点”。前端崩了?我上。数据库慢?我查。K8s部署失败?还是我来。上周五晚上,老板突然把我叫到会议室,扔下一句:“咱们单体应用撑不住了,下个版本必须上微服务。”我当时差点把手中的枸杞茶喷出来——这不是逼我现学Spring Cloud吗?
但没办法,创业公司节奏快,资源少,人手紧。你不往前冲,没人替你扛。于是,熬了几个通宵,踩了一堆坑,终于把第一个微服务架子搭起来了。今天这篇文,就当是给同样被“赶鸭子上架”的兄弟们留个路标——Spring Cloud从零开始,其实没那么玄乎。
单体之痛:不是想微,而是不得不微
我们原来的系统是标准的Spring Boot单体应用:一个application.jar打天下,前后端分离,MySQL + Redis + RabbitMQ,看着挺美。可随着业务量猛增(去年双11期间QPS直接翻了5倍),问题全暴露了:
- 一个模块出Bug,整个服务挂掉;
- 发布一次要停机10分钟,运营天天投诉;
- 新功能开发互相阻塞,前端等后端,后端等DBA;
- 最致命的是——性能瓶颈没法局部优化。比如商品查询慢,你总不能为了它把整个应用重构一遍吧?
运维老王有次在晨会上吐槽:“你们这代码,跟一锅粥似的,改一行,全家抖三抖。” 虽然扎心,但确实是事实。
于是,微服务成了唯一出路。而Spring Cloud,作为国内最主流的微服务解决方案(尤其在深圳这片腾讯系扎堆的地儿,连楼下咖啡店老板都知道Eureka是干啥的),自然成了首选。
技术选型:别一上来就搞全套
很多教程一上来就给你甩Nacos + Gateway + Sentinel + Seata + Sleuth……兄弟,咱是创业公司,不是阿里云实验室!微服务不是堆组件,而是解决问题。
我最终定了一个“最小可行微服务架构”(MVP版):
| 组件 | 作用 | 是否必要 |
|---|---|---|
| Spring Boot | 基础框架 | ✅ 必须 |
| Eureka | 服务注册与发现 | ✅ 核心 |
| Feign | 声明式HTTP调用 | ✅ 简化通信 |
| Hystrix(或Resilience4j) | 熔断降级 | ⚠️ 可选但推荐 |
| Config Server | 配置中心 | ❌ 初期可用本地配置 |
注:虽然Spring官方已将Hystrix标记为维护状态,但在小团队里,它的学习成本低、效果立竿见影。等业务稳定了再迁移到Resilience4j也不迟。
动手实操:从两个服务开始
Step 1:搭建服务注册中心(Eureka Server)
新建一个Spring Boot项目,加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
主启动类加注解:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static 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 # 关闭自我保护(开发环境)
启动后访问 http://localhost:8761,看到那个熟悉的蓝色界面,心里踏实了一半。
Step 2:拆出第一个业务服务(比如 user-service)
再建一个Spring Boot项目,引入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
配置 application.yml:
spring:
application:
name: user-service # 服务名,关键!
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ # 指向注册中心
写个简单Controller:
@RestController
public class UserController {
@GetMapping("/user/{id}")
public Map<String, Object> getUser(@PathVariable Long id) {
// 实际应查DB,这里简化
return Map.of("id", id, "name", "张三", "service", "user-service");
}
}
启动后刷新Eureka页面,看到 USER-SERVICE 出现在Instances列表里——搞定!
Step 3:服务间调用(Feign)
现在假设有个 order-service 需要调用 user-service 获取用户信息。
先在 order-service 中引入Feign:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类加 @EnableFeignClients:
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
定义Feign接口:
@FeignClient(name = "user-service") // 对应user-service的spring.application.name
public interface UserClient {
@GetMapping("/user/{id}")
Map<String, Object> getUser(@PathVariable("id") Long id);
}
在OrderController中注入使用:
@RestController
public class OrderController {
@Autowired
private UserClient userClient;
@GetMapping("/order/{orderId}")
public Map<String, Object> getOrder(@PathVariable String orderId) {
Map<String, Object> user = userClient.getUser(123L);
return Map.of("orderId", orderId, "user", user);
}
}
注意:Feign默认用Ribbon做负载均衡,如果你只部署了一个user-service实例,它也会正常工作。但一旦你扩缩容,Feign+Ribbon会自动选择可用实例——这才是微服务的威力。
性能优化:别让微服务变“微龟速”
微服务拆分后,网络调用次数暴增。原来一次SQL搞定的事,现在可能要跨3个服务。我在压测时就发现,一个订单创建接口平均耗时从80ms飙升到320ms!
怎么办?几点经验:
- 合理聚合接口:避免“贫血模型”式调用。比如不要连续调用
/user/{id}、/user/{id}/profile、/user/{id}/wallet,而是提供一个/user/{id}/full接口。 - 异步化非关键路径:发短信、写日志这类操作,扔到MQ里异步处理。
- 缓存前置:在API Gateway层或Feign Client前加一层本地缓存(Caffeine),对读多写少的数据效果显著。
- 连接池调优:Feign底层用HttpURLConnection,性能一般。建议替换为OkHttp或Apache HttpClient,并调整连接池大小。
# feign + okhttp 示例
feign:
httpclient:
enabled: false
okhttp:
enabled: true
okhttp:
connection-pool:
max-idle-connections: 200
keep-alive-duration: 300s
熔断降级:别让一个服务拖垮全家
曾经有一次,user-service因为DB慢查询卡死,导致order-service所有线程都在等它响应,最后整个系统雪崩。当时真的想砸电脑。
于是赶紧加上Hystrix熔断:
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
// ...
}
@Component
public class UserClientFallback implements UserClient {
@Override
public Map<String, Object> getUser(Long id) {
// 返回兜底数据,避免级联失败
return Map.of("id", id, "name", "未知用户", "fallback", true);
}
}
同时开启Hystrix:
feign:
hystrix:
enabled: true
虽然简单,但在关键时刻能保命。后续我们会迁移到Sentinel,更细粒度的流控和熔断策略更适合生产环境。
数据库设计:每个服务独享DB
这点很多人忽略!微服务的核心原则之一是数据隔离。别再让多个服务共用一个MySQL库了。
我们拆分时做了:
- user-service → user_db
- order-service → order_db
- product-service → product_db
跨服务数据一致性怎么办?初期用最终一致性 + 补偿机制。比如创建订单时,先扣库存(product-service),再创建订单(order-service)。如果第二步失败,发一个“回滚库存”消息到MQ,由product-service消费并恢复库存。
当然,这会带来复杂度。但比起强一致性的分布式事务(比如Seata),这种方案更轻量、更适合创业公司。
运维经验:微服务不是“部署完就完事”
上线后才发现,微服务对运维要求更高:
- 日志分散:每个服务独立日志,排查问题得grep半天。解决方案:ELK统一收集,加TraceID透传。
- 配置管理:不同环境(dev/test/prod)配置不同。初期用profile区分,后期上Apollo或Nacos Config。
- 健康检查:必须暴露
/actuator/health,配合K8s Liveness Probe自动重启异常Pod。
我们在Jenkins里加了个脚本,每次发布自动检查所有服务的健康端点,有一个挂了就中断发布——再也不用半夜被电话叫醒救火了。
总结:微服务不是银弹,但值得尝试
折腾了一个月,我们的系统终于从“巨石”变成了“乐高”。虽然初期投入大、调试复杂,但好处也实实在在:
- 用户服务升级,不再影响订单流程;
- 商品模块性能优化,独立压测、独立扩容;
- 新人入职,只需熟悉1-2个服务,上手快。
最重要的是——系统有了弹性。流量来了,我只扩瓶颈服务;出Bug了,只回滚相关模块。这种掌控感,在单体时代是不敢想的。
所以,如果你也在创业公司,被老板“逼”着上微服务,别慌。从两个服务开始,用Spring Boot打底,Spring Cloud搭桥,一步步来。微服务不是目的,敏捷交付和系统稳定才是。
对了,刚写完这篇文章,PM又在群里@我:“那个……能不能顺便把支付模块也拆一下?”
我默默关掉IDE,打开招聘软件——或许,是时候招个专职后端了?
(完)

评论 0