Spring Cloud Alibaba 生产实践:从“能跑就行”到稳如老狗的性能优化之路
上周五晚上十一点半,我刚把最后一行配置推到 GitLab,合上 MacBook 的时候,脑子里还在回放过去三个月里那几次半夜被 PagerDuty 喊醒的噩梦。说来也巧,就在我准备离职、全身心投入创业前夜,团队终于把基于 Spring Cloud Alibaba(SCA)的新一代微服务架构稳定上线了。今天写这篇文章,不为别的,就当是给过去几年在阿里系技术栈里摸爬滚打的日子画个句号,顺便给还在踩坑的兄弟们留点干货。
我是谁?前某二线大厂技术总监,坐标杭州未来科技城,日常用 Vim 写代码(别问,问就是肌肉记忆),最近刚裸辞准备搞自己的 SaaS 项目。如果你也在考虑用 SCA 搞生产级系统,这篇你得看完——因为里面的每一个坑,都是我拿头发换来的。
为啥非要用 Spring Cloud Alibaba?
时间拉回到去年双11前一个月,老板突然拍板:“我们要做全域营销中台,618必须上线!” 产品经理拿着 PPT 画了一堆“用户旅程触点闭环”,测试同学已经开始写用例了,而我们后端团队还在纠结:用 Spring Cloud Netflix 还是直接上 SCA?
说实话,当时我对 Nacos、Sentinel 这些玩意儿还停留在“听说过”的阶段。但现实很骨感:公司用的是阿里云,ECS + SLB + RDS 都是阿里全家桶,硬上 Netflix 套件等于自找麻烦。而且,领导一句话扎心了:“你们不是天天吹‘贴近业务’吗?现在连注册中心都要跨云调用?”
于是,我们咬牙上了 SCA。Spring Boot 2.7 + Spring Cloud Alibaba 2021.0.5.0,搭配 Nacos 2.2、Sentinel 1.8、Seata 1.5,一套组合拳打下来,初期真是一地鸡毛。
第一个坑:Nacos 注册中心的“假死”现场
刚上线那会儿,服务偶尔会“失联”。监控显示某个实例 CPU 突然飙到 100%,日志里全是:
com.alibaba.nacos.client.naming.remote.http.NamingHttpClient
- [NA] failed to request http://nacos:8848/nacos/v1/ns/instance/list
运维小哥一脸懵:“网络没问题啊,Nacos 三节点集群稳得很。”
我翻了半小时源码,终于发现问题出在 客户端心跳间隔配置不当。
默认情况下,Nacos 客户端每 5 秒发一次心跳,但我们的服务部署在抢占式实例上(省钱嘛,你懂的),网络抖动频繁。一旦连续 3 次心跳失败,Nacos 就会把实例标记为不健康。更要命的是,Spring Cloud 默认的 spring.cloud.nacos.discovery.heartbeat.interval 并不会自动重试注册!
解决方案?手动调优 + 健康检查兜底。
我们在 application.yml 里加了这些配置:
spring:
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER:localhost:8848}
# 心跳间隔(秒)
heartbeat-interval: 3
# 心跳超时(秒),超过则标记不健康
ip-delete-timeout: 30
# 关键!开启自动注册重试
ephemeral: true
同时,在启动脚本里加了个简单的健康检查钩子:
# 启动后每10秒检查一次是否注册成功,失败则重启
while ! curl -s http://nacos:8848/nacos/v1/ns/instance?serviceName=${APP_NAME} | grep $(hostname -i); do
echo "Not registered, retrying..."
sleep 10
done
上线后,注册成功率从 92% 提升到 99.98%。运维终于不用半夜接我的电话了(他请我喝了杯瑞幸,值了)。
Sentinel:限流不是摆设,是保命符
还记得那次线上事故吗?某个新接口没做限流,结果被爬虫盯上,QPS 瞬间干到 5000+,数据库连接池直接爆掉。DBA 在群里疯狂@我:“再这样我就删库跑路了!”
从那以后,Sentinel 成了我们项目的标配。但很多人以为加个 @SentinelResource 就完事了,其实远远不够。
我们做了三件事:
- 动态规则持久化:默认规则存在内存里,服务一重启就没了。我们改成了 Nacos 持久化规则,通过
sentinel-datasource-nacos模块实现。 - 热点参数限流:比如
/user/profile/{userId},防止恶意刷某个高权重用户。 - 熔断降级策略精细化:不是所有异常都该熔断,我们只对
TimeoutException和DbConnectionException触发熔断。
关键配置如下:
// SentinelConfiguration.java
@Configuration
public class SentinelConfig {
@PostConstruct
public void initRules() {
// 从 Nacos 加载流控规则
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource =
new NacosDataSource<>("${nacos.server}", "SENTINEL_GROUP", "flow-rules", source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleAAAAAAAAAAA);
}
}
而在 Nacos 里,我们维护了一个 JSON 配置:
[
{
"resource": "GET:/api/order/detail",
"limitApp": "default",
"grade": 1,
"count": 100,
"strategy": 0,
"controlBehavior": 0
}
]
这里的
grade=1表示 QPS 模式,count=100是阈值。别小看这 100,它救了我们三次大促。
Seata 分布式事务:别被“AT模式”忽悠了
创业前最后一次架构评审,CTO 问我:“你们怎么保证订单和库存的一致性?”
我说:“用 Seata AT 模式啊,自动回滚,多省事。”
他冷笑一声:“那你有没有算过 undo_log 表的膨胀速度?”
当场社死。
回去一查,果然:每天 50 万订单,undo_log 表三天就涨到 2000 万行,MySQL 主从延迟直接拉到 30 秒。更可怕的是,Seata 的全局锁在高并发下成了性能瓶颈。
后来我们做了两件事:
- 异步清理 undo_log:写了个定时任务,每天凌晨删除 7 天前的成功事务记录。
- 关键路径改用 Saga 模式:对于非强一致场景(比如发优惠券),用状态机 + 补偿事务,彻底绕开数据库锁。
Seata 配置也做了优化:
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
client:
rm:
report-success-enable: false # 减少上报开销
async-commit-buffer-limit: 1000
tm:
commit-retry-count: 3
rollback-retry-count: 3
顺便吐槽一句:Seata 的文档真的反人类,很多参数藏在 GitHub issue 里,建议直接看源码。
性能优化:从工具链开始
作为 Vim 党,我对 IDE 自带的“智能提示”毫无兴趣,但我对 命令行工具和监控体系 极度敏感。在 SCA 项目里,我们搭建了一套轻量但高效的观测栈:
| 工具 | 用途 | 替代方案痛点 |
|---|---|---|
| Arthas | 线上诊断、热更新 | 重启服务成本太高 |
| Prometheus + Grafana | 指标采集与可视化 | 自定义指标难集成 |
| SkyWalking | 链路追踪 | Zipkin 在高并发下丢数据 |
| JMeter + Gatling | 压测 | Locust 对 Java 支持弱 |
举个例子,上周我们发现某个接口 P99 延迟高达 2s。用 Arthas 一查:
$ watch com.xxx.service.OrderService createOrder '{params, returnObj, throwExp}' -x 3
结果发现是 Nacos 配置监听器触发了全量 Bean 刷新!原来我们在 @RefreshScope 里注入了太多依赖,每次配置变更都会重建整个上下文。
解法:拆分配置类,只对真正需要动态刷新的字段加 @Value,避免滥用 @RefreshScope。
数据库与接口设计:别让微服务变“分布式单体”
很多团队以为拆成微服务就万事大吉,结果数据库还是一个大宽表,接口动不动传几百个字段。我们在设计时坚持两个原则:
- 每个服务独享数据库,禁止跨库 JOIN。用 Event-driven 架构同步状态(RocketMQ + Canal)。
- 接口契约先行:用 OpenAPI 3.0 定义 DTO,前端和后端并行开发,避免“你改我改大家改”。
比如订单服务返回的结构:
public class OrderDTO {
private Long id;
private String orderNo;
private BigDecimal amount;
// 不返回 user 对象!只返 userId
private Long userId;
// 状态用枚举,前端自己映射文案
private OrderStatus status;
}
这样既减少网络传输,又避免循环依赖。测试同学终于不用再抱怨“接口字段又变了”。
最后:为什么我敢裸辞创业?
因为在搞定这套 SCA 架构后,我意识到:现代 Java 微服务的核心,不是框架本身,而是对“可观测性、弹性、自动化”的极致追求。
Spring Boot 让我们快速启动,Spring Cloud Alibaba 让我们无缝对接云原生,但真正决定系统生死的,是你对每一个线程、每一次 RPC、每一行 SQL 的敬畏。
现在,我的创业项目也在用这套技术栈。虽然规模小得多,但原则不变:能压测的才敢上线,能监控的才叫服务,能自动恢复的才配叫系统。
如果你也在杭州,或者正被 SCA 折磨,欢迎加我微信聊聊(Vim 用户优先 😎)。创业不易,但至少,咱们写的代码得对得起自己那几根剩下的头发。
附:生产环境关键参数参考表
| 组件 | 参数 | 推荐值 | 说明 |
|---|---|---|---|
| Nacos Client | heartbeat.interval | 3s | 心跳间隔 |
| Nacos Client | ip.delete.timeout | 30s | 实例删除超时 |
| Sentinel | csp.sentinel.flow.cold.factor | 3 | 冷启动因子 |
| Seata | client.rm.report.success.enable | false | 关闭成功事务上报 |
| Tomcat | max-threads | 200 | 避免线程过多导致上下文切换 |
| JVM | -XX:+UseG1GC | — | 大堆内存推荐 G1 |
注:以上参数需根据实际负载调整,切勿照搬。
本文首发于个人博客,转载请注明出处。代码已脱敏,事故细节略有夸张,但情绪真实。

评论 0