从历史上看,管理员需要使服务器脱机才能更新和升级本地软件。 但是,对于全球 24×7 服务,停机时间是不可接受的。 许多现代云服务是用户运行其业务的关键依赖项。 从来没有好时机让系统关闭,那么团队在安装重要的安全和功能更新时如何提供持续服务?
通过使用版本控制更新,这些关键服务可以无缝地从一个版本过渡到另一个版本, 而客户则主动使用这些服务。 并非所有更新都很难。 更新前端布局或样式很容易。 对功能的更改可能很棘手,但有一些已知做法可以降低迁移风险。 但是,来自数据层的更改引入了一类需要特殊考虑的新挑战。
单独更新层
在多个数据中心和分布式数据存储中运行的在线服务中,并非所有内容都能同时更改。 如果典型服务拆分为应用程序代码和数据库(大概相互独立版本控制),则其中一方需要吸收处理版本控制的复杂性。
通常,在应用程序代码中,版本控制更易于处理。 较大的系统通常具有相当多的旧代码,例如驻留在其数据库中的 SQL。 应用程序代码应处理复杂性,而不是进一步使此 SQL 复杂化。 具体而言,可以创建一组了解 SQL 版本控制的工厂类。
在每个冲刺期间,使用该版本创建新接口,以便始终有与每个数据库版本匹配的代码。 可以在部署期间轻松回滚任何二进制文件。 如果在部署新二进制文件后出现问题,请还原到以前的代码。 如果二进制部署成功,请启动数据库服务。
那么,这实际上是如何运行的? 例如,假设团队当前正在部署 Sprint 123。 二进制文件了解 Sprint 123 数据库架构,并且了解 Sprint 122 架构。 通常的做法是同时使用 SQL 模式的版本 N 和 N-1。 二进制文件查询数据库,确定要与其通信的架构版本,然后加载相应的绑定。 然后,应用程序代码将处理新数据架构尚不可用的情况。 新版本可用后,应用程序代码就可以开始使用最新数据库版本启用的新功能。
仅使用数据层前滚
升级数据库后,如果出现问题,服务将处于 前滚 状态。 在线数据库迁移很复杂,通常是多个步骤组成,所以向前推进往往是解决问题的最佳方法。 换句话说,如果升级失败,则回滚也可能会失败。
投入构建和测试团队从未预计使用的回滚代码的努力几乎没有什么价值。
部署顺序
假设需要向数据库添加一组列并转换某些数据。 这种转换需要对用户不可见,这意味着尽可能避免表锁,然后尽可能短地保留锁,以便他们无法感知。
我们做的第一件事是处理数据,可能使用 SQL 触发器来保持并行表中的数据同步。大型数据迁移和转换有时必须通过多个部署,跨越多个迭代以多步骤进行。
并行创建额外数据或新架构后,团队将进入应用程序代码的 部署模式 。 在部署模式下,当代码调用数据库时,它会先获取架构上的锁,然后在运行存储过程后释放它。 在发出对数据库的调用和存储过程运行时,数据库无法更改。
升级代码充当架构编写器并请求架构上的编写器锁。 应用程序代码优先获取读取器锁,升级代码位于后台尝试获取编写器锁。 在编写器锁下,表上只允许进行少量非常快速的操作。 然后释放该锁,应用程序会记录新版本的数据库正在使用中,并使用与新数据库版本匹配的接口。
数据库升级都是使用迁移模式执行的。 一组代码和脚本查看数据库版本,然后进行增量更改,将架构从旧版本迁移到新版本。 所有迁移都是自动化的,并通过发布管理服务推出。
必须在不打扰用户的情况下更新 Web UI。 升级 JavaScript 文件、样式表或图像时,请避免客户端加载旧版本和新版本。 这可能会导致错误,可能会丢失用户正在编辑的字段等正在进行的工作。 因此,应通过将所有与部署有关的文件放入单独的版本文件夹中来对所有 JavaScript、CSS 和图像文件进行版本控制。 当 Web UI 调用回应用程序层时,将加载具有指定版本的资产。 只有当用户作导致完整页面刷新时,新的 Web UI 才会加载到浏览器中。 升级不会中断用户体验。
后续步骤
Microsoft几十年来一直是世界上最大的软件开发公司之一。 了解 Microsoft如何使用 DevOps 运行可靠的系统。