从单体到云原生:一个独立后端的血泪实战

写码的阿川
2026-01-04 12:32
阅读 261

上周五晚上十一点,我戴着 AirPods 听着 Lo-fi beats,在家里的小书房敲代码。突然收到运维告警:生产环境 CPU 飙到 95%!点开日志一看,又是那个“万年不改”的单体服务在搞事情——这已经是我们团队本月第三次线上雪崩了。

作为一家不到 30 人的小厂里唯一负责某条核心业务线的后队开发者(没错,就我一个人扛这条线),我真的有点绷不住了。每天既要对接产品经理天马行空的需求(“能不能加个区块链功能?听起来很酷”),又要应付测试同事凌晨三点发来的 Bug 报告,还得自己部署、监控、回滚……最离谱的是,上周 HR 还悄悄问我:“你最近有更新简历吗?”

于是,我决定不再忍受这个又大又臭的单体应用了。趁着 Q3 没有大促,我咬牙启动了架构演进计划:从单体走向云原生。今天这篇文,就是我这半年踩坑、翻车、再爬起来的实战记录。不讲理论,只说真话。


起点:那个让我又爱又恨的 Django 单体

我们的老系统是用 Python + Django 写的,2018 年上线,至今还在跑。说实话,一开始它真的很香:开发快、部署简单、文档齐全。但随着业务增长,它逐渐变成了“意大利面条式架构”——订单、用户、支付、风控、甚至临时加的“区块链积分兑换”模块(对,产品经理真的让我加了)全塞在一个 repo 里。

典型的症状包括:

  • 一次小需求要全量部署,风险极高
  • 数据库连接池经常打满,因为所有业务共用同一个 PostgreSQL 实例
  • 日志混在一起,排查问题像大海捞针
  • 想加个新功能?先祈祷别影响老逻辑

更惨的是,去年双 11 前夕,因为一个风控规则写错了,导致整个下单流程瘫痪。我当时坐在电脑前,手抖得连 kubectl 都打错了(其实那时候我们还没用 k8s)。那一刻我就知道:必须重构。


第一步:微服务拆分 —— 别信教科书,先保命

很多人一说架构演进就直接上 Kubernetes、Service Mesh,但现实是:小厂资源有限,老板只关心“能不能跑、稳不稳、花不花钱”。

所以我定了三个原则:

  1. 不动数据库(DB 是底线,不敢乱动)
  2. 渐进式拆分(不能停服)
  3. 优先拆高风险模块(比如那个“区块链积分”)

实战:把区块链模块拎出来

产品经理非要加的“区块链积分”其实是个伪需求——底层只是调第三方 API 记录交易哈希,并没有真正的链上交互。但它却和其他核心逻辑耦合极深,每次改积分规则都可能炸掉支付流程。

我用 FastAPI 快速写了一个独立服务,只做两件事:

  • 接收主站发来的积分事件(通过 RabbitMQ)
  • 调用第三方区块链网关,返回 TxID
# blockchain_service/main.py
from fastapi import FastAPI
from celery import Celery
import requests

app = FastAPI()
celery_app = Celery('blockchain_tasks', broker='amqp://guest@rabbitmq//')

@app.post("/record_transaction")
async def record_tx(user_id: str, amount: float):
    # 异步处理,避免阻塞主流程
    record_blockchain_tx.delay(user_id, amount)
    return {"status": "accepted"}

@celery_app.task
def record_blockchain_tx(user_id, amount):
    try:
        resp = requests.post(
            "https://fake-blockchain-api.example.com/tx",
            json={"user": user_id, "amount": amount}
        )
        if resp.status_code != 200:
            # 发送告警到企业微信
            alert(f"Blockchain API failed: {resp.text}")
    except Exception as e:
        log_error(e)

关键点:

  • 异步解耦:主站只发消息,不等结果
  • 失败重试:Celery 自带 retry 机制
  • 隔离风险:就算区块链服务挂了,主站照样能下单

上线后,主站的错误率直降 40%。产品经理还夸我“技术前瞻性”(其实是怕背锅)。


第二步:容器化 —— Docker 不是银弹,但真香

光拆服务不够,部署还是靠手动 scp + supervisor,每次发布都像在拆炸弹。

我决定全面 Docker 化。但小厂没 DevOps,只能自己撸 YAML。

# Dockerfile for main app
FROM python:3.9-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# 关键:不打包虚拟环境,减小镜像体积
EXPOSE 8000

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myapp.wsgi:application"]

