从单体到微服务:一个奶爸程序员的深夜实战手记

何明
2025-12-20 18:04
阅读 2309

上周五晚上十点半,娃终于睡了。我轻手轻脚地坐到MacBook前,打开终端,心里只有一个念头:再不把那个祖传单体应用拆了,我怕是连简历都投不出去。

入职新公司快两个月了,作为两个娃的奶爸程序员,白天写代码的时间少得可怜——不是在开会就是在救火,要不就是被产品经理追着问“这个功能明天能上线吗?” 晚上回家?等娃睡了才有自己的时间。但这次真拖不下去了:系统一到下午三点就慢如蜗牛,DB连接池爆满,运维大哥看我的眼神都带着怜悯。

而最扎心的是,前两天刷招聘软件,清一色写着“熟悉微服务架构、K8s经验优先”。我瞅了瞅自己那套跑在Docker Compose里的Python Flask单体应用,默默关掉了页面——这玩意儿放简历上,HR怕是要以为我还在2016年打工。


起点:那个“什么都干”的单体应用

我们原来的系统是个典型的Python Flask单体应用。用户管理、订单处理、支付回调、消息推送……全塞在一个代码库里。本地开发用flask run,测试环境靠docker-compose up,生产直接扔到一台4核8G的云服务器上,Nginx反代一下完事。

听起来是不是很熟悉?没错,这就是大多数创业公司或中小团队的起点。问题不大时,它确实香——改个接口五分钟上线,调试方便,日志集中。但一旦业务量上来,问题就来了:

  • 一个模块出问题(比如支付回调超时),整个服务卡死
  • 数据库表越建越多,耦合严重,改个字段要全盘评估
  • 部署必须全量更新,哪怕只改了一行前端JS
  • 团队协作困难:后端A改订单逻辑,后端B改用户模块,PR经常冲突

最要命的是,上次双11预演,系统直接崩了。监控显示PostgreSQL CPU飙到100%,连接数打满。原因?一个低效的SQL查询在高并发下被放大成灾难。那一刻,我知道:微服务不是“要不要做”,而是“必须马上做”。


拆!但别瞎拆

很多人一听微服务,立马开干:用户服务、订单服务、商品服务、通知服务……拆它十个八个。但作为一个被娃折腾得只剩半条命的老父亲,我深知——重构不是炫技,是解决问题

我和架构师(兼CTO,也是我直属领导)对齐了几个原则:

  1. 按业务边界拆,不是按技术分层
    别搞什么“数据库服务”、“缓存服务”,那叫中间件,不是微服务。
  2. 先拆高频、高风险模块
    支付和订单是痛点,优先动刀;静态内容(比如帮助中心)先不动。
  3. 通信方式统一用HTTP + JSON
    别整gRPC那一套,团队没人熟,后期维护成本高。简单稳定最重要。
  4. 每个服务必须可独立部署、可独立扩缩容

于是第一阶段,我们只拆出两个核心服务:

  • auth-service:负责用户注册、登录、鉴权
  • order-service:处理订单创建、状态变更

原单体保留其他功能,作为“遗留系统”逐步迁移。


工具链:奶爸的效率命脉

带娃的人没时间折腾花里胡哨的东西。工具必须简单、可靠、文档齐全。以下是我们的“微服务全家桶”:

类别 工具 选择理由
语言框架 Python + FastAPI 异步支持好,自动生成OpenAPI文档,比Flask更现代
容器化 Docker 公司已有镜像仓库,运维熟悉
编排 Kubernetes (K8s) 新公司用EKS,正好练手
服务发现 K8s Service + DNS 不引入Consul/etcd,减少复杂度
API网关 Nginx Ingress 现成,支持JWT验证
日志 Loki + Grafana 轻量,比ELK省资源
监控 Prometheus + Alertmanager 标配,K8s生态友好

特别说下FastAPI。以前用Flask总觉得少了点啥,FastAPI的Pydantic模型校验 + 自动生成Swagger,让我这个天天被产品改需求的人泪流满面——接口文档再也不用手写了!

