新聞中心
[[205691]]

我們 MySQL 數(shù)據(jù)庫基礎(chǔ)架構(gòu)是 Github 關(guān)鍵組件。 MySQL 提供 Github.com、 GitHub 的 API 和驗證等等的服務(wù)。每一次的 git 請求都以某種方式觸及 MySQL。我們的任務(wù)是保持?jǐn)?shù)據(jù)的可用性,并保持其完整性。即使我們 MySQL 集群是按流量分配的,但是我們還是需要執(zhí)行深度清理、即時更新、在線模式schema遷移、集群拓?fù)渲貥?gòu)、連接池化pooling和負(fù)載平衡等任務(wù)。 我們建有基礎(chǔ)架構(gòu)來自動化測試這些操作,在這篇文章中,我們將分享幾個例子,來說明我們是如何通過持續(xù)測試打造我們的基礎(chǔ)架構(gòu)的。這是讓我們一夢到天亮的根本保障。
備份
沒有比備份數(shù)據(jù)更重要的了,如果您沒有備份數(shù)據(jù)庫,在它出事前這可能并不是什么問題。Percona 的 Xtrabackup 是我們一直用來完整備份 MySQL 數(shù)據(jù)庫的工具。如果有專門需要備份的數(shù)據(jù),我們就會備份到另一個專門備份數(shù)據(jù)的服務(wù)器上。
除了完整的二進制備份外,我們每天還會多次運行邏輯備份。這些備份數(shù)據(jù)可以讓我們的工程師獲取到***的數(shù)據(jù)副本。有時候,他們希望從表中獲取一整套數(shù)據(jù),以便他們可以在一個生產(chǎn)級規(guī)模的表上測試索引的修改,或查看特定時間以來的數(shù)據(jù)。Hubot 可以讓我們恢復(fù)備份的表,并且當(dāng)表準(zhǔn)備好使用時會通知我們。
tomkrouper
- .mysql backup-list locations
Hubot
- +-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
- | Backup ID | Table Name | Donor Host | Backup Start | Backup End | File Name |
- +-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
- | 1699494 | locations | db-mysql-0903 | 2017-07-01 22:09:17 | 2017-07-01 22:09:17 | backup-mycluster-locations-1498593122.sql.gz |
- | 1699133 | locations | db-mysql-0903 | 2017-07-01 16:11:37 | 2017-07-01 16:11:39 | backup-mycluster-locations-1498571521.sql.gz |
- | 1698772 | locations | db-mysql-0903 | 2017-07-01 10:09:21 | 2017-07-01 10:09:22 | backup-mycluster-locations-1498549921.sql.gz |
- | 1698411 | locations | db-mysql-0903 | 2017-07-01 04:12:32 | 2017-07-01 04:12:32 | backup-mycluster-locations-1498528321.sql.gz |
- | 1698050 | locations | db-mysql-0903 | 2017-06-30 22:18:23 | 2017-06-30 22:18:23 | backup-mycluster-locations-1498506721.sql.gz |
- | ...
- | 1262253 | locations | db-mysql-0088 | 2016-08-01 01:58:51 | 2016-08-01 01:58:54 | backup-mycluster-locations-1470034801.sql.gz |
- | 1064984 | locations | db-mysql-0088 | 2016-04-04 13:07:40 | 2016-04-04 13:07:43 | backup-mycluster-locations-1459494001.sql.gz |
- +-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
tomkrouper
- .mysql restore 1699133
Hubot
- A restore job has been created for the backup job 1699133. You will be notified in #database-ops when the restore is complete.
Hubot
- @tomkrouper: the locations table has been restored as locations_2017_07_01_16_11 in the restores database on db-mysql-0482
數(shù)據(jù)被加載到非生產(chǎn)環(huán)境的數(shù)據(jù)庫,該數(shù)據(jù)庫可供請求該次恢復(fù)的工程師訪問。
我們保留數(shù)據(jù)的“備份”的***一個方法是使用延遲副本delayed replica。這與其說是備份,不如說是保護。對于每個生產(chǎn)集群,我們有一個延遲 4 個小時復(fù)制的主機。如果運行了一個不該運行的請求,我們可以在 chatops 中運行 mysql panic 。這將導(dǎo)致我們所有的延遲副本立即停止復(fù)制。這也將給值班 DBA 發(fā)送消息。從而我們可以使用延遲副本來驗證是否有問題,并快速前進到二進制日志的錯誤發(fā)生之前的位置。然后,我們可以將此數(shù)據(jù)恢復(fù)到主服務(wù)器,從而恢復(fù)數(shù)據(jù)到該時間點。
備份固然好,但如果發(fā)生了一些未知或未捕獲的錯誤破壞它們,它們就沒有價值了。讓腳本恢復(fù)備份的好處是它允許我們通過 cron 自動執(zhí)行備份驗證。我們?yōu)槊總€集群設(shè)置了一個專用的主機,用于運行***備份的恢復(fù)。這樣可以確保備份運行正常,并且我們能夠從備份中檢索數(shù)據(jù)。
根據(jù)數(shù)據(jù)集大小,我們每天運行幾次恢復(fù)?;謴?fù)的服務(wù)器被加入到復(fù)制工作流,并通過復(fù)制保持?jǐn)?shù)據(jù)更新。這測試不僅讓我們得到了可恢復(fù)的備份,而且也讓我們得以正確地確定備份的時間點,并且可以從該時間點進一步應(yīng)用更改。如果恢復(fù)過程中出現(xiàn)問題,我們會收到通知。
我們還追蹤恢復(fù)所需的時間,所以我們知道在緊急情況下建立新的副本或還原需要多長時間。
以下是由 Hubot 在我們的機器人聊天室中輸出的自動恢復(fù)過程。
Hubot
- gh-mysql-backup-restore: db-mysql-0752: restore_log.id = 4447
- gh-mysql-backup-restore: db-mysql-0752: Determining backup to restore for cluster 'prodcluster'.
- gh-mysql-backup-restore: db-mysql-0752: Enabling maintenance mode
- gh-mysql-backup-restore: db-mysql-0752: Setting orchestrator downtime
- gh-mysql-backup-restore: db-mysql-0752: Disabling Puppet
- gh-mysql-backup-restore: db-mysql-0752: Stopping MySQL
- gh-mysql-backup-restore: db-mysql-0752: Removing MySQL files
- gh-mysql-backup-restore: db-mysql-0752: Running gh-xtrabackup-restore
- gh-mysql-backup-restore: db-mysql-0752: Restore file: xtrabackup-notify-2017-07-02_0000.xbstream
- gh-mysql-backup-restore: db-mysql-0752: Running gh-xtrabackup-prepare
- gh-mysql-backup-restore: db-mysql-0752: Starting MySQL
- gh-mysql-backup-restore: db-mysql-0752: Update file ownership
- gh-mysql-backup-restore: db-mysql-0752: Upgrade MySQL
- gh-mysql-backup-restore: db-mysql-0752: Stopping MySQL
- gh-mysql-backup-restore: db-mysql-0752: Starting MySQL
- gh-mysql-backup-restore: db-mysql-0752: Backup Host: db-mysql-0034
- gh-mysql-backup-restore: db-mysql-0752: Setting up replication
- gh-mysql-backup-restore: db-mysql-0752: Starting replication
- gh-mysql-backup-restore: db-mysql-0752: Replication catch-up
- gh-mysql-backup-restore: db-mysql-0752: Restore complete (replication running)
- gh-mysql-backup-restore: db-mysql-0752: Enabling Puppet
- gh-mysql-backup-restore: db-mysql-0752: Disabling maintenance mode
- gh-mysql-backup-restore: db-mysql-0752: Setting orchestrator downtime
- gh-mysql-backup-restore: db-mysql-0752: Restore process complete.
還有一件我們可以使用備份做的事情是向一組現(xiàn)有的 MySQL 服務(wù)器添加一個新的副本。我們初始化一個新的服務(wù)器,一旦我們得知它準(zhǔn)備就緒,我們就可以開始恢復(fù)該特定集群的***備份。我們有一個腳本,可以運行所有需要手動執(zhí)行的恢復(fù)命令。我們的自動恢復(fù)系統(tǒng)基本上使用的是相同的腳本。這簡化了系統(tǒng)構(gòu)建過程,并允許我們在一個啟動運行的主機上使用幾個對話命令(chatops)來替代一堆手動執(zhí)行命令。下面顯示的是在該對話(chatops)中手動執(zhí)行的恢復(fù):
jessbreckenridge
- .mysql backup-restore -H db-mysql-0007 -o -r magic_word=daily_rotating_word
Hubot
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Determining backup to restore for cluster 'mycluster'.
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: restore_log.id = 4449
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Enabling maintenance mode
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Setting orchestrator downtime
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Disabling Puppet
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Stopping MySQL
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Removing MySQL files
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Running gh-xtrabackup-restore
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Restore file: xtrabackup-mycluster-2017-07-02_0015.xbstream
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Running gh-xtrabackup-prepare
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Update file ownership
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting MySQL
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Upgrade MySQL
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Stopping MySQL
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting MySQL
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Setting up replication
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting replication
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Backup Host: db-mysql-0201
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Replication catch-up
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Replication behind by 4589 seconds, waiting 1800 seconds before next check.
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Restore complete (replication running)
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Enabling puppet
- @jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Disabling maintenance mode
故障轉(zhuǎn)移
我們使用協(xié)調(diào)器 來為主服務(wù)器master和中間服務(wù)器intermediate master執(zhí)行自動化故障切換。我們期望協(xié)調(diào)器orchestrator能夠正確檢測主服務(wù)器故障,指定一個副本進行晉升,在所指定的副本下修復(fù)拓?fù)?,完成晉升。我們預(yù)期 VIP(虛擬 IP)、連接池可以相應(yīng)地進行變化、客戶端進行重連、puppet 在晉升后的主服務(wù)器上運行基本組件等等。故障轉(zhuǎn)移是一項復(fù)雜的任務(wù),涉及到我們基礎(chǔ)架構(gòu)的許多方面。
為了建立對我們的故障轉(zhuǎn)移的信賴,我們建立了一個類生產(chǎn)環(huán)境的測試集群,并且我們不斷地崩潰它來觀察故障轉(zhuǎn)移情況。
這個類生產(chǎn)環(huán)境的測試集群是一套復(fù)制環(huán)境,與我們的生產(chǎn)集群的各個方面都相同:硬件類型、操作系統(tǒng)、MySQL 版本、網(wǎng)絡(luò)環(huán)境、VIP、puppet 配置、haproxy 設(shè)置 等。與生產(chǎn)集群唯一不同的是它不發(fā)送/接收生產(chǎn)流量。
我們在測試集群上模擬寫入負(fù)載,同時避免復(fù)制滯后。寫入負(fù)載不會太大,但是有一些有意地寫入相同數(shù)據(jù)集的競爭請求。這在正常情況下并不是很有用,但是事實證明這在故障轉(zhuǎn)移中是有用的,我們將會稍后簡要描述它。
我們的測試集群有來自三個數(shù)據(jù)中心的典型的服務(wù)器。我們希望故障轉(zhuǎn)移能夠從同一個數(shù)據(jù)中心內(nèi)晉升替代副本。我們希望在這樣的限制下盡可能多地恢復(fù)副本。我們要求盡可能地實現(xiàn)這兩者。協(xié)調(diào)器對拓?fù)浣Y(jié)構(gòu)沒有先驗假定prior assumption;它必須依據(jù)崩潰時的狀態(tài)作出反應(yīng)。
然而,我們有興趣創(chuàng)建各種復(fù)雜而多變的故障恢復(fù)場景。我們的故障轉(zhuǎn)移測試腳本為故障轉(zhuǎn)移提供了基礎(chǔ):
- 它能夠識別現(xiàn)有的主服務(wù)器
- 它能夠重構(gòu)拓?fù)浣Y(jié)構(gòu),來代表主服務(wù)器下的所有的三個數(shù)據(jù)中心。不同的數(shù)據(jù)中心具有不同的網(wǎng)絡(luò)延遲,并且預(yù)期會在不同的時間對主機崩潰做出反應(yīng)。
- 能夠選擇崩潰方式??梢赃x擇干掉主服務(wù)器(kill -9)或網(wǎng)絡(luò)隔離(比較好的方式: iptables -j REJECT 或無響應(yīng)的方式: iptables -j DROP)方式。
腳本通過選擇的方法使主機崩潰,并等待協(xié)調(diào)器可靠地檢測到崩潰然后執(zhí)行故障轉(zhuǎn)移。雖然我們期望檢測和晉升在 30 秒鐘內(nèi)完成,但腳本會稍微放寬這一期望,并在查找故障轉(zhuǎn)移結(jié)果之前休眠一段指定的時間。然后它將檢查:
- 一個新的(不同的)主服務(wù)器是否到位
- 集群中有足夠的副本
- 主服務(wù)器是可寫的
- 對主服務(wù)器的寫入在副本上可見
- 內(nèi)部服務(wù)發(fā)現(xiàn)項已更新(如預(yù)期般識別到新的主服務(wù)器;移除舊的主服務(wù)器)
- 其他內(nèi)部檢查
這些測試可以證實故障轉(zhuǎn)移是成功的,不僅是 MySQL 級別的,而是在更大的基礎(chǔ)設(shè)施范圍內(nèi)成功的。VIP 被賦予;特定的服務(wù)已經(jīng)啟動;信息到達了應(yīng)該去的地方。
該腳本進一步繼續(xù)恢復(fù)那個失敗的服務(wù)器:
- 從備份恢復(fù)它,從而隱含地測試了我們的備份/恢復(fù)過程
- 驗證服務(wù)器配置是否符合預(yù)期(該服務(wù)器不再認(rèn)為其是主服務(wù)器)
- 將其加入到復(fù)制集群,期望找到在主服務(wù)器上寫入的數(shù)據(jù)
看一下以下可視化的計劃的故障轉(zhuǎn)移測試:從運行良好的群集,到在某些副本上發(fā)現(xiàn)問題,診斷主服務(wù)器(7136)是否死機,選擇一個服務(wù)器(a79d)來晉升,重構(gòu)該服務(wù)器下的拓?fù)洌瑫x升它(故障切換成功),恢復(fù)失敗的(原)主服務(wù)器并將其放回群集。
automated master failover
測試失敗怎么樣?
我們的測試腳本使用了一種“停止世界”的方法。任何故障切換組件中的單個故障都將導(dǎo)致整個測試失敗,因此在有人解決該問題之前,無法進行任何進一步的自動化測試。我們會得到警報,并檢查狀態(tài)和日志進行處理。
腳本將各種情況下失敗,如不可接受的檢測或故障轉(zhuǎn)移時間;備份/還原出現(xiàn)問題;失去太多服務(wù)器;在故障切換后的意外配置等等。
我們需要確保協(xié)調(diào)器正確地連接服務(wù)器。這是競爭性寫入負(fù)載有用的地方:如果設(shè)置不正確,復(fù)制很容易中斷。我們會得到 DUPLICATE KEY 或其他錯誤提示出錯。
這是特別重要的,因此我們改進協(xié)調(diào)器并引入新的行為,以允許我們在安全的環(huán)境中測試這些變化。
出現(xiàn):混亂測試
上面所示的測試程序?qū)⒉东@(并已經(jīng)捕獲)我們基礎(chǔ)設(shè)施許多部分的問題。這些夠了嗎?
在生產(chǎn)環(huán)境中總是有其他的東西。有些特定測試方法不適用于我們的生產(chǎn)集群。它們不具有相同的流量和流量方式,也不具有完全相同的服務(wù)器集。故障類型可能有所不同。
我們正在為我們的生產(chǎn)集群設(shè)計混亂測試。 混亂測試將會在我們的生產(chǎn)中,但是按照預(yù)期的時間表和充分控制的方式來逐個破壞我們的部分生產(chǎn)環(huán)境。 混亂測試在恢復(fù)機制中引入更高層次的信賴,并影響(因此測試)我們的基礎(chǔ)設(shè)施和應(yīng)用程序的更大部分。
這是微妙的工作:當(dāng)我們承認(rèn)需要混亂測試時,我們也希望可以避免對我們的服務(wù)造成不必要的影響。不同的測試將在風(fēng)險級別和影響方面有所不同,我們將努力確保我們的服務(wù)的可用性。
模式遷移
我們使用 gh-ost來運行實時模式遷移schema migration。gh-ost 是穩(wěn)定的,但也處于活躍開發(fā)中,重大新功能正在不斷開發(fā)和計劃中。
gh-ost 通過將數(shù)據(jù)復(fù)制到 ghost 表來遷移,將由二進制日志攔截的進一步更改應(yīng)用到 ghost 表中,就如其正在寫入原始表。然后它將 ghost 表交換代替原始表。遷移完成時,GitHub 繼續(xù)使用由 gh-ost 生成和填充的表。
在這個時候,幾乎所有的 GitHub 的 MySQL 數(shù)據(jù)都被 gh-ost 重新創(chuàng)建,其中大部分重新創(chuàng)建多次。我們必須高度信賴 gh-ost,讓它一遍遍地操弄我們的數(shù)據(jù),即使它還處于活躍開發(fā)中。下面是我們?nèi)绾潍@得這種信賴的。
gh-ost 提供生產(chǎn)環(huán)境測試能力。它支持在副本上運行遷移,其方式與在主服務(wù)器上運行的方式大致相同: gh-ost 將連接到副本,并將其視為主服務(wù)器。它將采用與實際主機遷移相同的方式解析其二進制日志。但是,它將復(fù)制行并將二進制日志事件應(yīng)用于副本,并避免對主服務(wù)器進行寫入。
我們在生產(chǎn)環(huán)境中給 gh-ost 提供專用的副本。這些副本并不為生產(chǎn)環(huán)境提供服務(wù)。每個這樣的副本將檢索生產(chǎn)表的當(dāng)前列表,并以隨機順序?qū)ζ溥M行迭代。一個接一個地選擇一個表并在該表上執(zhí)行復(fù)制遷移。遷移實際上并不修改表結(jié)構(gòu),而是運行一個微不足道的 ENGINE=InnoDB 更改。該測試運行遷移時,如果表正在生產(chǎn)環(huán)境中使用,會復(fù)制實際的生產(chǎn)數(shù)據(jù),并接受二進制日志之外的真實的生產(chǎn)環(huán)境流量。
這些遷移可以被審計。以下是我們?nèi)绾螐膶υ捴?chatops)檢查運行中的測試狀態(tài):
ggunson
- .migration test-status
Hubot
- # Migrating `prod`.`pull_requests`; Ghost table is `prod`.`_pull_requests_gho`
- # Migrating ghost-db-mysql-0007:3306; inspecting ghost-db-mysql-0007:3306; executing on ghost-db-mysql-0007
- # Migration started at Mon Jan 30 02:13:39 -0800 2017
- # chunk-size: 2500; max-lag-millis: 1500ms; max-load: Threads_running=30; critical-load: Threads_running=1000; nice-ratio: 0.000000
- # throttle-additional-flag-file: /tmp/gh-ost.throttle
- # panic-flag-file: /tmp/ghost-test-panic.flag
- # Serving on unix socket: /tmp/gh-ost.test.sock
- Copy: 57992500/86684838 66.9%; Applied: 57708; Backlog: 1/100; Time: 3h28m38s(total), 3h28m36s(copy); streamer: mysql-bin.000576:142993938; State: migrating; ETA: 1h43m12s
當(dāng)測試遷移完成表數(shù)據(jù)的復(fù)制時,它將停止復(fù)制并執(zhí)行切換,使用 ghost 表替換原始表,然后交換回來。我們對實際替換數(shù)據(jù)并不感興趣。相反,我們將留下原始的表和 ghost 表,它們應(yīng)該是相同的。我們通過校驗兩個表的整個表數(shù)據(jù)來驗證。
測試能以下列方式完成:
- 成功 :一切順利,校驗和相同。我們期待看到這一結(jié)果。
- 失敗 :執(zhí)行問題。這可能偶爾發(fā)生,因為遷移進程被殺死、復(fù)制問題等,并且通常與 gh-ost 自身無關(guān)。
- 校驗失敗 :表數(shù)據(jù)不一致。對于被測試的分支,這個需要修復(fù)。對于正在進行的 master 分支測試,這意味著立即阻止生產(chǎn)遷移。我們不會遇到后者。
測試結(jié)果經(jīng)過審核,發(fā)送到機器人聊天室,作為事件發(fā)送到我們的度量系統(tǒng)。下圖中的每條垂直線代表成功的遷移測試:
automated master failover
這些測試不斷運行。如果發(fā)生故障,我們會收到通知。當(dāng)然,我們可以隨時訪問機器人聊天室(chatops),了解發(fā)生了什么。
測試新版本
我們不斷改進 gh-ost。我們的開發(fā)流程基于 git 分支,然后我們通過拉取請求(PR)來提供合并。
提交的 gh-ost 拉取請求(PR)通過持續(xù)集成(CI)進行基本的編譯和單元測試。一旦通過,該 PR 在技術(shù)上就有資格合并,但更好的是它有資格通過 Heaven 進行部署。作為我們基礎(chǔ)架構(gòu)中的敏感組件,在其進入 master 分支前,我們會小心部署分支進行密集測試。
shlomi-noach
- .deploy gh-ost/fix-reappearing-throttled-reasons to prod/ghost-db-mysql-0007
Hubot
- @shlomi-noach is deploying gh-ost/fix-reappearing-throttled-reasons (baee4f6) to production (ghost-db-mysql-0007).
- @shlomi-noach's production deployment of gh-ost/fix-reappearing-throttled-reasons (baee4f6) is done! (2s)
- @shlomi-noach, make sure you watch for exceptions in haystack
jonahberquist
- .deploy gh-ost/interactive-command-question to prod/ghost-db-mysql-0012
Hubot
- @jonahberquist is deploying gh-ost/interactive-command-question (be1ab17) to production (ghost-db-mysql-0012).
- @jonahberquist's production deployment of gh-ost/interactive-command-question (be1ab17) is done! (2s)
- @jonahberquist, make sure you watch for exceptions in haystack
shlomi-noach
- .wcid gh-ost
Hubot
- shlomi-noach testing fix-reappearing-throttled-reasons 41 seconds ago: ghost-db-mysql-0007
- jonahberquist testing interactive-command-question 7 seconds ago: ghost-db-mysql-0012
- Nobody is in the queue.
一些 PR 很小,不影響數(shù)據(jù)本身。對狀態(tài)消息,交互式命令等的更改對 gh-ost 應(yīng)用程序的影響較小。而其他的 PR 對遷移邏輯和操作會造成重大變化,我們將嚴(yán)格測試這些,通過我們的生產(chǎn)表車隊運行這些,直到其滿足了這些改變不會造成數(shù)據(jù)損壞威脅的程度。
總結(jié)
在整個測試過程中,我們建立對我們的系統(tǒng)的信賴。通過自動化這些測試,在生產(chǎn)環(huán)境中,我們得到了一切都按預(yù)期工作的反復(fù)確認(rèn)。隨著我們繼續(xù)發(fā)展我們的基礎(chǔ)設(shè)施,我們還通過調(diào)整測試來覆蓋***的變化。
產(chǎn)品總會有令你意想不到的未被測試覆蓋的場景。我們對生產(chǎn)環(huán)境的測試越多,我們對應(yīng)用程序的期望越多,基礎(chǔ)設(shè)施的能力就越強。
當(dāng)前名稱:GitHub的MySQL基礎(chǔ)架構(gòu)自動化測試
分享網(wǎng)址:http://www.5511xx.com/article/dpcpdes.html


咨詢
建站咨詢
