零停機部署和資料庫

零停機部署和資料庫

本文詳細介紹如何解決部署中的資料庫相容性問題。 我們將告訴您,如果您在沒有預先準備的情況下嘗試部署,您的生產應用程式可能會發生什麼情況。 然後,我們將經歷零停機所需的應用程式生命週期階段(約。 車道:進一步 - 零停機時間)。 我們操作的結果將以向後相容的方式應用向後不相容的資料庫變更。

如果您想了解本文中的程式碼範例,可以在以下位置找到它們 GitHub上.

介紹

零停機部署

多麼神秘 零停機部署? 您可以說,這是當您的應用程式以這樣的方式部署時,您可以成功地將應用程式的新版本引入生產,而用戶不會注意到它的不可用。 從使用者和公司的角度來看,這是最好的部署方案,因為它允許在不中斷的情況下引入新功能並修復錯誤。

如何實現這項目標? 有多種方法,以下是其中一種:

  • 部署您的服務的版本 1
  • 執行資料庫遷移
  • 與版本 #2 並行部署服務的版本 #1
  • 一旦您看到版本 2 可以正常運作,請刪除版本 1
  • 準備好!

很容易,不是嗎? 不幸的是,事情並沒有那麼簡單,我們稍後會詳細討論。 現在讓我們來看看另一個相當常見的部署流程—藍綠部署。

你可曾聽說 藍綠部署? Cloud Foundry 讓這一切變得極為簡單。 看看 本文,我們在這裡更詳細地描述這一點。 簡單總結一下,讓我們提醒您如何進行藍綠部署:

  • 確保您的生產代碼的兩個副本(“藍色”和“綠色”)有效;
  • 將所有流量引導至藍色環境,即以便生產 URL 指向那裡;
  • 在綠色環境中部署和測試所有應用程式變更;
  • 將 url 從藍色環境切換為綠色環境

藍綠部署是一種允許您輕鬆引入新功能而不必擔心生產中斷的方法。 這是因為,即使出現問題,您也可以透過簡單的「輕按開關」輕鬆回滾到先前的環境。

讀完以上內容後,您可能會問這樣的問題:零停機與藍綠部署有什麼關係?

嗯,它們有很多共同點,因為維護同一環境的兩個副本需要付出雙倍的努力來維護它們。 這就是為什麼有些團隊聲稱 馬丁·福勒(Martin Fowler),遵循此方法的變體:

另一個選擇是使用相同的資料庫,為 Web 層和網域層建立藍綠交換器。 在這種方法中,資料庫通常會成為問題,特別是當您需要更改其架構以支援新版本的軟體時。

我們來到了本文的主要問題。 數據庫。 我們再看一下這句話。

執行資料庫遷移。

現在您必須問自己一個問題 - 如果資料庫更改不向後相容怎麼辦? 我的應用程式的第一個版本不會崩潰嗎? 事實上,這正是將會發生的事情......

因此,儘管零停機/藍綠部署具有巨大的好處,但該公司仍傾向於遵循以下更安全的流程來部署其應用程式:

  • 準備一個包含新版本應用程式的套件
  • 關閉正在運行的應用程式
  • 運行腳本來遷移資料庫
  • 部署並啟動應用程式的新版本

在本文中,我們將詳細介紹如何使用資料庫和程式碼來利用零停機部署。

數據庫問題

如果您有一個不在資料庫中儲存任何資料的無狀態應用程序,您可以立即獲得零停機部署。 不幸的是,大多數軟體都需要在某個地方儲存資料。 這就是為什麼在對電路進行任何更改之前應該三思而後行。 在詳細了解如何更改架構以便實現無停機部署之前,我們首先關注版本控制架構。

版本控制方案

在本文中我們將使用 飛道 作為版本控制工具(約。 翻譯:我們正在談論資料庫遷移)。 當然,我們還將編寫一個具有內建 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引導文檔.

將原始碼控制工具與 Spring Boot 結合使用,您可以獲得兩大好處:

  • 將資料庫變更與程式碼變更分開
  • 資料庫遷移隨著應用程式的推出而發生,即您的部署流程已簡化

排除資料庫問題

在本文的下一部分中,我們將重點放在兩種資料庫變更方法上。

  • 向後不相容
  • 向後相容性

第一個將被視為警告,在沒有初步準備的情況下不應執行零停機部署...第二個提供瞭如何在不停機的情況下執行部署並同時保持向後相容性的解決方案。

我們將要開發的專案將是一個簡單的 Spring Boot Flyway 應用程序,它具有 Person с first_name и last_name 在資料庫中(約。 翻譯: Person 是一個表格和 first_name и last_name - 這些是其中的字段)。 我們想重命名 last_name в surname.