配合 docker-compose 本地调试:

# docker-compose.yml
version: '3'
services:
  web:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - db
      - rabbitmq
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: myapp
  rabbitmq:
    image: rabbitmq:3-management

教训:一开始我把 static 文件也打进镜像,结果每次前端改个 CSS 都要重新 build 整个后端镜像。后来改成 Nginx 独立托管静态资源,清爽多了。


第三步:拥抱云原生 —— 在成本和稳定性之间走钢丝

公司用的是阿里云,预算紧张,所以没法直接上 ACK(阿里云 Kubernetes)。但我发现 Serverless + 容器实例 是个折中方案。

具体做法:

  • 核心服务用 ECI(Elastic Container Instance) 部署,按秒计费
  • 无状态服务(如区块链积分)用 函数计算 FC
  • 数据库继续用 RDS,但加了读写分离
# eci-deployment.yaml (简化版)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: main-app
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: web
        image: registry.cn-hangzhou.aliyuncs.com/myapp/main:latest
        ports:
        - containerPort: 8000
        resources:
          limits:
            cpu: "1"
            memory: "2Gi"

效果

指标 单体时代 云原生后
部署时间 15 分钟 < 2 分钟
故障隔离 模块级
月成本 ¥8,000 ¥6,200(省了 22%!)
回滚成功率 60% 98%

最爽的是,现在我可以半夜在家用手机点一下“重启服务”,不用开电脑。远程办公的快乐,谁懂?


关于 Python 的选择:FastAPI > Flask > Django(在微服务场景)

很多人问:为什么新服务不用 Django?

答:太重了。Django ORM、Admin、Session 这些在单体里是优势,在微服务里全是负担。

我现在主力用 FastAPI,理由很实在:

  • 自动生成 OpenAPI 文档,前端对接省事
  • Pydantic 模型校验,减少脏数据
  • 异步支持好,适合 I/O 密集型任务(比如调区块链 API)

举个接口例子:

from pydantic import BaseModel

class OrderRequest(BaseModel):
    user_id: str
    items: List[str]
    promo_code: Optional[str] = None

@app.post("/orders")
async def create_order(req: OrderRequest):
    # 自动校验 req 是否符合模型
    # 如果 promo_code 是 "BLOCKCHAIN2023",触发积分服务
    if req.promo_code == "BLOCKCHAIN2023":
        await send_to_blockchain_service(req.user_id, 10)
    return {"order_id": "xxx"}

产品经理看到自动生成的 Swagger 页面,居然主动说:“原来接口是这样用的啊!” —— 这可能是我今年听到最暖心的话。


简历上的“云原生”不是吹的,是熬出来的

说到简历,最近我确实更新了。以前写“熟悉微服务架构”,现在敢写“主导从单体到云原生架构演进,QPS 提升 3 倍,故障恢复时间从 30 分钟降至 2 分钟”。

但只有我知道背后有多难:

  • 为了说服老板投钱买 ECI,我做了整整一周的成本对比表
  • 为了不让测试同事骂我,我写了详细的迁移 checklist
  • 为了监控,我硬着头皮学了 Prometheus + Grafana,现在 dashboard 比我的桌面还干净

最搞笑的是,上周面试一个候选人,他简历写着“精通云原生”,我问他:“如果 Pod 频繁 OOMKilled 怎么办?” 他支支吾吾说“加内存”。我默默关掉了简历——真正的云原生,是在资源受限下还能稳如老狗


写在最后:小厂程序员的生存之道

我不是大厂架构师,没有百万 QPS 的场景,也没有专职 SRE 团队。我只是一个在家边听音乐边改 Bug 的普通后端,想让系统别在半夜把我叫醒。

从单体到云原生,不是技术炫技,而是用最小成本换取最大稳定性。如果你也在小厂挣扎,我的建议很朴素:

  1. 先解决最痛的点(比如那个总炸的模块)
  2. 别追求一步到位,能跑就行,后续再优化
  3. 文档和监控比代码更重要(否则下次出事还是你背锅)
  4. 学会用云厂商的托管服务,别重复造轮子

对了,那个“区块链积分”功能,上个月已经下线了——产品经理说“风向变了”。但没关系,拆出来的服务还能复用,代码可维护性也提高了。至少,我的简历又多了一行实战经验。

现在,我要去改另一个需求了:“能不能接 Web3 钱包登录?” …… 我默默打开了 FastAPI 文档,顺手把 Lo-fi 播放列表切到了“Coding in the Rain”。

(完)

评论 0

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