已经写了很多有关不停地更新系统(零停机时间部署)的动态文章,这种方法的许多方面都是显而易见的。 我认为,在这种情况下,部署中最困难的部分是如果数据仓库的合同(方案)已更改,则更新数据仓库。 我想在本文中考虑的就是这一方面。
无论数据库是什么(具有关系式的显式数据方案或NoSQL的任意数据库),即使在应用程序级别,该数据方案仍然存在。 即使存储库本身不对其结构施加任何限制,从数据库读取的数据对客户端也应该是可理解的。
假设具有特定数据结构且数据库中数据达到TB的系统已经在生产中运行。 在新版本的系统中,我们需要稍微更改结构以实现新功能或提高性能。 考虑一下电路中可能发生的变化:
- 添加新字段
- 字段删除
- 重命名字段
- 栏位类型变更
- 将字段传输到另一个数据结构(例如,在非规范化的情况下)
添加新字段以及添加任何其他数据库对象是向后兼容的更改,并且在实现零停机时间部署方面不需要任何其他步骤(带有一个警告-如果此新字段或对象在功能上不依赖于已存储在数据库中的其他字段或对象,请注意)数据)。 只需将更改动态地应用于数据库,然后部署使用新数据库对象的新版本代码。
删除字段或任何其他数据库对象不是向后兼容的更改,但是实现该方法的方法非常简单-仅在系统的新版本完全死锁后才删除不必要的数据库对象。
就提供零停机时间部署而言,其他三种类型的更改更为复杂。 通常,所有这些操作都可以通过将数据复制到其他字段/实体,然后在成功迁移数据后删除“旧”字段来执行:要重命名,您可以将数据从旧字段复制到具有新名称的字段,然后删除旧字段,更改数据类型可以与重命名等一起完成 在一段时间内,数据库必须以某种方式支持新旧合同。 至少有两种方法可以即时进行此类更改:
如果数据库支持触发器
- 创建触发器,以便在进行任何更改/添加时将数据从旧地方复制到新地方,并在生产时进行设置。
- 应用一个功能相同的数据转换实用程序,但适用于数据库中的所有记录。 由于已经安装了触发器,因此该实用程序可能不会做任何事情,而不仅仅是对每个记录进行“虚拟”更新(UPDATE table SET字段= field ...)。 这里非常重要的一点是,从旧位置读取数据并写入新位置的操作应该是原子的,并应避免丢失更改。 根据数据库的结构,您可以通过SELECT FOR UPDATE或类似方法使用悲观锁定,或者如果表具有记录版本的字段,则可以使用乐观锁定。
- 该实用程序完成工作之后(取决于数据量和更新的复杂性,执行时间可能需要几天),已经可以安装支持新数据方案的新版本的系统。 此时,启动该实用程序时数据库中存在的所有记录将被成功转换,并且在其运行过程中出现的所有新记录也将由触发器转换。
- 删除触发器和不再需要的所有字段(或其他数据库对象)。
如果不可能使用触发器(许多NoSQL解决方案就是这种情况)

- 创建并部署新版本的应用程序(图中的临时版本1),该版本始终从旧字段读取,但是在写入该字段时,它会同时更新旧位置和相应的新位置(在图中“ C”-旧的“ H”-新)。 将这个版本Zadepli应用于运行应用程序实例的所有节点。
- 应用一个实用程序,将数据从旧位置复制到新位置。 与触发器一样,您需要采取措施来防止丢失更改。
- 创建该实用程序并完成后,安装该应用程序的另一个版本(临时版本2),该版本从新字段读取数据,但仍在两个位置写入。 此步骤是必需的,因为在每个节点的顺序更新期间,当读取旧字段的应用程序的先前版本的实例与新字段同时工作时,仍然存在间隙。
- 创建并在上一个版本的完整扫描结束时部署最终版本,该版本已经不与旧字段交互。
- 删除旧字段。
第二种方法需要创建和安装应用程序的三个不同版本,这可能非常不方便且麻烦。 取而代之的是,您可以使用功能切换-将所有三个版本的逻辑合二为一,但是根据配置参数切换模式,理想情况下可以动态切换。 因此,无需安装每个后续版本,只需更改参数的值即可(如果未提供动态更新配置,则重新启动服务)。 成功完成最终版本的安装后,所有与确保数据迁移相关的代码都应从工作分支中完全删除,即使该代码在生产环境中“生效”直到下一次系统更新。
很容易注意到,在更新系统时确保零停机是一项繁琐而脆弱的过程,因此只有在业务部门有相应要求的情况下,才有必要打扰一下。 但是,即使对系统可用性的要求非常低(例如,每年99%,并且计划的系统更新时间为24小时),安装新版本所需的数据转换仍可能会花费更长的时间。 因此,如果您计划存储大量数据,则需要提前准备好使用此类解决方案。
一种替代方法可能是有意拒绝数据库模式中的向后不兼容的更改,但是,不幸的是,实际上并非总是可以实现的,因为提高数据访问性能的最有效方法通常是重组模式。