从单体到云原生:一个文科转码仔的后端架构踩坑实录

TypeScript守夜人
2026-01-05 18:16
阅读 1830

大家好,我是小林,非科班出身的前端开发,大学读的是中文系——没错,就是那个写论文能写八千字、写代码只能写 console.log('hello world') 的专业。两年前,我硬是靠啃文档、刷 LeetCode 和凌晨三点的咖啡,混进了现在的这家 SaaS 初创公司,干起了“伪全栈”(其实是被产品经理和后端同事轮流压榨的前端)。

虽然主职是前端,但因为团队小、人手紧,加上我好奇心重,这两年也深度参与了不少后端架构的演进工作,尤其是从去年开始,我们整个产品线从 Python 单体应用逐步迁移到云原生架构。今天就想聊聊这段“痛并快乐着”的经历,顺便吐吐槽,给同样在转型路上挣扎的朋友一点参考。


起因:产品疯了,系统崩了

事情得从去年双11前说起。我们的核心产品是一个面向中小企业的营销自动化平台,早期用 Django + PostgreSQL 搞了个单体应用,跑在一台 8C32G 的云服务器上。那时候产品需求简单,用户也不多,一切岁月静好。

但随着市场部一顿猛如虎的操作,客户量三个月翻了五倍。结果双1一大促当天,系统直接雪崩:API 响应时间飙到 30s+,数据库 CPU 打满,连后台管理页面都打不开。运维小哥半夜打电话给我:“小林,你们前端是不是又发了啥奇怪请求?” 我一脸懵:“我连后端代码都没碰过啊!”

后来复盘发现,根本问题不在前端——是单体架构扛不住高并发了。所有功能模块(用户管理、邮件发送、数据分析、Webhook 回调)全塞在一个进程里,一个慢查询就能拖垮整个服务。更糟的是,部署一次要停机十分钟,产品经理看我的眼神都快冒火了:“你这前端能不能让系统别老挂?”

那一刻我悟了:再不搞微服务,我就要被产品祭天了


第一步:拆!但别乱拆

我们决定先做服务拆分。作为文科生,我对“领域驱动设计”这种词天然恐惧,于是拉着后端大佬老张一起画模块边界。原则很简单:高频调用、资源密集、独立业务逻辑的模块优先拆

第一个被拎出来的是“邮件发送服务”。它依赖外部 SMTP,经常超时,还吃大量内存。我们用 FastAPI 重写,独立部署,通过 REST API 供主站调用。

# email-service/main.py
from fastapi import FastAPI, BackgroundTasks
import smtplib

app = FastAPI()

def send_email_async(to: str, subject: str, body: str):
    # 模拟耗时操作
    with smtplib.SMTP("smtp.example.com") as server:
        server.sendmail("noreply@ourproduct.com", to, f"Subject: {subject}\n\n{body}")

@app.post("/send")
async def send_email(to: str, subject: str, body: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(send_email_async, to, subject, body)
    return {"status": "queued"}

踩坑点1:一开始没加异步队列,直接同步发邮件,结果新服务自己也被拖垮了。后来引入 Celery + Redis,才算稳住。


上云原生:K8s 不是万能药

拆完几个核心服务后,我们决定上 Kubernetes。理由很现实:老板说“别家都在用云原生,显得我们很土”

作为前端,我原本以为 K8s 就是个高级版 Docker Compose,结果第一天就栽了。写了个 Deployment,Pod 死活起不来,日志就一行:

CrashLoopBackOff: Liveness probe failed: HTTP probe failed with statuscode: 500

查了半天,原来是健康检查路径 /healthz 没实现。文科生的直觉告诉我:“这不就是个 ping 吗?随便返回 200 不就行了?” 于是:

@app.get("/healthz")
def health_check():
    return {"status": "ok"}  # 实际应该检查 DB、Redis 等依赖!

上线后三天,数据库挂了,但健康检查还是绿的,K8s 以为服务正常,继续往里导流,事故扩大。教训:健康检查不是摆设,得真查资源状态

后来我们补上了真正的探针逻辑:

@app.get("/healthz")
def health_check():
    try:
        db.execute("SELECT 1")
        redis.ping()
        return {"status": "healthy"}
    except Exception:
        raise HTTPException(status_code=500, detail="Dependency down")

资源调度:别让 Python 成为瓶颈

我们主力后端语言还是 Python(团队历史包袱),但 GIL 和内存占用一直让人头疼。有次压测,一个数据分析服务的 Pod 内存飙到 2GB,被 K8s OOMKilled 了十几次。

踩坑点2:一开始只设了 requests,没设 limits,结果某个服务吃光节点内存,把其他服务全挤下去了。

后来我们做了三件事:

  1. gunicorn + uvicorn 部署 ASGI 应用,合理设置 worker 数(CPU 核数 * 2 + 1)
  2. 严格限制每个 Pod 的内存上限(比如 512Mi)
  3. 对内存敏感的服务改用 Go 重写(别骂,真香)
服务类型 语言 CPU Request Memory Limit P99 延迟
用户中心 Python 500m 512Mi 180ms
邮件发送 Python 300m 768Mi 420ms
实时分析 Go 1000m 1Gi 65ms

数据不会骗人:Go 在 CPU 密集型场景确实碾压 Python。但考虑到团队熟悉度,我们只对关键路径做语言替换,其他仍用 Python —— 技术选型不是炫技,是平衡


开发心得:文科生也能懂架构

作为一个曾经连“什么是负载均衡”都要百度的人,这两年最大的感悟是:架构演进不是一蹴而就的炫酷技术堆砌,而是对“资源”和“产品需求”的持续权衡

  • 产品要快速试错?那就别一上来就搞 Service Mesh,先保证交付速度。
  • 资源有限?那就优先拆解故障域,而不是追求“彻底微服务化”。
  • 团队能力不足?那就用托管服务(比如 AWS RDS、阿里云 ACK)降低运维复杂度。

上周五晚上,我又在加班调 HPA(Horizontal Pod Autoscaler)策略。产品经理路过,笑问:“小林,你一个前端怎么天天跟 K8s 打交道?”
我苦笑:“因为我不想再背锅了啊。”


最后几句真心话

从单体到云原生,我们走了弯路,也踩了坑,但每一步都值得。现在的系统,即使流量突增 3 倍,也能自动扩容、平稳运行。最爽的是——再也没在半夜被叫起来救火

如果你也是非科班出身,别怕接触后端或架构。我一个写散文的都能搞懂 K8s,你肯定行。记住:技术没有高低贵贱,解决问题才是王道

对了,最近我在学 eBPF 做网络观测……别问,问就是“产品经理说竞品用了,我们也得有”。

(完)

评论 0

最热最新
暂无评论
TypeScript守夜人Lv.1
0
影响力
0
文章
0
粉丝