从硬件狗到Go仔,我在Java微服务里翻车又爬起

不想写日报
2026-01-04 12:15
阅读 1474

去年双11前夜,我正戴着耳机听着Lo-fi beats敲着Go代码,突然被拉进一个紧急会议——“老张,你不是搞过嵌入式系统?现在这个Java微服务集群挂了,你来顶一下。”
我当时差点把咖啡喷出来:我转Go都快一年了,Spring Cloud Alibaba是啥玩意儿?

但没办法,谁让我是团队里“什么都略懂一点”的万金油呢?更扎心的是,这项目原本是另一个同事负责的,结果他跳槽去了字节,留下一坨没文档、没注释、配置全写死的“遗产”。产品还催着上线,测试那边已经开始甩锅:“后端接口又502了!”

于是,这个曾经在示波器前调SPI通信的硬件仔,被迫重新拾起Java,一头扎进了Spring Cloud Alibaba的深水区。今天这篇“血泪史”,就是想告诉后来者:别等线上崩了才去学,有些坑,踩一次就够了


Nacos注册中心:你以为连上了,其实它把你拉黑了

第一个翻车点出在Nacos。本地跑得好好的,一上测试环境,服务死活注册不上去。日志里只有一行:

com.alibaba.nacos.api.exception.NacosException: failed to req API:/nacos/v1/ns/instance after all servers([nacos.prod.xxx.com:8848]) tried

我第一反应是网络不通。用telnet nacos.prod.xxx.com 8848一试,通的啊!再看防火墙规则、安全组……都没问题。最后发现,Nacos客户端默认用的是HTTP长连接 + 心跳机制,而我们的K8s Ingress层对长连接有超时限制(30秒),心跳包发不出去,服务就被自动剔除了。

解决方案很简单:要么改Ingress的keepalive配置,要么让客户端走短连接。我们选了后者,在bootstrap.yml里加了:

spring:
  cloud:
    nacos:
      discovery:
        # 强制使用短连接,避免被中间件掐断
        ephemeral: true
        # 显式指定命名空间,别用默认public
        namespace: prod-ns-2023

经验:生产环境千万别用public命名空间!我们早期图省事,所有环境混在一起,有一次测试服务注册到生产命名空间,直接把线上流量打歪了,半夜被call醒修bug,那叫一个酸爽。


Sentinel限流:配了等于没配?

第二个大坑是Sentinel。产品经理说:“大促期间要防刷,每个接口QPS不能超过100。” 我心想,Sentinel不就是干这个的吗?于是在控制台配了个流控规则,资源名填了/api/order/create,阈值100。

结果压测时,QPS飙到500,系统稳如老狗——根本没触发限流

查了半天才发现:Sentinel默认只监控@SentinelResource注解标记的方法,或者通过SphU.entry()手动埋点的资源。而我们用的是Spring MVC的普通Controller,路径/api/order/create根本不会被自动识别为“资源”。

正确的做法有两种:

  1. 给Controller方法加@SentinelResource(推荐):
@GetMapping("/order/create")
@SentinelResource(value = "createOrder", blockHandler = "handleBlock")
public ResponseEntity<Order> createOrder(@RequestParam String userId) {
    // 业务逻辑
}
  1. 或者开启Web Servlet支持,在application.yml中启用:
spring:
  cloud:
    sentinel:
      filter:
        enabled: true
      web-context-unify: false # 关键!否则所有URL合并成一个资源

注意:web-context-unify: false必须设为false,否则所有请求都会被归到同一个资源名下(比如/*),限流就失效了。


Seata分布式事务:别信“开箱即用”

最让我崩溃的是Seata。业务要求“下单+扣库存”必须原子性,领导拍板:“上Seata,阿里开源的,肯定稳。”

结果一集成,各种GlobalTransaction not activebranch register failed。数据库用的是MySQL 8.0,驱动版本不对;undo_log表没建;TC(Transaction Coordinator)和TM(Transaction Manager)网络不通……整整三天,我每天加班到凌晨,耳机里循环播放《The Nights》,感觉自己像个修水管的。

最终理清几个关键点:

问题 解决方案
数据库驱动不兼容 升级mysql-connector-java到8.0.28+
undo_log表缺失 手动在每个业务库执行Seata提供的SQL脚本
TC地址配置错误 registry.conf中显式指定serverAddr = "seata-server:8091"
事务超时太短 @GlobalTransactional(timeoutMills = 60000)

还有个隐藏坑:Seata的AT模式要求所有参与方都能回滚。我们有个老服务用的是MongoDB,根本不支持undo_log,结果整个分布式事务卡住。最后只能对那个服务做补偿逻辑,绕过Seata。


配置中心:别把密码写进Git!

说到配置,我得吐槽一句:见过太多人把application-prod.yml提交到Git仓库了!包括accessKey、数据库密码、Redis密钥……简直是安全灾难。

我们用Nacos Config做配置中心,但初期大家还是习惯本地写配置。后来我强制推行了一条CI规则:任何包含prod字样的YAML文件禁止合入主干

正确的做法是:

  1. 所有敏感配置放在Nacos Config中
  2. 应用启动时通过dataId + group拉取
  3. 本地只保留bootstrap.yml,且只包含Nacos地址和namespace
# bootstrap.yml
spring:
  application:
    name: order-service
  cloud:
    nacos:
      config:
        server-addr: nacos.prod.xxx.com:8848
        namespace: prod-ns-2023
        group: DEFAULT_GROUP
        file-extension: yaml

这样,即使代码泄露,攻击者也拿不到真实配置。运维同学再也不用半夜打电话吼:“你们谁把数据库密码commit了?!”


资源治理:别让一个服务拖垮整个集群

最后说说“资源”这个词。在硬件时代,我天天跟RAM、Flash、CPU cycle打交道,知道每一字节都珍贵。可到了Java微服务世界,很多人仿佛忘了“资源”二字。

我们曾有一个服务,每次启动都要加载500MB的缓存数据到内存。结果K8s Pod内存限制设得太低(1G),频繁OOM Kill。日志里全是:

java.lang.OutOfMemoryError: Java heap space

解决思路很简单:按需加载 + 分页预热。但更深层的问题是:没人做容量规划。于是我和运维一起定了几条铁律:

  • 每个服务必须声明requestslimits
  • JVM堆内存不超过Pod内存的70%
  • 禁止在启动时加载全量数据
  • 关键接口必须有熔断和降级策略

现在,我们的服务在Prometheus里跑得明明白白,CPU、内存、GC次数一目了然。再也不用靠“重启大法”续命了。


写在最后:代码人生,就是不断填坑

从示波器到IDEA,从C到Go再到被迫重拾Java,我的“代码人生”就像一场不断打怪升级的游戏。Spring Cloud Alibaba确实强大,但它不是银弹,更不是玩具。生产环境里,一个配置错位,可能就是百万损失。

如果你也在用SCA,记住三件事:

  1. 别信默认配置,生产环境必须显式声明
  2. 监控先行,没Metrics的服务等于裸奔
  3. 文档比代码重要,别让你的继任者骂你祖宗

上周五晚上,我又一次戴着耳机敲代码。这次是Go,但桌角还放着一本《Spring Cloud Alibaba实战》。我知道,技术人的路没有尽头,只有下一个坑在等着我。

不过没关系,只要音乐还在响,键盘还在敲,我就还能写下去——毕竟,这才是真实的代码人生

评论 0

最热最新
暂无评论
不想写日报Lv.1
0
影响力
0
文章
0
粉丝