后端架构演进:从单体到云原生 —— 一个 Spark 老兵的血泪史

何建军_架构师
2025-12-15 08:44
阅读 275

大家好,我是阿星,坐标深圳南山科技园,目前在一家腾讯系背景的数据中台团队搬砖,干了快三年大数据开发。日常就是和 Spark、Flink、Kafka 打交道,调优 shuffle、优化 GC、追着 OOM 日志跑,堪称“数据民工”。

上周五晚上 10 点半,我又一次被钉钉拉进一个线上故障群:“用户中心服务雪崩,所有依赖接口超时”。我一边啃着冷掉的猪脚饭,一边翻看 Grafana 面板,心里却在想:这破单体架构,真的撑不住了。

说来你可能不信,我们这个核心用户服务,去年双11前还在用 Spring Boot 单体部署,数据库是 MySQL 主从 + Redis 缓存,部署方式?Jenkins 打包成 fat jar,scp 到三台物理机上,nohup 启动。听起来是不是像 2015 年的剧本?但现实就是这么魔幻——业务跑得快,技术债堆成山。

今天这篇不是教程(别急着收藏当速成指南),也不是为了写简历包装“精通云原生”(虽然确实对跳槽有帮助),而是想和你聊聊:一个被现实毒打的大数据人,如何被迫卷入后端架构演进这场战争,并最终拥抱云原生


一切始于一次 P0 故障

事情要从三个月前说起。那天产品经理小王兴冲冲地跑来:“我们要上链!用区块链做用户行为溯源!” 我当场差点喷出咖啡——我们连微服务都没拆完,现在要搞区块链?

但老板点头了,需求就来了。更糟的是,用户中心作为所有业务的入口,QPS 在促销期间飙到 8w+,而单体应用里还塞着用户注册、登录、积分、风控、甚至区块链上链逻辑(是的,他们真把 Web3SDK 塞进同一个 Spring Boot 应用里了)。

结果?一次 GC Full 暂停 4 秒,整个集群雪崩。监控告警炸锅,SRE 小哥凌晨三点打电话骂街:“你们这代码是拿脚写的吗?”

那一刻我意识到:再不拆,我们就得一起陪葬


第一步:别幻想,先微服务化

很多人一提架构演进就直接跳“上 Kubernetes”,但现实很骨感。我们第一步根本没碰容器,而是暴力拆单体

  • 把用户核心信息(ID、手机号、状态)拆成 user-core
  • 登录鉴权拆成 auth-service
  • 积分系统独立为 point-engine
  • 区块链上链逻辑?单独起个 blockchain-adapter,通过 MQ 异步处理

💡 经验教训:不要试图一次性完美拆分。我们第一次拆的时候,漏掉了“用户注销”需要级联清理多个服务的数据,上线后导致数据不一致。测试同事差点把我钉在墙上。

数据库也跟着拆:

  • user-core 用 MySQL 分库分表(ShardingSphere)
  • auth-service 用 Redis Cluster 存 session
  • 区块链相关数据?MongoDB(因为结构太杂)

接口设计上,我们强制要求所有内部调用走 gRPC(比 HTTP 快 30%+),对外 RESTful API 由 API Gateway 统一暴露。顺便引入了 OpenTelemetry 做全链路追踪——再也不用靠日志 grep 排查跨服务问题了。

# user-core 的 gRPC 接口定义片段
service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc UpdateProfile(UpdateProfileRequest) returns (Empty);
}

message GetUserRequest {
  string user_id = 1;
  repeated string fields = 2; // 按需返回字段,避免大对象传输
}

这一步花了两个月,但效果立竿见影:故障隔离了。就算 blockchain-adapter 挂了,用户照样能登录。


第二步:容器化 ≠ 云原生,但必须迈出去

微服务拆完,部署还是老样子:每台机器手动启停 jar 包。运维小哥每天在群里吼:“谁又改了 JVM 参数没同步文档?”

于是我们开始容器化。别误会,容器化只是手段,不是目的。我们的目标是让服务“可声明、可调度、可自愈”。

我们选了 Kubernetes(K8s),原因很简单:公司内部已经有成熟的 K8s 平台,连 Helm Chart 模板都给你写好了。作为大数据开发,我对 K8s 的第一印象其实是“这不就是 YARN 的加强版吗?”——都是资源调度嘛!

一个典型的服务 Deployment 配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-core
spec:
  replicas: 6
  selector:
    matchLabels:
      app: user-core
  template:
    metadata:
      labels:
        app: user-core
    spec:
      containers:
      - name: user-core
        image: harbor.corp.com/user-core:v1.2.3
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10

