Sora公测之后,我们的视频生成系统流量暴涨,原来的代码彻底扛不住了。

说实话,原来的代码写得很烂。因为是早期快速迭代出来的,各种临时方案、硬编码、复制粘贴,能跑就行。Sora没公测的时候,用户少,问题还不明显。公测之后,用户量翻了几十倍,各种问题全暴露出来了:响应慢、经常崩溃、bug修不完、加个新功能要改好几天。

老板拍板:重构。给我们两周时间,把系统重写一遍。

这篇文章,我想分享一下这次代码重构的过程,聊聊我们是怎么把一堆烂代码,重构成优雅、可维护、高性能的系统的。

重构前的烂代码有多烂

先说说重构前的代码有多烂。

我们的系统是一个AI视频生成平台,用户输入文字描述,系统调用Sora API生成视频,然后处理、存储、返回给用户。

重构前的代码,主要有这么几个问题:

第一个问题是,所有逻辑都写在一个文件里。整个后端就是一个几千行的Python文件,API接口、业务逻辑、数据库操作、第三方API调用、视频处理,全混在一起。改一个地方,可能影响到其他地方,每次改代码都提心吊胆。

第二个问题是,硬编码满天飞。API Key、数据库地址、文件路径、超时时间、各种参数,全是硬编码在代码里的。换个环境就要改代码,而且经常忘了改某个地方,导致线上出问题。

第三个问题是,没有错误处理。调用Sora API失败了怎么办?视频处理出错了怎么办?数据库连接不上怎么办?这些都没有处理。出了问题,就是一个500错误,用户看到一片空白,我们也不知道哪里出了问题。

第四个问题是,复制粘贴严重。类似的逻辑,在不同的地方复制了好几份。比如,调用Sora API的代码,在三个地方都有,每份还不太一样。改一个bug,要改好几个地方,经常漏改。

第五个问题是,没有日志和监控。出了问题,只能靠猜。用户说生成失败了,我们不知道是API调用失败了,还是视频处理出错了,还是存储出问题了。排查问题全靠翻代码、加print、重新部署。

第六个问题是,性能差。视频生成是异步的,但我们的代码是同步等待的。一个请求要等几分钟,占用一个连接。并发一高,连接池就满了,新请求进不来。

第七个问题是,没有测试。整个项目一个测试都没有。改完代码,只能手动测几个场景,经常改出bug,上线之后才发现。

这样的代码,在用户量小的时候还能凑合,但Sora公测之后,用户量暴涨,这些问题全变成了灾难。

重构的目标和原则

开始重构之前,我们先明确了目标和原则。

重构的目标:

  • 系统能支撑10倍于现在的并发量。
  • 代码结构清晰,新人能快速上手。
  • 有完善的错误处理和日志监控,出了问题能快速定位。
  • 加新功能的效率提升50%以上。
  • 有基本的测试覆盖,核心逻辑有单元测试。

重构的原则:

第一个原则是,不改变外部行为。重构是内部结构的优化,对外的API接口、功能、行为都不变。用户感知不到重构,只觉得系统变快了、变稳定了。

第二个原则是,小步快跑,逐步替换。不是一次性把所有代码都重写,而是分模块、分步骤地重构。每重构完一个模块,就部署上线,验证没问题了再继续下一个。这样风险小,出了问题也容易回滚。

第三个原则是,先搭骨架,再填肉。先把整体架构搭好,定义好模块之间的接口,然后逐个模块把旧代码迁移过来。这样,即使重构没做完,新架构也能跑起来。

第四个原则是,每一步都可验证。重构完一个模块,就跑测试、做对比验证,确保新代码和旧代码的行为一致。不能凭感觉说"应该没问题"。

第一步:分层架构设计

重构的第一步,是重新设计架构。

我们把原来的"大泥球"架构,改成了清晰的分层架构:

第一层是,接口层(API Layer)。负责接收HTTP请求,参数校验,返回响应。这一层很薄,不包含业务逻辑。

第二层是,业务层(Service Layer)。负责核心的业务逻辑,比如创建视频生成任务、查询任务状态、管理用户配额等。

第三层是,集成层(Integration Layer)。负责和外部系统的交互,比如调用Sora API、调用视频处理服务、操作对象存储。

第四层是,数据层(Data Layer)。负责数据库操作,封装所有的SQL和ORM调用。

