从单体到云原生:一个传统企业后端开发的血泪演进史

开发者后花园
2026-01-13 04:55
阅读 537

上周五晚上十点半,我还在工位上盯着 VSCode 里一堆红得发亮的 Prometheus 告警,心里默默问候产品经理全家。事情起因是上周三,领导在晨会上轻描淡写地说:“咱们这个老系统得上云,双11前搞定。”
我当时差点把咖啡喷到显示器上——这可是跑了快八年的 Java 单体应用,连 Maven 都还在用 3.2 版本!

我是公司数字化转型组的 Java 开发,干了快两年,日常就是和老旧代码、慢如蜗牛的 Oracle 查询、以及永远不够用的服务器资源斗智斗勇。插件装了一堆(Lombok、MyBatisX、Rainbow Brackets……),但面对这种“史诗级重构”,VSCode 也救不了我。


起点:那个又大又臭的单体

我们原来的系统,典型的“大泥球”架构:一个 WAR 包部署在 Tomcat 上,前端 JSP + 后端 Spring MVC + MyBatis,数据库是 Oracle 11g。功能模块之间耦合得像打结的耳机线,改个用户模块,订单服务就崩给你看。

最要命的是性能。去年双11,凌晨两点,DBA 打电话说“CPU 100%,连接池耗尽”,我翻着日志发现是个爬虫接口没做限流——对,你没听错,我们居然在主业务系统里直接暴露了一个给第三方用的爬虫数据接口!当时真的想砸电脑。

但老板说了:“客户要用,就得给。”
行吧,那就加缓存、加队列、加熔断……结果越补越烂,系统像个贴满创可贴的漏船。


转折:被逼上微服务

今年初,公司终于决定搞“云原生转型”。说是转型,其实就是把单体拆成微服务,跑在 Kubernetes 上。我本来以为能摸鱼学点新东西,结果第一天就被安排去拆“商品中心”模块。

难点在哪?不是技术,是数据
商品表里混着库存、价格、规格、甚至用户评论的冗余字段。想拆?先解决跨库事务问题。我们试过 Seata,结果性能掉一半;后来妥协用了“最终一致性 + 补偿任务”,虽然丑,但至少稳了。

中间踩了个大坑:服务注册与发现
一开始用 Eureka,结果测试环境网络抖一下,整个调用链雪崩。运维小哥吐槽:“你们 Java 系统怎么动不动就 Full GC?” 我反问:“你们 K8s 的 readinessProbe 配 30 秒延迟,不崩才怪!”

最后换成 Nacos + 本地缓存兜底,总算稳住。


意外收获:Go 写的 Sidecar

拆服务过程中,有个需求特别头疼:老系统的日志格式太乱,没法统一采集
运维要求所有服务输出 JSON 日志,接入 ELK。但改老代码?那得重写半个系统。

灵机一动,我想起 GitHub 上看到的一个项目:用 Go 写个轻量 Sidecar,专门处理日志格式转换。
Go 启动快、内存小,正好适合这种辅助任务。于是周末肝了两天,写了个 log-adapter,通过共享 volume 读取应用日志,转成标准 JSON 再 stdout 出去。

// log-adapter/main.go
func main() {
    watcher, _ := fsnotify.NewWatcher()
    watcher.Add("/shared/logs")
    
    for {
        select {
        case event := <-watcher.Events:
            if event.Op&fsnotify.Write == fsnotify.Write {
                lines := readLines(event.Name)
                for _, line := range lines {
                    jsonLog := parseLegacyLog(line) // 自定义解析逻辑
                    fmt.Println(jsonLog)
                }
            }
        }
    }
}

部署到 K8s Pod 里,作为 sidecar container,完美解决问题。运维大哥拍我肩膀:“没想到你还会 Go 啊?”
我苦笑:“GitHub 上抄的,改了改。”


云原生不是银弹,但能救命

现在系统跑在阿里云 ACK 上,Spring Boot + Nacos + Sentinel + Prometheus + Grafana 全家桶。自动扩缩容在大促时真香——流量上来,Pod 自动从 3 个扩到 50 个,再也不用手动重启 Tomcat。

但代价也不小:

  • 调试变难了:本地起一套完整环境?做梦。现在全靠 Arthas + 远程 debug。
  • 配置爆炸:每个服务都有 application-dev.ymlapplication-prod.yml、K8s ConfigMap、Helm values……改个超时时间要改四个地方。
  • 监控告警疲劳:半夜被 PagerDuty 叫醒三次,结果都是误报。

不过整体来看,稳定性提升明显。最近一次压测,QPS 从原来的 800 提升到 5000+,P99 延迟从 2s 降到 200ms。DBA 终于不用天天骂我们“Java 应用吃内存”了(虽然 JVM 还是占了 2G……)。

下面是简单对比:

指标 单体架构 云原生微服务
部署频率 月更(手动) 天更(CI/CD)
故障隔离 无(一崩全崩) 有(熔断降级)
资源利用率 低(常驻高配) 高(按需伸缩)
开发效率 前期快,后期慢 前期慢,后期快
学习成本 高(K8s/YAML/Service Mesh)

一点真心话

说实话,不是所有系统都值得上云原生。如果你的业务稳定、流量不大、团队就三个人,硬拆微服务纯属自虐。但我们这种传统企业,业务在增长,技术债堆成山,不重构就是等死。

这次转型让我意识到:架构演进不是技术炫技,而是为业务续命
我们甚至保留了部分单体模块(比如财务对账),因为拆了反而增加复杂度。云原生不是终点,而是工具箱里多了一把趁手的锤子。

另外,别信“Go 比 Java 快所以该换语言”这种鬼话。我们核心交易还是 Java,因为生态、人才、稳定性摆在那里。Go 只用在边缘场景,比如刚才说的 log adapter,或者偶尔写个高性能爬虫——对,我们还真用 Go 写了个内部数据采集爬虫,每天从竞品网站扒商品信息,跑在单独的 K8s Job 里,和主业务完全解耦。


最后

如果你也在传统企业搞数字化转型,我的建议是:

  1. 先治理,再拆分:把代码规范、日志、监控、CI/CD 弄好,再动架构。
  2. 小步快跑,别想一口吃成胖子:我们第一个微服务只拆了“商品基础信息”,其他全 mock。
  3. 拥抱开源,但别盲目跟风:GitHub 上项目很多,但适合你的才是最好的。
  4. 和运维搞好关系:他们掌握着你的 K8s 权限,也掌握着你的上线命脉(笑)。

写这篇文章的时候,已经是凌晨一点。明天还要改一个“紧急需求”——产品经理说要在爬虫结果里加个 AI 推荐标签。
我深吸一口气,打开 VSCode,新建了一个 ai-tagging-service 目录。

路还很长,但至少,我们不再是在泥潭里挣扎了。

技术分享的意义,大概就是让后来者少踩几个坑,多睡几小时觉吧

评论 0

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