# order-service 示例
from fastapi import FastAPI, Depends
from pydantic import BaseModel

app = FastAPI()

class CreateOrderRequest(BaseModel):
    user_id: int
    items: list[str]
    total_amount: float

@app.post("/orders")
def create_order(req: CreateOrderRequest):
    # 自动校验 req 是否符合模型
    # 如果传了个字符串给 user_id,直接422报错
    ...

这种“契约先行”的设计,在微服务间通信时简直是救命稻草。


坑?那可太多了

坑1:数据库怎么拆?

最头疼的不是代码,是数据。原来所有表都在一个PostgreSQL实例里。现在要拆,意味着:

  • auth-serviceauth_db
  • order-serviceorder_db

但订单里要有 user_id 啊!跨库关联查不了,怎么办?

我们的解法是:服务间通过API获取必要数据,不共享数据库

比如订单服务需要用户昵称?调 GET /users/{id} 接口。虽然多一次网络调用,但换来的是彻底解耦。为了性能,我们在订单创建时把关键用户信息(如昵称、手机号)冗余存一份——最终一致性,总比强一致拖垮系统强。

坑2:本地开发怎么联调?

以前 docker-compose up 一套全跑起来。现在五个服务,总不能每改一行代码就推镜像到K8s吧?

解决方案:Telepresence + Skaffold

  • Telepresence:把本地进程“注入”到K8s集群,就像你在集群里运行一样
  • Skaffold:监听代码变化,自动 build & deploy

我在Mac上改完代码,5秒内就能在K8s里看到效果。Windows?那台老伙计只用来测IE兼容性了,微服务开发?不存在的。

坑3:分布式事务怎么搞?

用户下单 → 扣库存 → 创建订单 → 发通知。任何一个环节失败,都要回滚。

我们没上Seata那种重量级方案,而是用Saga模式 + 补偿事务

  1. 创建订单(状态:pending)
  2. 调库存服务扣减
    • 成功:更新订单状态为 confirmed
    • 失败:调补偿接口(比如释放预占库存),订单状态设为 cancelled
  3. 异步发通知(失败可重试)

配合Kafka做事件驱动,失败任务进DLQ(死信队列),人工介入。虽然不是100%自动化,但在当前业务规模下,够用且可控。


效果:终于敢在简历写“微服务”了

重构花了三周,其中两周在改测试用例(别问,问就是血泪教训)。上线后:

  • 系统吞吐量提升3倍(从300 QPS到900+)
  • 订单服务可独立扩容,大促时CPU从95%降到40%
  • 一次支付回调故障,只影响支付模块,其他功能正常
  • 我终于能在简历的“项目经验”里堂堂正正写上:“主导单体应用向微服务架构演进,基于Python/FastAPI/K8s”

更重要的是,上周五晚上十点半,我提交完最后一个PR,看着Grafana里平稳的曲线,轻轻合上MacBook——娃还没醒,老婆在追剧,世界如此宁静。

或许这就是中年程序员的小确幸:代码跑得稳,娃睡得香,简历能投出去。


给同行的几句真心话

  • 别为了微服务而微服务:如果团队不到10人,业务没到一定规模,单体可能更合适。
  • 自动化测试是生命线:微服务多了,手工回归测试会疯掉。我们要求每个服务有90%+单元测试覆盖率。
  • 监控和日志不是可选项:没有可观测性,微服务就是分布式噩梦。
  • 简历可以写“微服务”,但别吹“高并发”:除非你真扛过百万QPS,否则容易被面试官问穿。

最后,如果你也是个下班后才有时间学习的奶爸/妈程序员,我想说:慢慢来,别焦虑。每一个深夜敲下的代码,都是未来跳槽时简历上闪亮的一行。

毕竟,我们不仅要写好代码,还要给娃换尿布呢。

评论 0

最热最新
暂无评论
何明Lv.1
0
影响力
0
文章
0
粉丝