第五层是,基础设施层(Infrastructure Layer)。负责配置管理、日志、监控、消息队列、缓存等通用设施。

每一层只能调用下一层,不能跨层调用,也不能反向调用。这样,各层之间的依赖关系清晰,改一层不会影响其他层。

除了分层,我们还按业务领域做了模块划分。比如,视频生成是一个模块,用户管理是一个模块,计费是一个模块。每个模块内部有自己的Service、Repository、Model,模块之间通过接口通信,不直接依赖内部实现。

这个架构设计,花了我们两天时间。但这两天花得值,因为后面的开发都是在这个架构上进行的,方向对了,效率就高。

第二步:配置管理和硬编码清理

架构搭好之后,我们做的第一件事,是把所有的硬编码清理掉。

我们用了一个配置管理库(pydantic-settings),把所有的配置都集中到一个地方:

  • 数据库连接信息
  • Sora API的Key和地址
  • 对象存储的Access Key和Secret
  • 各种超时时间和重试次数
  • 功能开关

配置从环境变量读取,不同的环境(开发、测试、生产)用不同的环境变量。代码里不再有任何硬编码的配置。

这样做的好处是:

  • 换环境不需要改代码,只需要改环境变量。
  • 敏感信息(API Key、密码)不会出现在代码里,更安全。
  • 配置集中管理,一目了然,不会散落在代码的各个角落。

我们还加了配置校验,启动的时候检查所有必要的配置是否都设置了,如果缺了,直接报错退出,而不是跑到一半才发现配置不对。

第三步:异步化和任务队列

原来的系统最大的性能问题,是同步等待。

视频生成是一个长时间的任务,调用Sora API生成一个视频,可能需要几分钟。原来的代码是同步等待的,一个请求占用一个连接几分钟,并发一高就扛不住。

重构之后,我们改成了异步架构:

  • 用户提交生成请求,接口层立刻返回一个任务ID,不等待生成完成。
  • 任务放到消息队列(我们用的是Redis Queue)里。
  • 后台的Worker进程从队列里取任务,调用Sora API生成视频。
  • 生成完成后,更新任务状态,把视频存到对象存储。
  • 用户通过任务ID查询生成状态和结果。

这样,接口层的响应时间从几分钟降到了几十毫秒,能支撑的并发量提升了几十倍。

我们还做了任务队列的优先级和限流:

  • 付费用户的任务优先级高,免费用户的任务优先级低。
  • 每个用户有并发限制,防止一个用户占用所有资源。
  • 队列有长度限制,满了之后拒绝新任务,返回"系统繁忙"的提示,而不是把系统拖垮。

Worker进程可以水平扩展,任务多的时候多加几个Worker,任务少的时候减少Worker。这样,系统的处理能力可以根据负载动态调整。

第四步:错误处理和重试

原来的代码几乎没有错误处理,重构之后,我们给每个可能出错的地方都加了错误处理。

具体来说:

第一,调用Sora API的时候,加了重试机制。网络超时、API限流、5xx错误,自动重试,最多重试3次,每次重试的间隔递增(指数退避)。

第二,区分可重试错误和不可重试错误。比如,网络超时是可重试的,参数错误是不可重试的。不可重试的错误,直接返回给用户,不浪费时间重试。

第三,每个任务有明确的状态流转:等待中、处理中、成功、失败。失败的任务,记录失败原因,用户可以看到具体的错误信息,也可以重试。

第四,所有的错误都有详细的日志。错误发生的时间、任务ID、用户ID、错误类型、错误信息、堆栈跟踪,都记录下来。出了问题,通过日志能快速定位。

第五,有降级策略。如果Sora API不可用,系统自动切换到备用的视频生成模型,或者提示用户"当前服务繁忙,请稍后再试",而不是直接崩溃。

第五步:日志和监控

原来的系统没有日志和监控,出了问题全靠猜。重构之后,我们加了完善的日志和监控。

日志方面:

  • 每个请求有一个唯一的Request ID,贯穿整个处理流程,方便追踪。
  • 关键节点都有日志:请求进来、任务创建、API调用、视频处理、任务完成、错误发生。
  • 日志分级别:DEBUG、INFO、WARNING、ERROR。生产环境只记录INFO及以上,开发环境记录DEBUG。
  • 日志集中收集到ELK,支持按Request ID、用户ID、任务ID搜索。

