三个月前我还在为一个单体Python项目焦头烂额。项目经理每天催进度,测试组天天报bug,我连喝水的时间都没有。项目越来越大,启动一次要等三分钟,改一行代码要重新跑整个测试套件。最离谱的是有一次我改了登录模块,结果把报表功能弄崩了。
老板拍桌子说下个月必须上线。我一咬牙,决定拆成微服务。
这个项目是用Django写的,主要做数据处理和报表生成。一开始特别小,就几个视图函数。后来客户加需求,从数据采集到最终展示全塞在一起。代码倒是能跑,就是改起来像拆炸弹。每加一个功能,我都要在admin里注册新模型,加新路由,写新视图。最后models.py有3000多行。
我跟老板说想拆微服务。他以为我要重构,吓得脸都白了。我解释说不是重写,是把功能拆开,各跑各的。他半信半疑地同意了。
我先拆的是数据采集模块。这个模块每天从各种API拉数据,用Celery定时任务跑。我把它单独拎出来,变成一个独立的Flask服务。数据库也分开,采集服务只管写原始数据,不碰其他表。接口一清二楚,就五个:拉数据、查状态、重试失败任务、清缓存、健康检查。
原来想拆报表生成模块。这个最头疼,报表逻辑跟视图模型混在一起。我写了一个纯函数的报表引擎,输入是SQL查询结果,输出是Pandas DataFrame。不依赖Django的任何东西。测试也好写,造几行数据就能验证逻辑。
拆完之后我写了一个API网关,用FastAPI搭的,就几百行代码。网关负责路由、鉴权、限流。每个微服务只暴露内部端口,没有公网地址。数据采集服务跑在端口5001,报表服务跑在5002,用户服务跑在5003。网关统一处理所有外部请求。
拆开第一个星期,最明显的变化是启动时间。原来三分钟,现在每个服务启动只需要几秒。开发的时候也不用跑整个项目了,改采集模块就只启动采集服务。我开三个终端窗口,各跑各的,改完直接重启对应服务,秒级完成。
测试也变得简单。每个服务有自己的测试容器,跑测试只需要加载自己需要的依赖。原来一个测试套件要跑四十分钟,现在每个服务单独跑,快的几分钟跑完。有一次我改报表引擎,拉数据服务出了bug,报表服务完全不受影响,照样能正常响应请求。
最让我意外的是老板的态度。有一天他主动跑来找我,说监控系统显示项目稳定性提高了不少。原来一个月要宕机四五次,拆完后一次没出过。其实不是微服务本身多厉害,而是每个服务变简单了,出问题的概率就小。单一职责的原则在代码层面就解决了。
后来我又拆了用户认证服务。原来的Django自带认证够用,但跟业务逻辑耦合太紧。我单独写了一个认证服务,用JWT做token。其他服务不存用户密码,只认token。这样一来,如果认证服务出问题,其他服务还能用缓存里的token继续工作。
现在整个项目跑在四台云服务器上,每个服务可以独立扩缩容。数据采集服务压力大就多开几个实例,报表服务访问少就只跑一个。用Docker Compose管理本地开发环境,生产环境用Kubernetes。运维同学一开始觉得麻烦,后来发现出问题只需要重启单个服务,不用整个项目停掉。
老板在周会上专门表扬了我,说这个架构改得好。他原话是“这人干活靠谱,不用盯着”。我心想其实没多高深,就是把大问题切成小问题,让每个小问题都变得简单。写Python的人都有感觉,一个模块变大了,第一反应应该是拆,而不是继续往里塞东西。
现在团队其他人也开始用这种方式开发。新来的同事想加功能,先看看挂哪个服务合理,然后单独开一个repo。代码审查也轻松了,一次只看一个服务的变更。不用再看三千行的diff,大脑不会过载。
唯一麻烦的是日志和监控。原来集中在一个地方查,现在分散了。我们搭了ELK集群,把各服务日志统一收。还写了几个健康检查脚本,每天凌晨跑一次,有问题自动发告警。这件事花了两天时间,但省掉了后续排查问题的精力。
总结起来就一句话:别怕拆,拆开之后你会发现原来觉得复杂的东西其实很简单。写Python的人最知道,一个函数超过五十行就该拆了,微服务也是同理。拆的时候先拆最稳定、变化最少的部分,比如数据采集。留下最复杂、变化最多的业务逻辑,等拆完了再慢慢处理。