几周前,当我使用 Mifa 主题刷新我的博客时,我发现了一件不得了的事情:我的博客使用的 Python 版本是 2.7,而不是我预期的 3.5。并且我用的 Django 版本是 1.9,它是 2015 年的版本。这些让我意识到,如果我再不做点什么,我的博客可能就维护不了了。
毕竟 Django 已经 2.0 了,而 Python 2.7 即将(好多年了)成为过去式了。
尽管我在之前的文章,讲述了一系列的遗留系统的问题,其中之一就是遗留技术栈。因为技术栈老旧而导致系统难以维护,并不是我们想要的结果,只是当它并不是我们的第一优先级事务时,短期的技术栈老旧也是可以接受的。
于是,我决定要去升级我的博客的技术栈了,理想中的步骤很简单:
升级核心框架
迁移应用代码
迁移数据库
只是在这个过程中,遇到一系列的坑。真实的步骤如下所示:
创建全新的环境
使用线上的数据进行测试及数据库迁移
更细力度的版本管理控制,方便回滚
优先升级次要组件的版本,以方便向上兼容性
一步步升级核心框架
必要时,自己编写组件代码
清理掉不需要的代码、文件
上线前使用线上的环境进行预部署
接着,让我来一步步复述这个过程。
1. 创建全新的环境
如上一篇文章《荐书《遗留系统:重建实战》:当你面对一坨代码时,你应该这么做 所说,我们应该对环境搭建这个任务进行计时,以便于找出系统的瓶颈。
首先,我遇到的第一个问题是:MBP 上没有环境。因为之前笔记本的硬盘坏了,上面的大部分资料都丢失了,因此我需要从头搭建一个环境。
对于我而言,这是一次相当不错的机会,它可以验证系统的本地配置是不是正确的。然后,我发现新的系统上也没有安装 virtualenv,于是不得不重从搭建一个环境。
然后,我按照我在博客源码上的 README,发现并没有搭建成功。这种感觉就像你拿着一本产品说明书,发现上面说的都是错的一样。
简单的总结一下这个阶段的几个原因:
本地配置文件
local_settings.py
文件不存在没有对数据为空的情形进行判断
当然还有其它一些问题,让我再细细说来。
2. 使用线上的数据进行测试及数据库迁移
在我早期的项目中,我们一般会拥有一份半年前的线上环境,用于在 staging(模拟环境)环境上进行测试。它可以用数据证明,这些功能本身是相当可靠的。
搭建环境的过程中,我尝试创建一个全新的测试数据库。但是,想一想发现这样做容易出现问题,于是便想找份线上的环境的数据库进行测试。虽然我并没有将我的各种环境充分的自动化,但是我会定期手动将数据库(SQLite3)备份到 AWS S3 上——我最初选择使用 SQLite3 的主要原因是,备份方便,不需要占用额外的服务器资源。对于一般的应用来说,使用 MySQL/MariaDB 才是正确的选择。SQLite 3 是我在博客设计初期做的一个错误的决策,当时 Too Young。未来,可能会将数据库切换到 DynamoDB 上。
再回到线上数据的这一问题上,除了应对数据本身的变化之外。还有一点是,Django 或者 Mezzanine 在升级的时候,都需要进行数据库迁移。
我之前将 Mezzanine 1.3 升级到 Mezzanine 3.0 的过程中,遇到一系列的数据库问题,最后不得不重建数据库。这个惨痛的经历告诉了我,数据库迁移是一个大坑。
3. 更细力度的版本管理控制,方便回滚
是的,即使你更新了个小依赖,也要确保使用了版本管理。它可以让我们随时能回滚到上一次个性,以确保迁移的正常。
如果你在某一时刻,你同时更新了 A、B、C 依赖,那么可能因此修改大量的代码。而在修改代码的过程中,一旦出错的话,那么回滚的难度就变得相当的大。
于是我提交的步伐,比以往的正常时候都更小了。小到只更新一行配置,我也会做一个提交。
反正最后是迁移成功了,不能证明这个策略是不是对的。但是,这样做是对的。
4. 优先升级次要组件的版本,以方便向上兼容性
对于项目中用到的一些辅助软件,如使用 djangorestframework
作为 RESTful API 框架,我优先升级了它们。对于这些框架而言,他们在兼容低版本的同时,也会兼容更版本的软件。但是高版本的 Django,有可能并不会兼容低版本的其它软件。因此,优先升级这些组件,可以保证核心组件可以更容易迁移。而不是在升级核心框架的同时,查看是否有这些次要组件带来的问题。
过去我一般不会采用固定依赖版本的方式来运行部署,如 Ruby 中的 Gemfile,Node.js 中的 Yarn.lock,Python 中采用的方式是:
django==1.10.7Mezzanine==4.2.3bleach==1.5djangorestframework==3.7.7 django-uuslug django-cors-headers djangorestframework-jwt django-widget-tweaks==1.4.1markdown==2.6.11
如上所见,我只会在重要的组件中,采用 fix 的版本号,主要是为了方便升级。对于次要组件来主,这种策略相当的成功。
5. 一步步升级核心框架
好了,现在到了最大的坑里,升级 Django 版本。
事实证明,直接对着 CHANGELOG 来修改代码,是最简单的一种升级方式。
起先我直接改 Django 改成了 2.0.1 版本,发现一系列的不兼容——主要是 Mezzanine CMS 引起的问题,于是只能转到 1.10.7。
然而,从 1.9.6 到 1.10.7 算是一个大的升级,为 2.0 移除了一系列不兼容的 API,如:
新的 TEMPLATES 配置,旧的版本中使用多个模板配置,而新的版本中只需要一个配置即可。
旧的
urls.py
全部升级,在新的 Django 中统一了路由的写法。不再需要的
future
标签,future 可以在低版本的 Python 上运行一些新的语言特性。
在迁移的过程中,还发现了一个第三方组件不支持新版本的 Django。
6. 必要时,自己编写组件代码
使用开源软件,便意味着:在你使用的过程中,作者有可能随时会弃坑;因此,随时要做好填坑的准备。在我迁移的过程中,也遇到了这样的问题。
默认使用的 markdown 编辑器,mezzanine_pagedown
中的 urls.py
使用的 pattern 已经被抛弃了。而这个 repo 几乎已经陷入了不维护的状态,于是我只得引入这个库到我的项目中,然后自己去修复这些问题。
好在升级起来还是蛮很容易的,后来仔细一读代码发现,这个库只是对 markdown 库进行了一些封装。因此,自己动手写了一个 markdown 的封装。
7. 清理掉不需要的代码、文件
早期使用 python manage.py collectstatic
的时候,留下了不同版本的静态文件,如早期的 jQuery 1.4.2、jquery-1.7.1.min.js、jquery-1.7.2.min.js 等等。
因此,便直接删除了旧的静态文件,这些文件有:
静态文件库
不使用的代码
就现在而言,清理这些旧文件,并不会带来额外的收益。但是,未来就不好说了。
8. 上线前使用线上的环境进行预部署
我在我的服务器上获取最新的版本,创建了新的虚拟环境,然后测试:
python manage.py runserver 8888
代码看来似乎是好的,于是我更新了启动脚本里的 虚拟环境
的路径,并使用启动脚本来运行服务。结果,系统并没有启动起来——原因是少了 gunicorn。
我忘了在 requirements.txt
中加油入 gunicorn
的依赖,
于是,我重新启动了服务,打开了 phodal.com,发现 500 了又。mdzz,
去看了看日志,发现线上使用了 memcached 作为数据缓存,这个配置写在 local_settings.py
文件中,但是没有添加在依赖中。
怪我咯。