监控方面:

  • 业务指标:任务总数、成功率、平均生成时间、队列长度、活跃用户数。
  • 系统指标:CPU、内存、磁盘、网络、Worker进程数。
  • 外部依赖指标:Sora API的调用次数、成功率、响应时间、错误率。
  • 告警:关键指标超过阈值,自动发告警到钉钉和邮件。

有了日志和监控,出了问题不再是"用户说用不了,我们不知道为什么",而是"监控告警了,我们看一下日志,几分钟就定位到问题"。

第六步:代码质量和测试

重构的最后一步,是提升代码质量和加测试。

代码质量方面:

  • 统一了代码风格,用Black做代码格式化,用Flake8做代码检查。
  • 函数和类都有文档字符串,说明功能、参数、返回值。
  • 复杂的逻辑有注释,说明为什么这么做。
  • 代码审查(Code Review),每个PR至少有一个人审查之后才能合并。
  • 消除了重复代码,把公共逻辑抽成工具函数和基类。

测试方面:

  • 核心业务逻辑加了单元测试,覆盖率达到了70%以上。
  • 加了集成测试,测试整个流程从创建任务到生成完成。
  • CI/CD流水线,每次提交代码自动跑测试和代码检查,不通过不能合并。
  • 加了契约测试,确保对外的API接口不发生破坏性变更。

有了测试之后,改代码就有信心了,不用担心改出bug。以前改代码要手动测半天,现在跑一遍测试,几分钟就知道有没有问题。

重构的效果

两周之后,重构完成,新系统上线。

效果非常明显:

第一个效果是,性能提升。接口响应时间从平均30秒(同步等待的时候)降到了50毫秒。系统能支撑的并发量,从原来的几十个并发,提升到了几千个并发。

第二个效果是,稳定性提升。上线一个月,没有出现过系统崩溃的情况。Sora API偶尔出问题,系统能自动重试和降级,用户几乎感知不到。

第三个效果是,开发效率提升。加一个新功能,以前要改好几天,现在因为结构清晰,一两天就能搞定。新人入职,看一遍架构文档和代码,一周就能上手开发。

第四个效果是,问题排查效率提升。以前出了问题,排查要几个小时,现在看监控和日志,几分钟就能定位。

第五个效果是,团队心情变好了。以前面对一堆烂代码,每天都很痛苦,改代码像拆炸弹。现在代码结构清晰,写代码变成了一种享受。

重构中的坑和经验

这次重构,我们也踩了不少坑,总结一些经验。

第一个坑是,不要试图一次性重写所有代码。我们一开始想两周内把所有代码都重写,后来发现不现实。改成了分模块逐步替换,先把核心的视频生成流程重写,其他模块后面慢慢迁移。这样风险小,也能早点看到效果。

第二个坑是,重构期间不要加新功能。重构期间,如果同时加新功能,会让重构变得复杂,容易出问题。我们的做法是,重构期间冻结新功能,只修bug。重构完成之后,再开始加新功能。

第三个坑是,一定要有回归测试。重构最担心的是,新代码和旧代码的行为不一致。我们在重构之前,先写了一批端到端的测试用例,重构之后跑同样的测试,确保行为一致。这一点非常重要。

第四个坑是,不要过度设计。重构的时候,容易想"这次一定要设计一个完美的架构,以后都不用改了"。但实际上,没有完美的架构,需求会变,技术会变。设计够用就好,不要为了不确定的未来,做过度的抽象和设计。

第五个坑是,和团队对齐。重构不是一个人的事,要整个团队都理解新的架构和规范,并且遵守。我们在重构之前,开了好几次会,讨论架构设计,达成共识之后才开始动手。

写在最后

这次Sora公测后的代码重构,是我职业生涯中最有成就感的项目之一。

看着一堆烂代码,在自己手里变成了结构清晰、性能优异、可维护的系统,那种成就感是无法形容的。

代码重构,不是为了重构而重构,而是为了让系统能支撑业务的发展,让团队能更高效地工作。当你的代码已经成为业务发展的瓶颈的时候,重构就是必须的。

如果你也在维护一堆烂代码,每天都很痛苦,我的建议是:不要抱怨,行动起来。先从最小的改进开始,比如清理硬编码、加日志、加测试,然后逐步重构架构。烂代码不是一天写成的,也不是一天能改完的,但只要开始,就会越来越好。

希望我们的重构经验,能给正在做类似事情的朋友一些参考。如果你也有代码重构的经验或者问题,欢迎在评论区交流。