假設

在我們深入了解細節之前,我們需要對我們的應用程式做出一些假設。 我們想要實現的主要結果將是一個相當簡單的過程。

那個筆記。 商務專業提示。 簡化流程可以為您節省大量支援費用(為您的公司工作的人員越多,您可以節省的錢就越多)!

無需回滾資料庫

這簡化了部署過程(有些資料庫回滾幾乎是不可能的,例如刪除回滾)。 我們更願意僅回滾應用程式。 這樣,即使您有不同的資料庫(例如 SQL 和 NoSQL),您的部署管道也會看起來相同。

必須始終能夠將應用程式回滾到一個版本(不能再回滾)

僅應在必要時進行回滾。 如果目前版本中存在不易修復的錯誤,我們應該能夠恢復到最新的工作版本。 我們假設這個最新的工作版本是前一個版本。 維護多次部署的程式碼和資料庫相容性將是極其困難和昂貴的。

筆記。 為了提高可讀性,在本文中我們將更改應用程式的主要版本。

第 1 步:初始狀態

應用程式版本: 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');

程式碼變更

該應用程式將人員資料儲存在 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
 *
 *      http://www.apache.org/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

評論

目前的變更不允許我們同時執行兩個實例(舊的和新的)。 因此,零停機部署將很難實現(如果考慮到假設,實際上是不可能的)。

A/B 測試

目前的情況是我們有一個應用程式版本 1.0.0, 部署在生產和資料庫中 v1。 我們需要部署應用程式的第二個實例,版本 2.0.0.BAD,並將資料庫更新為 v2bad.

Шаги:

  1. 部署版本應用程式的新實例 2.0.0.BAD它將資料庫更新為 v2bad
  2. 在資料庫中 v2bad 柱子 last_name 不再存在 - 它已更改為 surname
  3. 資料庫和應用程式更新成功,部分實例正在運行 1.0.0, 其他人 - 在 2.0.0.BAD。 一切都連接到資料庫 v2bad
  4. 該版本的所有實例 1.0.0 將開始拋出錯誤,因為他們將嘗試將資料插入到列中 last_name誰不再存在
  5. 該版本的所有實例 2.0.0.BAD 可以正常工作

正如您所看到的,如果我們對資料庫和應用程式進行向後不相容的更改,A/B 測試是不可能的。

應用回滾

我們假設在嘗試進行 A/B 部署之後(約。 per.:作者在這裡可能指的是 A/B 測試)我們決定需要將應用程式回滾到該版本 1.0.0. 假設我們不想回滾資料庫。

Шаги:

  1. 我們停止版本應用程式實例 2.0.0.BAD
  2. 資料庫仍然是 v2bad
  3. 自版本以來 1.0.0 不明白那是什麼 surname,我們會看到錯誤
  4. 地獄已經崩潰,我們再也回不去了

正如您所看到的,如果我們對資料庫和應用程式進行向後不相容的更改,我們將無法回滾到先前的版本。

腳本執行日誌

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;

程式碼變更

我們更改了欄位名稱 lastNamesurname.

以向後相容的方式重新命名列

這是我們可能遇到的最常見的情況。 我們需要進行向後不相容的更改。 我們已經證明,對於零停機部署,我們不應該簡單地應用資料庫遷移而不需要額外的步驟。 在本文的這一部分中,我們將執行 3 次應用程式部署以及資料庫遷移,以實現所需的結果,同時保持向後相容性。

那個筆記。 回想一下,我們有一個版本資料庫 v1。 它包含列 first_name и last_name。 我們必須改變 last_namesurname。 我們還有應用程式版本 1.0.0, 尚未使用 surname.

第 2 步:新增姓氏

應用程式版本: 2.0.0
資料庫版本: v2

評論

透過新增列並複製其內容,我們創建了向後相容的資料庫變更。 同時,如果我們回滾 JAR 或運行舊的 JAR,它在執行過程中不會中斷。

我們正在推出新版本

Шаги:

  1. 執行資料庫遷移以建立新列 surname。 現在你的資料庫版本 v2
  2. 從複製數據 last_name в surname. 請注意如果你有很多這樣的數據,你應該考慮批量遷移!
  3. 在使用它們的地方編寫程式碼 兩個都 и 舊的 柱子。 現在您的應用程式版本 2.0.0
  4. 從列中讀取值 surname,如果不是 null,或從 last_name如果 surname 未指定。 您可以刪除 getLastName() 從程式碼中,因為它會輸出 null 當回滾你的應用程式時 3.0.02.0.0.

