领先一步
VMware 提供培训和认证,助您加速进步。
了解更多本文将深入探讨如何处理与数据库兼容性和部署过程相关的问题。我们将展示在未做好准备的情况下尝试进行此类部署时,您的生产应用程序可能会发生什么。然后,我们将逐步讲解应用程序生命周期中实现零停机的必要步骤。我们的操作结果将以向后兼容的方式应用一个向后不兼容的数据库更改。
如果您想动手操作下面的代码示例,您可以在 GitHub 上找到所需的一切。
什么是零停机部署?如果您的应用程序可以在不让用户察觉到应用程序在此期间宕机的情况下成功引入新版本,那么就可以说您的应用程序是以这种方式部署的。从用户和公司的角度来看,这是最佳的部署场景,因为可以在没有任何中断的情况下引入新功能并修复错误。
如何实现这一点?有很多方法,但其中一种方法就是直接
部署服务 V1
将数据库迁移到新版本
并行部署服务 V2,与 V1 一起运行
一旦您看到 V2 运行得非常出色,就关闭 V1
大功告成!
简单吧?不幸的是,事情并没有那么简单,我们稍后会重点讨论这一点。现在,让我们来看看另一种常见的部署流程,即蓝绿部署。
您听说过蓝绿部署吗?使用 Cloud Foundry 可以非常轻松地实现。只需查看这篇文章,其中我们对此进行了更深入的描述。快速回顾一下,进行蓝绿部署就像
维护生产环境的两个副本(“蓝色”和“绿色”);
通过将生产 URL 映射到蓝色环境,将所有流量路由到蓝色环境;
在绿色环境中部署和测试应用程序的任何更改;
通过将 URL 映射到绿色环境并从蓝色环境取消映射来“切换开关”。
蓝绿部署是一种方法,可以轻松引入新功能,而无需担心生产环境会彻底崩溃。这是因为即使发生了这种情况,您也可以通过“切换开关”轻松地将路由器回滚到之前的环境。
阅读完以上内容后,您可能会问自己一个问题:零停机部署与蓝绿部署有什么关系?
嗯,它们有很多共同点,因为维护同一环境的两个副本需要加倍的支持工作。这就是为什么一些团队,正如Martin Fowler 所述,倾向于采用该方法的一种变体
另一种变体是使用相同的数据库,对 Web 层和域层执行蓝绿切换。
对于这种技术,数据库经常是一个挑战,特别是当您需要更改数据库模式以支持新版本的软件时。
到这里,我们就来到了本文将要探讨的主要问题。数据库。让我们再仔细看看这句话
将数据库迁移到新版本
现在您应该问自己一个问题——如果数据库更改是向后不兼容的怎么办?我的 V1 应用程序不会崩溃吗?事实上,它会的……
因此,尽管零停机/蓝绿部署的好处巨大,但公司往往会遵循一个更安全的应用程序部署流程
准备一个包含应用程序新版本的软件包
关闭正在运行的应用程序
运行数据库迁移脚本
部署并运行应用程序的新版本
在本文中,我们将更深入地介绍如何处理数据库和代码,以便您能够从零停机部署中受益。
如果您的应用程序是无状态的,不将任何数据存储在数据库中,那么您可以立即开始零停机部署。不幸的是,大多数软件都需要将数据存储在某个地方。因此,在进行任何模式更改之前,您必须三思而后行。在详细介绍如何以允许零停机部署的方式更改模式之前,我们先关注模式版本控制。
在本文中,我们将使用 Flyway 作为模式版本控制工具。理所当然,我们还将编写一个具有原生 Flyway 支持的 Spring Boot 应用程序,该应用程序将在应用程序上下文设置时执行模式迁移。使用 Flyway 时,您可以将迁移脚本存储在项目的文件夹中(默认情况下在 classpath:db/migration 下)。这里有一个迁移文件的示例
└── db
└── migration
├── V1__init.sql
├── V2__Add_surname.sql
├── V3__Final_migration.sql
└── V4__Remove_lastname.sql
在此示例中,我们可以看到 4 个迁移脚本,如果之前未执行过,将在应用程序启动时逐个执行。让我们以其中一个文件(V1__init.sql)为例。
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');
这非常容易理解:您可以使用 SQL 定义如何更改数据库。有关 Spring Boot 和 Flyway 的更多信息,请查看 Spring Boot 文档。
通过将模式版本控制工具与 Spring Boot 结合使用,您可以获得两个巨大的好处。
您将数据库更改与代码更改解耦
数据库迁移与应用程序部署同步——您的部署流程得到了简化
在本文的下一部分,我们将重点介绍两种数据库更改的方法。
向后不兼容
向后兼容
第一种方法将作为警告,提醒您不要在没有进行一些准备的情况下尝试进行零停机部署。第二种方法将提出一个建议的解决方案,说明如何同时实现零停机部署并保持向后兼容性。
我们将使用一个简单的 Spring Boot Flyway 应用程序,该应用程序有一个 Person,在数据库中具有 first_name 和 last_name 字段。我们希望将 last_name 列重命名为 surname。
在深入研究细节之前,我们需要对我们的应用程序做出一些假设。我们希望达到的关键结果是拥有一个相当简单的流程。
提示
业务专业提示。简化流程可以为您节省大量支持成本(您的公司雇佣的人越多,您可以节省的钱就越多)!
我们不想回滚数据库
不回滚数据库可以简化部署流程(某些数据库回滚几乎不可能,例如回滚删除)。我们宁愿只回滚应用程序。这样,即使您拥有不同的数据库(例如 SQL 和 NoSQL),您的部署管道看起来也会相同。
我们希望始终能够将应用程序回滚到上一个版本(仅一个版本)
我们希望仅在必要时才回滚。如果当前版本中存在一个不易解决的 bug,我们希望能够恢复到上一个可用的版本。我们假设上一个可用的版本就是前一个版本。维护比一个部署版本更多的代码和数据库兼容性将极其困难且成本高昂。
提示
为了便于阅读,本文中的应用程序版本将采用大版本递增。
应用程序版本:1.0.0
数据库版本:v1
这将是我们考虑的应用程序的初始状态。
数据库包含一个名为 last_name 的列。
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');
应用程序将 Person 数据存储在名为 last_name 的列中
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://apache.ac.cn/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastname) {
this.lastName = lastname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
+ "]";
}
}
让我们看看如果您想更改列名
警告
下面的示例是故意设计成会中断的。我们展示它是为了说明数据库兼容性问题。
应用程序版本:2.0.0.BAD
数据库版本:v2bad
当前的更改不允许我们同时运行两个实例(旧的和新的)。因此,零停机部署将难以实现(如果考虑到我们的假设,实际上是不可能的)。
当前情况是我们有一个部署到生产环境的版本为 1.0.0 的应用程序和版本为 v1 的数据库。我们想要部署应用程序的第二个实例,它将是版本 2.0.0.BAD,并将数据库更新到 v2bad。
步骤
部署版本为 2.0.0.BAD 的新实例,该实例将数据库更新到 v2bad
在 v2bad 数据库中,last_name 列不再存在——它已更改为 surname
数据库和应用程序升级成功,并且您有一些实例运行在 1.0.0,其他实例运行在 2.0.0.BAD。所有实例都连接到 v2bad 数据库
所有 1.0.0 版本的实例都将开始产生异常,因为它们将尝试将数据插入到已不存在的 last_name 列中
所有 2.0.0.BAD 版本的实例都将正常运行,没有任何问题
如您所见,如果我们对数据库和应用程序进行向后不兼容的更改,A/B 测试是不可能的。
假设在尝试进行 A/B 部署后,我们决定需要将应用程序回滚到 1.0.0 版本。我们假设我们不想回滚数据库。
步骤
我们关闭了运行 2.0.0.BAD 版本的实例
数据库仍然是 v2bad
由于 1.0.0 版本不理解 surname 列是什么,它将产生异常
情况变得糟糕,我们无法恢复
如您所见,如果我们对数据库和应用程序进行向后不兼容的更改,我们就无法回滚到之前的版本。
Backward incompatible scenario:
01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0.BAD
05) Wait for the app (2.0.0.BAD) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0 <-- this should fail
07) Generate a person by calling POST localhost:9992/person to version 2.0.0.BAD <-- this should pass
Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}
Starting app in version 2.0.0.BAD
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
curl: (22) The requested URL returned error: 500 Internal Server Error
Generate a person in version 2.0.0.BAD
Sending a post to 127.0.0.1:9995/person. This is the response:
{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}
迁移脚本将列从 last_name 重命名为 surname
初始 Flyway 脚本
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');
重命名 last_name 的脚本。
-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;
我们将字段名从 lastName 更改为 surname。
这是我们最常遇到的情况。我们需要执行向后不兼容的更改。我们已经证明,要实现零停机部署,我们不能简单地在没有额外工作的情况下应用数据库迁移。在本节中,我们将通过三个应用程序部署以及数据库迁移来实现所需效果,同时保持向后兼容性。
提示
作为提醒——让我们假设我们有版本为 v1 的数据库。它包含 first_name 和 last_name 列。我们想将 last_name 改为 surname。我们还有一个版本为 1.0.0 的应用程序,它还未使用 surname 列。
应用程序版本:2.0.0
数据库版本:v2
通过添加新列并复制其内容,我们进行了向后兼容的数据库更改。目前,如果我们回滚 JAR 或同时运行旧 JAR,它也不会在运行时中断。
步骤
迁移您的数据库以创建名为 surname 的新列。现在您的数据库是 v2
将数据从 last_name 列复制到 surname。注意,如果您有大量此类数据,则应考虑分批迁移!
编写代码以同时使用新列和旧列。现在您的应用程序是 2.0.0 版本
从 surname 列读取 surname 值(如果它不为 null),如果 surname 未设置,则从 last_name 读取。您可以从代码中删除 getLastName(),因为它会在您的应用程序从 3.0.0 回滚到 2.0.0 时产生 null 值。
如果您使用的是 Spring Boot Flyway,这两步将在应用程序 2.0.0 版本启动时执行。如果您手动运行数据库版本控制工具,则需要将其分开放置(先手动升级数据库版本,然后部署新应用程序)。
重要
请记住,新创建的列不得设置为非空。如果您回滚,旧应用程序不知道新列,并且在 Insert 时不会设置它。但是,如果您添加该约束并且您的数据库是 v2,则需要设置新列的值。这将导致约束违例。
重要
您应该删除 getLastName() 方法,因为在 3.0.0 版本中,代码中没有 last_name 列的概念。这意味着将设置 null 值。您可以保留该方法并添加 null 检查,但更好的解决方案是确保在 getSurname() 的逻辑中选择正确的非空值。
当前情况是我们有一个部署到生产环境的版本为 1.0.0 的应用程序和版本为 v1 的数据库。我们想要部署应用程序的第二个实例,它将是版本 2.0.0,并将数据库更新到 v2。
步骤
部署版本为 2.0.0 的新实例,该实例将数据库更新到 v2
与此同时,一些请求被运行在 1.0.0 版本的实例处理
升级成功,并且您有一些实例运行在 1.0.0,其他实例运行在 2.0.0。所有实例都连接到 v2 数据库
1.0.0 版本不使用数据库的 surname 列,而 2.0.0 版本使用。它们不会相互干扰,不会抛出异常。
2.0.0 版本将数据保存到旧列和新列中,因此它是向后兼容的
重要
如果您有任何基于旧/新列的值进行计数查询,您必须记住现在您有重复的值(很可能仍在迁移中)。例如,如果您想计算姓氏(无论您如何称呼它)以字母 A 开头的用户数量,那么直到数据迁移(旧→新列)完成,如果您对新列执行查询,您可能会得到不一致的数据。
当前情况是我们有一个版本为 2.0.0 的应用程序和版本为 v2 的数据库。
步骤
将应用程序回滚到 1.0.0 版本。
1.0.0 版本不使用数据库的 surname 列,因此回滚应该是成功的
数据库包含一个名为 last_name 的列。
初始 Flyway 脚本
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');
添加 surname 列的脚本。
警告
切记不要为添加的列添加 NOT NULL 约束。因为如果您回滚 JAR,旧版本没有添加列的概念,并且会自动设置 NULL 值。如果有约束,旧应用程序将会崩溃。
-- NOTE: This field can't have the NOT NULL constraint cause if you rollback, the old version won't know about this field
-- and will always set it to NULL
ALTER TABLE PERSON ADD surname varchar(255);
-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
UPDATE PERSON SET PERSON.surname = PERSON.last_name
我们将数据存储在 last_name 和 surname 列中。此外,我们从 last_name 列读取数据,因为它最新。在部署过程中,一些请求可能已经被尚未升级的实例处理。
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://apache.ac.cn/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String surname;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
/**
* Reading from the new column if it's set. If not the from the old one.
*
* When migrating from version 1.0.0 -> 2.0.0 this can lead to a possibility that some data in
* the surname column is not up to date (during the migration process lastName could have been updated).
* In this case one can run yet another migration script after all applications have been deployed in the
* new version to ensure that the surname field is updated.
*
* However it makes sense since when looking at the migration from 2.0.0 -> 3.0.0. In 3.0.0 we no longer
* have a notion of lastName at all - so we don't update that column. If we rollback from 3.0.0 -> 2.0.0 if we
* would be reading from lastName, then we would have very old data (since not a single datum was inserted
* to lastName in version 3.0.0).
*/
public String getSurname() {
return this.surname != null ? this.surname : this.lastName;
}
/**
* Storing both FIRST_NAME and SURNAME entries
*/
public void setSurname(String surname) {
this.lastName = surname;
this.surname = surname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName + ", surname=" + this.surname
+ "]";
}
}
应用程序版本:3.0.0
数据库版本:v3
通过添加新列并复制其内容,我们进行了向后兼容的数据库更改。目前,如果我们回滚 JAR 或同时运行旧 JAR,它也不会在运行时中断。
当前情况是我们有一个版本为 3.0.0 的应用程序和版本为 v3 的数据库。3.0.0 版本不将数据存储到 last_name 列中。这意味着最新的信息存储在 surname 列中。
步骤
将应用程序回滚到 2.0.0 版本。
2.0.0 版本同时使用 last_name 和 surname 列。
2.0.0 版本首先选择 surname 列(如果它不为 null),如果不是这种情况,则选择 last_name
数据库没有任何结构更改。执行以下脚本,该脚本执行旧数据的最终迁移
-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
-- ALSO WE'RE NOT CHECKING IF WE'RE NOT OVERRIDING EXISTING ENTRIES. WE WOULD HAVE TO COMPARE
-- ENTRY VERSIONS TO ENSURE THAT IF THERE IS ALREADY AN ENTRY WITH A HIGHER VERSION NUMBER
-- WE WILL NOT OVERRIDE IT.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;
-- DROPPING THE NOT NULL CONSTRAINT; OTHERWISE YOU WILL TRY TO INSERT NULL VALUE OF THE LAST_NAME
-- WITH A NOT_NULL CONSTRAINT.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;
我们将数据存储在 last_name 和 surname 列中。此外,我们从 last_name 列读取数据,因为它最新。在部署过程中,一些请求可能已经被尚未升级的实例处理。
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://apache.ac.cn/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String surname;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSurname() {
return this.surname;
}
public void setSurname(String lastname) {
this.surname = lastname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", surname=" + this.surname
+ "]";
}
}
应用程序版本:4.0.0
数据库版本:v4
由于 3.0.0 版本的代码没有使用 last_name 列,因此如果我们回滚到删除数据库中的列后的 3.0.0 版本,则运行时不会出现任何问题。
We will do it in the following way:
01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0
05) Wait for the app (2.0.0) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0
07) Generate a person by calling POST localhost:9992/person to version 2.0.0
08) Kill app (1.0.0)
09) Run 3.0.0
10) Wait for the app (3.0.0) to boot
11) Generate a person by calling POST localhost:9992/person to version 2.0.0
12) Generate a person by calling POST localhost:9993/person to version 3.0.0
13) Kill app (3.0.0)
14) Run 4.0.0
15) Wait for the app (4.0.0) to boot
16) Generate a person by calling POST localhost:9993/person to version 3.0.0
17) Generate a person by calling POST localhost:9994/person to version 4.0.0
Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}
Starting app in version 2.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}
Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}
Killing app 1.0.0
Starting app in version 3.0.0
Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}
Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}
Killing app 2.0.0
Starting app in version 4.0.0
Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}
Generate a person in version 4.0.0
Sending a post to 127.0.0.1:9994/person. This is the response:
{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}
与 v3 相比,我们只是删除了 last_name 列并添加了缺失的约束。
-- REMOVE THE COLUMN
ALTER TABLE PERSON DROP last_name;
-- ADD CONSTRAINTS
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;
没有代码更改。
我们通过几次向后兼容的部署,成功地完成了重命名列的向后不兼容更改。以下是我们执行的操作摘要
部署应用程序版本 1.0.0,数据库模式为 v1(列名 = last_name)
部署应用程序版本 2.0.0,该版本将数据保存到 last_name 和 surname 列。应用程序从 last_name 列读取。数据库版本为 v2,包含 last_name 和 surname 列。surname 列是 last_name 列的副本。(注意:此列不能有非空约束)
部署应用程序版本 3.0.0,该版本仅将数据保存到 surname 列并从 surname 列读取。对于数据库,将执行从 last_name 到 surname 的最终迁移。此外,还移除了 last_name 列的非空约束。数据库现在是 v3 版本
部署应用程序版本 4.0.0——代码没有更改。数据库升级到 v4 版本,该版本首先执行 last_name 到 surname 的最终迁移,然后删除 last_name 列。您可以在此处添加任何缺失的约束
通过遵循这种方法,您可以始终回滚到上一个版本,而不会破坏数据库/应用程序兼容性。
本文使用的所有代码均可在 Github 上找到。以下是一些附加说明。
克隆存储库后,您将看到以下文件夹结构。
├── boot-flyway-v1 - 1.0.0 version of the app with v1 of the schema
├── boot-flyway-v2 - 2.0.0 version of the app with v2 of the schema (backward-compatible - app can be rolled back)
├── boot-flyway-v2-bad - 2.0.0.BAD version of the app with v2bad of the schema (backward-incompatible - app cannot be rolled back)
├── boot-flyway-v3 - 3.0.0 version of the app with v3 of the schema (app can be rolled back)
└── boot-flyway-v4 - 4.0.0 version of the app with v4 of the schema (app can be rolled back)
您可以运行脚本来执行演示向后兼容和不兼容更改应用于数据库的场景。
要检查向后兼容的情况,只需运行
./scripts/scenario_backward_compatible.sh
要检查向后不兼容的情况,只需运行
./scripts/scenario_backward_incompatible.sh
所有示例都是 Spring Boot Sample Flyway 项目的克隆。
您可以查看 [https://:8080/flyway](https://:8080/flyway) 来查看脚本列表。
该示例还启用了 H2 控制台(位于 [https://:8080/h2-console](https://:8080/h2-console)),以便您可以查看数据库状态(默认 jdbc url 为 jdbc:h2:mem:testdb)。