后端架构的十年漂流:从单体到云原生,一个Spark老炮儿的血泪史
去年双11前夕,我坐在深圳南山科技园的工位上,盯着监控面板上飙红的CPU曲线,心里默念:“这破单体应用,真该埋了。”
作为一个在腾讯系公司摸爬滚打三年的大数据开发,天天和 Spark、Hive、Kafka 打交道,原本以为自己这辈子就跟批处理和流计算锁死了。结果去年被临时抓壮丁,参与一个老系统的重构——理由是“你懂分布式,应该也懂微服务吧?”(领导原话)
我?一个 Vim 党,连 IDEA 都很少开的人,突然要搞后端架构演进?但转念一想,跳槽面试时“系统设计”题老被问到,不如趁机补补课。
一切始于那个“万能”的Java单体
五年前,我们有个核心服务,用 Spring Boot 写的,数据库是 MySQL,前端用 Thymeleaf 渲染页面,顺带嵌点 JavaScript 做交互。听起来是不是很复古?但它真的跑得动——直到业务量暴涨。
这个单体应用啥都干:用户管理、订单处理、日志记录、甚至还有个简易爬虫模块(别问,问就是产品经理说“竞品数据很重要”)。所有逻辑塞在一个 WAR 包里,部署在几台 Tomcat 上。每次发布,运维兄弟都要念三遍佛经,生怕出事。
最离谱的是,那个爬虫模块居然和交易逻辑共用同一个线程池!某次爬虫目标网站反爬机制触发,大量请求卡住,直接把整个交易链路拖垮。线上事故复盘会上,测试同事幽幽地说:“你们后端是不是觉得‘能跑就行’?”
那一刻,我知道:拆!
微服务:理想很丰满,现实全是坑
我们决定走微服务路线。拆成 user-service、order-service、crawler-service……每个服务独立部署,用 Dubbo + ZooKeeper 做 RPC。Java 还是主力语言,毕竟团队熟。
但问题来了:
- 服务间调用爆炸:一个下单操作要跨 5 个服务,链路追踪全靠日志 grep。
- 数据库耦合:虽然服务拆了,但所有服务还连同一个 MySQL 实例,DBA 看到我们就像看到仇人。
- 部署复杂度飙升:以前
scp一个包重启 Tomcat 就行,现在要管十几个服务的版本、配置、依赖。
更惨的是,那个爬虫服务经常因为目标网站改结构而挂掉,但它的崩溃会通过熔断机制间接影响主流程。有次凌晨三点,我被 PagerDuty 叫醒,发现是因为爬虫解析失败导致用户无法查看历史订单——就因为我们在前端 JavaScript 里硬编码了一个“最近抓取时间”字段。
教训:微服务不是银弹,拆得不好,只会把单体地狱变成分布式炼狱。
转向云原生:K8s 救我狗命
去年初,公司全面拥抱云原生。我们开始用 Kubernetes + Helm 管理服务,数据库也拆成了读写分离 + 分库分表(ShardingSphere 上场),消息队列换成 Pulsar(别问,问就是“比 Kafka 新”)。
关键转变在于:
- 容器化:每个服务打包成 Docker 镜像,CI/CD 流水线自动推到 Harbor。
- 声明式部署:用 YAML 定义 Deployment、Service、Ingress,再也不用手动 ssh。
- 弹性伸缩:爬虫服务高峰期自动扩容,闲时缩到 0(对,用 KEDA 做事件驱动伸缩)。
举个例子,爬虫服务现在完全独立:
# crawler-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: crawler-service
spec:
replicas: 2
template:
spec:
containers:
- name: crawler
image: my-registry/crawler:v1.2
env:
- name: TARGET_URLS
valueFrom:
configMapKeyRef:
name: crawler-config
key: urls
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "500m"
而且它不再直接暴露给前端。前端 JavaScript 调用的是 data-api 服务,后者从缓存(Redis)或异步任务结果表中读数据,彻底解耦。
架构对比:血与泪的数据说话
| 维度 | 单体架构 | 微服务(初期) | 云原生架构 |
|---|---|---|---|
| 部署频率 | 每月 1~2 次 | 每周 3~5 次 | 每天 10+ 次(GitOps) |
| 故障隔离 | 无(一挂全挂) | 部分(但链路复杂) | 强(Pod 级别隔离) |
| 资源利用率 | 低(预留冗余) | 中 | 高(HPA + VPA 动态调度) |
| 爬虫模块稳定性 | 极差(拖垮主流程) | 差(仍耦合数据) | 优秀(独立 + 异步) |
| 面试题命中率 | “讲讲你的单体优化” | “怎么保证最终一致性?” | “K8s 网络模型了解吗?” |
说到面试题——自从搞了云原生,我面试时终于能答出“如何设计一个高可用爬虫系统”这种题了。不再是背八股文,而是真有故事可讲:比如用 Kafka 做任务队列,Flink 做实时清洗,结果存入 Iceberg 表供 Spark 查询——这不就是我的日常?
别忘了前端那点事儿
很多人以为后端架构演进跟前端无关,大错特错。我们早期在 JavaScript 里直接调多个后端接口,导致浏览器并发限制、CORS 报错满天飞。后来引入 BFF(Backend For Frontend)层,用 Node.js 聚合数据,前端只对接一个入口。
// old way (bad!)
fetch('/user/profile')
fetch('/order/history')
fetch('/crawler/latest') // 这个经常404...
// new way (BFF)
fetch('/api/dashboard-data') // 由BFF聚合,加缓存
BFF 不仅提升了前端体验,还让后端服务可以安心做细粒度拆分,不用考虑“前端会不会调崩”。
写在最后:稳定压倒一切
折腾了快两年,系统终于稳了。上周五晚上,我看着 Grafana 面板上平稳的曲线,默默关掉 Vim,喝了口已经凉透的瑞幸。
作为大数据开发,我原本以为云原生是后端的事。但现实是:数据管道、实时计算、服务架构,早已融为一体。你不懂 K8s,怎么部署 Flink JobManager?你不了解服务网格,怎么排查 Spark Thrift Server 的网络延迟?
所以,别再把自己局限在“我是搞大数据的”或者“我只是写 Java 的”。技术栈在融合,边界在消失。下次面试官问“你怎么看后端架构演进”,你可以笑着说:“从单体到云原生,我踩过的坑,够写一本《程序员避坑指南》了。”
当然,如果他接着问“那爬虫模块怎么设计?”,你就知道——机会来了。

评论 0