如果您使用的是Spring Boot Flyway,這兩個步驟將在版本啟動時執行 2.0.0 應用程式. 如果手動執行資料庫版本控制工具,則必須執行兩個不同的操作(首先手動更新資料庫版本,然後部署新應用程式)。

這很重要。 請記住,新建立的列 不應該 быть 不是NULL。 如果您進行回滾,舊應用程式不知道新列,並且不會在安裝期間安裝它 Insert. 但是如果你加入這個約束,你的資料庫將會是 v2,這將需要設定新列的值。 這將導致違反限制。

這很重要。 你應該刪除該方法 getLastName(),因為在版本中 3.0.0 程式碼中沒有列的概念 last_name。 這意味著將在那裡設定 null。 您可以保留該方法並新增檢查 null,但更好的解決方案是確保在邏輯中 getSurname() 您選擇了正確的非零值。

A/B 測試

目前的情況是我們有一個應用程式版本 1.0.0,部署在生產環境中,資料庫位於 v1。 我們需要部署版本應用程式的第二個實例 2.0.0這會將資料庫更新為 v2.

Шаги:

  1. 部署版本應用程式的新實例 2.0.0它將資料庫更新為 v2
  2. 同時,版本實例處理了一些請求 1.0.0
  3. 更新成功並且您有多個正在運行的版本應用程式實例 1.0.0 和其他版本 2.0.0. 每個人都與資料庫通信 v2
  4. 版本 1.0.0 不使用資料庫中的姓氏列,而是使用版本 2.0.0 用途。 它們互不干擾,也不應該出現錯誤。
  5. 版本 2.0.0 將資料儲存在新舊列中,確保向後相容性

這很重要。 如果您有任何根據舊/新列中的值對項目進行計數的查詢,您應該記住您現在有重複的值(很可能它們仍在遷移)。 例如,如果您想要計算姓氏(無論該列稱為什麼)以字母開頭的使用者數量 A,然後直到資料遷移完成(oldnew 列),如果您查詢新列,則可能會出現不一致的資料。

應用回滾

現在我們有應用程式版本 2.0.0 和資料庫在 v2.

Шаги:

  1. 將您的應用程式回滾到版本 1.0.0.
  2. 版本 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
 *
 *      http://www.apache.org/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:從程式碼中刪除 last_name

應用程式版本: 3.0.0

資料庫版本:v3

評論

筆記per.:顯然,在原始文章中,作者錯誤地複製了步驟 2 中該區塊的文字。在此步驟中,應在應用程式程式碼中進行更改,旨在刪除使用該列的功能 last_name.

透過新增列並複製其內容,我們創建了向後相容的資料庫變更。 另外,如果我們回滾 JAR 或運行舊的 JAR,它在執行過程中不會中斷。

應用回滾

目前我們有應用程式版本 3.0.0 和資料庫 v3。 版本 3.0.0 不保存資料到 last_name。 這意味著在 surname 儲存最新資訊。

Шаги:

  1. 將您的應用程式回滾到版本 2.0.0.
  2. 版本 2.0.0 用途和 last_name и surname.
  3. 版本 2.0.0 將採取 surname,如果不為零,否則 -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;

程式碼變更

筆記per.:該區塊的描述也是作者從步驟 2 中錯誤複製的。按照文章的邏輯,這一步驟程式碼的變更應該旨在從中刪除與該列一起使用的元素 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
 *
 *      http://www.apache.org/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:從資料庫中刪除姓氏

應用程式版本: 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. 應用程式版本部署 1.0.0 с v1 資料庫架構(列名稱 = last_name)
  2. 應用程式版本部署 2.0.0, 它儲存資料在 last_name и surname。 應用程式讀取自 last_name。 資料庫處於版本狀態 v2包含像這樣的列 last_namesurname. surname 是 l 的副本ast_name。 (注意:該列不能有非空約束)
  3. 應用程式版本部署 3.0.0,它只儲存數據 surname 並從姓氏中讀取。 至於資料庫,正在進行最後一次遷移 last_name в surname。 也是一個限制 不是NULL 已經從...刪除 last_name。 資料庫現在已經是版本了 v3
  4. 應用程式版本部署 4.0.0 - 未對程式碼進行任何更改。 資料庫部署 v4,這會刪除 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 範例 Flyway

所有範例均取自 Spring Boot Sample Flyway.

你可以看一下 http://localhost:8080/flyway,有一個腳本清單。

此範例還包括 H2 控制台(位於 http://localhost:8080/h2-console)這樣你就可以查看資料庫狀態(預設jdbc URL是 jdbc:h2:mem:testdb).

另外

也請閱讀我們部落格上的其他文章:

來源: www.habr.com

添加評論