我是怎么被逼着把单体项目拆成Spring Cloud微服务的
去年10月底,上海刚入秋,公司楼下那家瑞幸已经排到马路牙子了。我还在改一个祖传Java单体应用的订单超时逻辑——没错,就是那种启动要5分钟、改一行代码全组提心吊胆、测试一跑就崩的“史诗级”项目。产品经理跑来问:“能不能加个实时库存预警功能?”我差点把咖啡泼他脸上。
但转头一看排期:双11大促前两周。得,不重构不行了。
于是,在一个周五晚上九点、戴着AirPods循环播放Lo-fi beats(边写代码边听音乐是我最后的倔强)、出租屋里泡面都凉透的时刻,我终于下定决心:干!上Spring Cloud!
为什么不是Dubbo?也不是K8s原生?
其实我们团队之前试过Dubbo,配置复杂不说,文档还老是“看源码吧”。而K8s虽然香,但运维成本太高,我们小团队连专职SRE都没有,光是Ingress和Service Mesh就能把人搞疯。
后来我折腾了一圈市面上各种AI编程工具——GitHub Copilot太泛,CodeWhisperer对中文支持拉胯,Windsurf倒是挺新潮,但生成的YAML文件经常缺胳膊少腿。最后还是回到了Cursor:它不仅能理解我的上下文(比如我正在写Eureka配置),还能根据注释自动补全整个Feign客户端,关键是——它懂中文报错!
说回正题。Spring Cloud这套全家桶,虽然被吐槽“重”,但胜在生态成熟、文档齐全、社区活跃。尤其对我们这种既要快速上线又要保证稳定的中小团队来说,简直是救命稻日晚间八点档。
Moltbot:我们的第一个微服务实验田
我们决定拿一个边缘业务先练手:Moltbot——一个内部用的机器人消息推送服务。它原本是嵌在主应用里的一个模块,每天调用量不到五千次,逻辑也简单:接收事件 → 查用户 → 发钉钉/企业微信。
目标很明确:把它独立出来,做成一个可独立部署、自动扩缩容、带熔断降级的微服务。
第一步:注册中心选型
Spring Cloud默认推荐Eureka。虽然Consul、Nacos也行,但我们评估下来:
| 方案 | 部署难度 | 自动注销 | 中文文档 | 与Spring Boot集成 |
|---|---|---|---|---|
| Eureka | ⭐⭐ | 支持 | 一般 | 原生支持 |
| Nacos | ⭐⭐⭐ | 支持 | 丰富 | 需额外starter |
| Consul | ⭐⭐⭐⭐ | 需脚本 | 较少 | 社区版支持弱 |
最终选了Eureka——毕竟我们图的是快。本地起个@EnableEurekaServer,三行配置搞定。上线后发现一个问题:服务重启时,旧实例没及时下线,导致调用方偶尔404。
坑点来了:Eureka默认90秒才剔除失效实例!我们改成了:
eureka:
instance:
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 10
server:
enable-self-preservation: false # 关闭自我保护,否则宕机也不剔除
这波操作后,服务上下线几乎实时生效。测试同学终于不用每次部署完狂刷“是不是又挂了”。
Feign + Ribbon:别再自己拼URL了!
以前调内部接口,都是这样写的:
String url = "http://user-service/api/v1/users/" + userId;
RestTemplate.getForObject(url, User.class);
结果某天user-service改了端口,三个服务同时炸掉。运维半夜打电话骂我:“你咋不写服务发现?”
现在用Feign,优雅多了:
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/api/v1/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
配合Ribbon做负载均衡,天然支持轮询。但要注意:Feign默认没有超时控制!有一次user-service慢查询拖垮了Moltbot,线程池直接打满。
解决方案:加上Hystrix(虽然后来官方移除了,但我们还在用)或者直接用Spring Cloud LoadBalancer + Resilience4j。我们现在用的是后者:
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 5000
顺便提一句,千万别在Feign接口里传复杂对象当GET参数!Spring会序列化失败。血泪教训。
配置中心:告别application-prod.yml地狱
以前每个环境都要维护一套YAML,改个数据库密码得提四个PR。现在统一用Spring Cloud Config + Git后端。
结构如下:
config-repo/
├── moltbot-dev.yml
├── moltbot-test.yml
└── moltbot-prod.yml
服务启动时自动拉取对应环境的配置。关键是要配好加密!我们用JCEKS密钥库把数据库密码加密:
spring:
cloud:
config:
server:
git:
uri: https://gitlab.internal/config-repo
username: ${GIT_USER}
password: ${GIT_TOKEN}
encrypt:
key-store:
location: classpath:/keystore.jks
password: changeit
alias: myKey
上线第一天,我就忘了把encrypt.key加到K8s Secret里,服务启动直接报错:“Decryption failed”。当时真的想砸电脑……后来写了自动化检测脚本,CI阶段就验证配置是否能解密。
网关:Zuul已死,Gateway当立
最初我们用Zuul做路由,结果压测时TPS卡在300上不去。查了半天发现Zuul是阻塞IO!换成Spring Cloud Gateway后,性能直接翻倍。
Moltbot的路由规则很简单:
spring:
cloud:
gateway:
routes:
- id: moltbot-route
uri: lb://moltbot-service
predicates:
- Path=/api/moltbot/**
注意两点:
lb://表示走服务发现(LoadBalancer)- 路径匹配要用
/**,否则POST请求体可能丢
另外,我们在网关层加了全局日志:
@Component
public class LoggingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath().value();
log.info("Request: {} {}", exchange.getRequest().getMethod(), path);
return chain.filter(exchange).then(Mono.fromRunnable(() ->
log.info("Response status: {}", exchange.getResponse().getStatusCode())
));
}
}
现在排查问题快多了——再也不用去翻七八个服务的日志。
监控与链路追踪:别让Bug藏在黑盒里
微服务最怕“调用链断裂”。我们集成了Sleuth + Zipkin,一行依赖搞定:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
所有请求自动带上traceId和spanId,日志长这样:
2023-11-02 20:15:33.123 INFO [moltbot-service,abc123,def456] ...
Zipkin UI里能看到完整调用链:
gateway → moltbot → user-service → notify-service
上周五,测试报了个偶发失败。我在Zipkin里搜traceId,发现卡在notify-service的数据库连接池满了。原来是没配HikariCP的最大连接数!赶紧加上:
spring:
datasource:
hikari:
maximum-pool-size: 20
生产环境教训:永远不要相信默认配置。
数据库设计:每个服务管好自己的地盘
Moltbot只存两条表:message_template 和 send_record。关键原则:绝不跨服务查表!
以前有人想直接从Moltbot查user表,被我当场驳回。现在所有用户信息都通过Feign调用user-service获取。虽然多一次网络开销,但换来的是边界清晰、独立演进。
接口设计也遵循RESTful规范:
- GET /templates → 获取模板列表
- POST /messages → 创建推送任务
- PUT /messages/{id}/retry → 重试失败消息
返回统一格式:
{
"code": 200,
"data": { ... },
"msg": "success"
}
前端同学终于不用猜哪个字段叫errorCode哪个叫errCode了。
部署与运维:从Docker到K8s的平滑过渡
本地开发用Docker Compose一键启停:
version: '3'
services:
eureka:
image: springcloud/eureka-server
moltbot:
build: .
environment:
- SPRING_PROFILES_ACTIVE=dev
生产环境跑在K8s上,每个服务一个Deployment + Service。关键配置:
- 就绪探针:检查Eureka注册是否成功
- 存活探针:检查/actuator/health
- 资源限制:CPU 500m,内存 1G(实测峰值才用300m)
上线流程:
- GitLab CI 构建镜像并推送到Harbor
- ArgoCD 自动同步K8s清单
- 灰度发布:先切5%流量,观察10分钟
双11那天,Moltbot QPS飙到2000,CPU稳如老狗。运维拍我肩膀:“小伙子,可以啊。”
总结:微服务不是银弹,但值得拥有
折腾三个月,Moltbot从单体模块蜕变成标准微服务。带来的好处很明显:
✅ 独立部署,不再牵一发而动全身
✅ 自动扩缩容,扛住流量洪峰
✅ 故障隔离,一个服务崩不影响全局
当然,代价也有: ❌ 运维复杂度上升(得会看Trace、调配置) ❌ 网络延迟增加(不过内网基本可忽略) ❌ 学习曲线陡峭(新人入职先啃三天Spring Cloud文档)
但总体来看,对于中等规模、需要快速迭代的业务,Spring Cloud依然是性价比最高的选择。尤其是配合Cursor这类智能工具,写配置、调接口的效率提升不止一倍。
最后说句掏心窝子的话:别为了微服务而微服务。如果你们业务简单、团队小、变化少,单体应用+模块化可能更香。技术选型,终究是为了让人活得轻松一点——至少让我能在晚上十点前关掉电脑,去楼下买杯热美式,而不是对着一堆烂摊子熬夜。
对了,Moltbot现在稳定运行中。上周产品经理又来找我:“能不能加个邮件通知?”
我笑了笑:“行啊,下周上线。”
心里OS:这回,可不怕了。

评论 0