Spring Cloud Alibaba 生产实践:从“能跑就行”到稳如老狗的性能优化之路

极客生活家
2025-12-16 21:11
阅读 489

上周五晚上十一点半,我刚把最后一行配置推到 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 就完事了,其实远远不够。

我们做了三件事:

  1. 动态规则持久化:默认规则存在内存里,服务一重启就没了。我们改成了 Nacos 持久化规则,通过 sentinel-datasource-nacos 模块实现。
  2. 热点参数限流:比如 /user/profile/{userId},防止恶意刷某个高权重用户。
  3. 熔断降级策略精细化:不是所有异常都该熔断,我们只对 TimeoutExceptionDbConnectionException 触发熔断。

关键配置如下:

// 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 的全局锁在高并发下成了性能瓶颈。

后来我们做了两件事:

  1. 异步清理 undo_log:写了个定时任务,每天凌晨删除 7 天前的成功事务记录。
  2. 关键路径改用 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


数据库与接口设计:别让微服务变“分布式单体”

很多团队以为拆成微服务就万事大吉,结果数据库还是一个大宽表,接口动不动传几百个字段。我们在设计时坚持两个原则:

  1. 每个服务独享数据库,禁止跨库 JOIN。用 Event-driven 架构同步状态(RocketMQ + Canal)。
  2. 接口契约先行:用 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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