关键点:

  • 资源限制必须设!否则一个服务 OOM,Node 上其他 Pod 全遭殃
  • 健康检查不能少,K8s 靠它判断是否重启 Pod
  • 镜像版本要固定,别用 latest(血泪教训:某次自动拉 latest 导致回滚失败)

上线第一天,我就因为没设 memory limit,Pod 被 OOMKilled 了三次。SRE 在群里@我:“大数据佬,你这 Java 进程吃内存比 Spark Executor 还狠啊!”


第三步:拥抱云原生,但别被 buzzword 带跑偏

很多人以为上 K8s 就是云原生了,其实差得远。真正的云原生是以不可变基础设施、声明式 API、服务网格、可观测性为核心的一整套理念

我们在生产环境落地了几个关键组件:

组件 作用 坑点
Istio 服务网格,统一管理流量、熔断、重试 Sidecar 内存开销大,小服务慎用
Prometheus + Grafana 监控指标采集 自定义 metrics 要暴露 /metrics 端点
Loki 日志聚合(替代 ELK) 查询语法反人类,但省资源
Argo CD GitOps 持续部署 需要严格权限控制,否则谁都能改 prod

举个实际例子:以前服务 A 调用服务 B 超时,我们只能在代码里写 retry。现在通过 Istio VirtualService,一行配置搞定:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
spec:
  hosts:
  - auth-service
  http:
  - route:
    - destination:
        host: auth-service
    retries:
      attempts: 3
      perTryTimeout: 2s

再也不用改代码、重新打包、走发布流程了!运维和开发终于能坐下来喝杯瑞幸了(虽然是他请我)。


关于区块链:别被带节奏

还记得开头那个“上链”需求吗?实话实说,区块链在这里纯属伪需求。用户行为溯源完全可以用 Kafka + Flink 实现,还快 100 倍。

但我们还是做了,为什么?因为老板觉得“Web3 是未来”。于是我们搞了个折中方案:

  1. 用户行为事件发到 Kafka
  2. blockchain-adapter 消费 Kafka,批量打包上链(非实时)
  3. 链上只存哈希值,原始数据存在 S3 + IPFS

这样既满足了“上链”的 KPI,又没拖垮主链路。而且,区块链模块完全独立,挂了也不影响核心业务——这才是架构演进的意义:解耦风险。


性能与成本:云原生不是免费午餐

别以为上了云原生就万事大吉。我们曾因为过度拆分,导致服务间调用延迟飙升。一个简单查询要跨 5 个服务,P99 从 50ms 涨到 300ms。

后来我们做了两件事:

  1. 合并高频耦合服务:比如把用户资料和头像服务合并
  2. 引入缓存层:在 API Gateway 层加本地缓存(Caffeine),热点数据直接返回

数据库方面,分库分表后连接池管理成了新痛点。我们用 ShardingSphere 的 max-connections-size-per-query 限制单查询并发,避免 MySQL 连接打满。

至于成本?K8s 集群本身不便宜。但我们通过 HPA(Horizontal Pod Autoscaler)实现了弹性伸缩:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-core
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

大促期间自动扩到 20 个副本,结束后缩回 3 个,月度资源成本降了 40%。老板看了报表,终于松口给我们批了新项目预算。


写给正在看这篇文章的你

如果你也在维护一个“祖传单体”,别焦虑。架构演进不是一蹴而就的,关键是在每个阶段做出最适合当前业务状态的选择

我当初学 K8s,纯粹是因为想跳槽——简历上写“熟悉云原生架构”确实加分。但现在回头看,最大的收获不是技术,而是系统性思维:如何设计可扩展、可运维、可演进的系统。

顺便吐槽一句:那些鼓吹“所有应用都要 Serverless”、“不用微服务就是落后”的人,大概没经历过凌晨三点修线上 Bug 的绝望。

最后送大家一句话,也是我们团队墙上贴的:

好的架构,不是设计出来的,是演化出来的。

共勉。


附:真实数据对比(用户中心服务)

指标 单体架构(2022) 云原生架构(2024)
部署时间 15 分钟 2 分钟(GitOps)
故障恢复 手动重启(5~10 分钟) 自动重建(< 1 分钟)
P99 延迟 120ms 65ms
资源利用率 30%(常驻) 65%(弹性)
团队协作效率 开发互相阻塞 独立迭代

注:数据来自我们内部监控平台,已脱敏。


后记:写完这篇文章,我已经提交了离职申请。下家是一家做 Web3 基础设施的 startup,据说全员远程、不用写周报。但临走前,我还是把这份架构演进文档塞进了 Confluence——就当给后来人留个“避坑指南”吧。

毕竟,程序员的浪漫,就是让世界少一个重复造的轮子。

评论 0

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