背景介绍
由 Heroku 创始人 Adam Wiggins 提出的 12 条要素,定义了优雅的互联网应用设计的 12 条基本原则,参照这些原则可以帮助我们的应用更加健壮,拓展起来更方便。主要包含如下所示的优势:
- 使用标准化流程自动配置,从而使新的开发者花费最少的学习成本加入这个项目。
- 和操作系统之间尽可能的划清界限,在各个系统中提供最大的可移植性。
- 适合部署在现代的云计算平台,从而在服务器和系统管理方面节省资源。
- 将开发环境和生产环境的差异降至最低,并使用持续交付实施敏捷开发。
- 可以在工具、架构和开发流程不发生明显变化的前提下实现扩展。
本文主要参考 12 factors 和 Abby Fuller 的演讲 ,演讲是在 YouTube 上的,请自备梯子访问
具体准则
基准代码
一份基准代码,多份部署
单个应用只有一份基准代码,供所有部署环境使用,包括开发环境,测试环境,生产环境等
依赖
显式声明依赖,依赖隔离
显式声明依赖关系,通过依赖文件可以安装应用运行所需的全部依赖,应用运行不应该存在其他隐式依赖。引用也不应该依赖应用运行的主机上存在所需的依赖。在 Python 项目中一般使用 requirements.txt
声明依赖,而在 Node.js 中一般使用 package.json
声明依赖。
在运行过程中,应该通过依赖隔离工具来避免应用调用到系统中存在但依赖中未声明的依赖项,方便统一开发环境,测试环境与生产环境。极力需要避免出现在开发环境中实际使用了额外的依赖项,但是在线上因为不存在此依赖项而出错。
配置
在环境中存储配置信息
类似上面的图示,应用应该从实际执行的环境变量中读取配置信息,从而支持测试环境和生产环境的不同部署
配置信息保存至代码中,可能会有敏感信息暴露的危险,而且配置信息的更新也会更加麻烦
后端服务
将后端服务当做附加资源
后端服务是指程序运行可以通过网络调用的各种服务,比如:
- 数据库 (Mongo, MySQL)
- 缓存服务 (Redis)
- 消息队列(rabbitMQ)
本原则是期望保持应用与后端服务的松耦合,始终通过资源地址去访问后端资源,不加区分地访问本地和第三方服务。这样可以不用修改代码就可以将本地的服务切换为第三方服务。
构建,发布,运行
严格分离构建和运行阶段
构建阶段: 是指将代码仓库转化为可执行包的过程。构建时会使用指定版本的代码,获取和打包依赖项,编译成二进制文件和资源文件。
发布阶段: 将构建的结果和当前部署所需配置相结合,保证能够立刻在运行环境中投入使用。
运行阶段: 是指针对选定的发布版本,在执行环境中启动一系列应用程序进程。
应用应该严格区分构建,发布,运行阶段,保证部署可回退
每个发布版本都带有一个唯一的 id,方便追溯。
进程
以一个或多个无状态的进程执行应用
应用的进程应该是无状态且无共享的,任何需要持久化的数据都应该存储至后端
端口绑定
通过端口绑定提供服务
应用应该通过端口绑定提供服务,应用本身监听和处理发送给该端口的请求
正如上面的图示可见,请求最终会发送给绑定了端口的网络进程进行处理
并发
通过进程模型进行扩展
将任务划分不同的进程类型,比如包含 web 进程,worker 进程,api 处理进程等。各种不同的进程都可以独立根据实际情况进行水平扩展
易处理
通过快速启动和优雅终止最大化健壮性
快速启动是指进程应该追求最小启动时间,快速启动在下面情况下体现明显优势:
- 处理流量激增时可以快速扩展
- 进程崩溃是可以迅速替换
- 进程迁移
优雅终止是进程收到 SIGTERM
可以优雅终止。一般的网络进程会在接收到终止信号时,拒绝接受新请求,并继续处理已接收的请求,处理完成后终止
开发环境与线上环境等价
尽可能保证开发环境,测试环境与生产环境相同
应用应该尽可能地缩小本地和线上的差异,避免出现异常直到产品阶段才被发现的情况
避免为不同的环境配置不同的后端服务,比如本地使用轻量级数据库,线上使用更健壮的数据库。这会导致一些兼容性问题无法被提前发现。
日志
将日志当作事件流
应用不应该考虑日志的存储与管理,应该直接进行标准输出。在本地环境,可以直接看到日志,在测试环境和生产环境,由运行环境截获所有的输出流,将日志统一处理。
管理进程
将管理任务当做一次性进程
管理任务不可避免,比如如下场景:
- 迁移数据库
- 修复异常导致的脏数据
- 将数据库中的冷数据打包存储
管理任务的进程应该与常规进程使用同样的环境,保证与其他进程相同的要求,保证依赖存在,无状态,基于特定的发布版本运行,从而避免出现在特定情况上无法正确执行的问题
总结
12 准则是一些值得遵照的互联网应用设计的规范,遵守这些规范可以让你的应用更加标准化,应用的可移植性,扩展性,以及稳健性都能得到提升。算是前人踩坑无数后的经验之谈了。