Spring Cloud Alibaba 生产实践:一个普通一本CS狗的血泪踩坑记
大家好,我是小李,普通一本计算机专业大四在读,目前手握杭州某大厂(就不点名了,免得HR找我喝茶)的后端开发offer,等7月入职。坐标杭州,平时混迹于阿里、网易周边的咖啡馆(其实是图书馆),对分布式系统有点兴趣,但也只是“看过几篇论文 + 跑过几个Demo”那种水平。
最近三个月,趁着毕业前的空档期,我接了个外包项目练手——给一家做电商SaaS的小公司重构他们的微服务架构。原本以为就是 CRUD + Spring Boot 套个模板,结果 PM 一句“我们想上云原生”,直接把我推进了 Spring Cloud Alibaba(SCA) 的深水区。更惨的是,上线 deadline 是618大促前一周,而今天已经是5月20号……没错,我正在用情人节的夜晚调 Nacos 配置中心的灰度发布。
这篇文章不是教程,也不是官方文档复读机,纯粹是一个即将社畜的“代码人生”实战复盘。如果你也在用 SCA,或者正被产品经理逼着搞微服务,希望我的踩坑经验能帮你少熬两个通宵。
为什么是 Spring Cloud Alibaba?
先说背景:老系统是单体架构,Spring MVC + MyBatis,数据库 MySQL 单实例,部署在一台 4C8G 的 ECS 上。去年双11直接崩了,老板震怒,要求“必须拆微服务,要高可用、可扩展、能扛流量”。
团队里没人搞过生产级微服务,但 HR 招人慢,领导一拍脑袋:“小李不是学分布式吗?你来牵头!”
我:???我简历上写的是“了解CAP理论”,不是“精通PaaS平台”啊!
选型时对比了 Spring Cloud Netflix(Eureka + Hystrix)、Dubbo + Nacos,最后定了 Spring Cloud Alibaba,原因很现实:
- 公司服务器都在阿里云,Nacos、Sentinel、Seata 这些组件天然集成,运维成本低
- 团队 Java 技术栈,SCA 基于 Spring Boot,学习曲线平缓
- 网易隔壁组也在用,遇到问题还能“借”他们运维帮忙(后来发现他们也在摸着石头过河)
但理想很丰满,现实……emmm,后面你会看到。
第一个坑:Nacos 注册中心连不上?
项目启动第一天,我就被 com.alibaba.nacos.api.exception.NacosException: failed to req API 打脸。本地跑得好好的,一上测试环境就报错。
查了半天,发现是 Nacos 客户端版本和服务器不匹配!我们用了 SCA 2022.0.0.0(对应 Spring Boot 3.x),但测试环境的 Nacos 还是 1.4.2 —— 运维说“这版本稳定,别乱升级”。
行吧,降级 SCA 到 2021.0.1.0(支持 Nacos 2.0+),结果又遇到 gRPC 端口没开的问题。Nacos 2.0 默认启用了 gRPC 通信(9848/9849),但安全组只放行了 8848。
💡 生产建议:
- Nacos 一定要用 集群模式(至少3节点),别信“单机够用”的鬼话
- 客户端和服务端版本严格对齐,看 SCA 官方版本说明
- 开通安全组时,别忘了 9848/9849(客户端 gRPC)、7848(Jraft)
# bootstrap.yml
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: nacos-cluster.example.com:8848
namespace: prod # 别用 public!
group: DEFAULT_GROUP
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml
namespace 和 group 一定要配!不然所有服务挤在 public 里,测试、预发、生产配置全混一起,迟早出事。
配置中心:动态刷新别信 @RefreshScope
需求来了:运营要能实时改商品折扣率,不能重启服务。
我信心满满地加上 @RefreshScope,改完 Nacos 配置,结果……没生效?!
翻源码才发现:@RefreshScope 只对 Spring Bean 有效,且必须是懒加载。如果 Bean 在启动时就被其他组件注入并调用,那它还是旧值。
更坑的是,@RefreshScope 性能很差,每次刷新都会重建整个 Bean,高并发下可能 OOM。
解决方案:用 Nacos Config 监听器手动处理。
@Component
public class DiscountConfig {
private volatile double discountRate = 0.9;
@Autowired
private ConfigService configService;
@PostConstruct
public void init() throws NacosException {
// 监听配置变化
configService.addListener("discount.properties", "DEFAULT_GROUP", new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
Properties props = new Properties();
try {
props.load(new StringReader(configInfo));
discountRate = Double.parseDouble(props.getProperty("discount.rate", "0.9"));
log.info("Discount rate updated to: {}", discountRate);
} catch (Exception e) {
log.error("Failed to parse discount config", e);
}
}
@Override
public Executor getExecutor() {
return null; // 使用默认线程池
}
});
}
public double getDiscountRate() {
return discountRate;
}
}
这样性能好、可控性强,还能加日志和异常兜底。别再无脑用 @RefreshScope 了,尤其在核心链路上!
服务调用:OpenFeign + Sentinel 熔断实战
微服务拆分后,订单服务要调用库存服务。用 OpenFeign 很方便:
@FeignClient(name = "inventory-service", fallback = InventoryFallback.class)
public interface InventoryClient {
@PostMapping("/deduct")
Result<Boolean> deductStock(@RequestBody DeductRequest request);
}
但大促时库存服务一抖,订单服务线程池就爆了。这时候 Sentinel 就派上用场了。
关键配置:
# application.yml
feign:
sentinel:
enabled: true # 开启 Feign 对 Sentinel 的支持
spring:
cloud:
sentinel:
transport:
dashboard: sentinel-dashboard.example.com:8080
datasource:
ds1:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-sentinel-rules
groupId: SENTINEL_GROUP
rule-type: flow # 流控规则
然后在 Nacos 里建一个 order-service-sentinel-rules 配置:
[
{
"resource": "POST:http://inventory-service/deduct",
"limitApp": "default",
"grade": 1,
"count": 50,
"strategy": 0,
"controlBehavior": 0
}
]
意思是:对库存服务的调用,QPS 超过 50 就熔断,走 fallback。
但这里有个巨坑:Sentinel 默认的 fallback 是返回 null,如果上层没判空,直接 NPE!所以我们统一包装了 Result 对象,并在 fallback 里记录日志 + 返回友好提示。
@Component
public class InventoryFallback implements InventoryClient {
@Override
public Result<Boolean> deductStock(DeductRequest request) {
log.warn("Inventory service is down, request: {}", request);
return Result.fail("库存服务繁忙,请稍后再试");
}
}
上线后,618压测时库存 DB 慢查询导致超时,Sentinel 成功保护了订单服务,没让雪崩发生。那一刻,我觉得自己像个真正的后端工程师了(虽然工资还没发)。
分布式事务:Seata 别乱用
最头疼的是下单流程:扣库存 + 创建订单 + 扣优惠券。三个服务,必须原子性。
我一开始想上 Seata 的 AT 模式,结果发现:
- 老系统用的 MyBatis-Plus,自动生成 ID,Seata 的 undo_log 表主键冲突
- MySQL 5.7 不支持 savepoint,AT 模式回滚效率低
- 最致命:Seata Server 单点故障,我们没资源搭 HA
最后妥协方案:最终一致性 + 本地消息表。
- 订单服务创建订单(状态为“待支付”)
- 同步调用库存服务扣库存(失败则订单取消)
- 异步发送 MQ 消息给优惠券服务
- 优惠券服务消费消息,失败则重试 + 告警
虽然不完美,但简单可靠。记住:不是所有场景都需要强一致,业务允许的情况下,最终一致性更稳。
Go?你说 Go?
哦对,标题里有 Go。其实我们内部有个日志采集 Agent 是用 Go 写的(运维大佬的杰作),通过 Sidecar 模式部署,把应用日志实时推到 ES。Spring Boot 应用本身还是 Java,但 SCA 组件(如 Nacos、Sentinel)都提供了 Go SDK,未来如果要用 Go 重写某个服务,也能无缝接入。
不过对我这种 Java 狗来说,Go 还是太遥远了。先把 SCA 玩明白再说吧。
性能与监控:别等线上爆炸才看指标
上线前,我们做了三件事:
- Arthas 接入:随时在线诊断 JVM、线程、方法耗时
- Prometheus + Grafana:监控 Nacos 注册实例数、Sentinel QPS、JVM GC
- 日志规范:所有接口打印 traceId,用 MDC 透传
特别强调:一定要监控 Nacos 的连接数!我们有一次因为客户端没关 watchListener,导致 Nacos 内存打满,全站注册实例掉线。血的教训。
| 指标 | 正常范围 | 告警阈值 |
|---|---|---|
| Nacos 客户端连接数 | < 500/节点 | > 800 |
| Sentinel Block QPS | 0 | > 10 |
| Feign 调用 RT | < 200ms | > 1s |
写在最后:代码人生,就是不断填坑
从4月接到需求,到5月底压测通过,我掉了5斤肉,写了30+个 Nacos 配置,debug 了无数个 NullPointerException。但当看到 618 大促当天系统稳如老狗,QPS 从 200 扛到 3000+,心里还是有点小骄傲的。
Spring Cloud Alibaba 不是银弹,但它在阿里云生态下确实降低了微服务落地的门槛。关键是要 理解每个组件的原理,别盲目 copy-paste。
作为即将入职的新人,这次实战让我明白:学校教的是理论,生产教的是敬畏。每一行配置背后,都可能是线上事故;每一个 fallback,都是对用户的负责。
如果你也在杭州,欢迎约咖啡(我请,反正快领工资了)。如果你也在用 SCA,评论区聊聊你的踩坑故事?
代码人生,继续搬砖。
P.S. 本文所有代码已脱敏,如有雷同,纯属巧合。
P.P.S. 产品经理今天又提了新需求:“能不能加个 AI 推荐?” 我:……(默默打开了招聘软件)

评论 0