歡迎光臨
每天分享高質量文章

SQL 不是迴避 DevOps 的理由

 

有朋友最近告訴筆者,“我們做不了 DevOps,我們使用了關係資料庫。“筆者聽罷,差點從椅子上摔倒。這個觀點在很多層面都是錯誤的。
“你不瞭解我的處境!”他拒絕道。“DevOps 意味著我們需要更高頻率的部署我們軟體的發佈版本!現在我們不能控制部署,我們一年只有很少的時間可以乾這個。”
筆者追問朋友當前的部署流程。
“每隔幾個月我們會有新版發佈”,他解釋道。“將其部署到生產線需要很多工作。由於我們使用 SQL,部署過程會是這樣:第一,我們踢掉所有用戶,關閉應用服務。下一步,DBA 修改資料庫的 schema。一旦他們的工作完成,新版軟體就安裝完畢,可以使用了。整個過程花費好幾個小時,所以我們一般安排在周末進行……(這讓人討厭)。如果升級失敗,我們不得不使用備份的磁帶,將一切恢復到初始狀態,然後再試一次。”
他總結道,“僅是規劃一次升級部署都需要好幾周的協商。我們通常都很難達成共識,這也是為什麼我們需要在周末來處理升級。每隔幾個月做一次都很痛苦,要面對來自方方面面的壓力!我聽說一些公司每天能做好幾次軟體發佈。如果我們那樣做,我們的應用系統就會因為升級而一直處於下線狀態了!”
這裡有很多沒有說清楚的地方。讓我先澄清一些誤解。之後再討論採用一些技術手段如何讓部署更容易。
首先,DevOps 不是一項技術,它是一套方法論。 關於 DevOps 最精確的定義是,應用敏捷、精益的方法到從原始碼到部署的全過程。它是為了更快速的交付價值,或者說是用短的時間讓一個需求特性從想法轉變為生產系統的功能。更高頻率的發佈意味著減少已經就緒的特性等待上線的時間。
DevOps 不需要禁止任何特殊的資料庫技術。反過來,任何技術也不是採用和不採用 DevOps 的理由,就像使用特殊的語言不是阻礙將敏捷應有到專案中的理由。SQL 是常見的理由,但也是經不起推敲的。
很理解 DevOps 在一些人頭腦里是如何與少 SQL 資料庫關聯在一起。在2000年到2010年左右,投資和實踐 DevOps 的公司大部分是大型網站,它們的共識是推行 NoSQL 資料庫(鍵值儲存)。然而兩者的聯繫造成了因果的混淆。這些公司還向員工免費提供豐盛的午餐,但我們都同意這不是 DevOps 的前置條件。
第二,我不確定一些人是否可以做 DevOps。 你可以使用 DevOps 的技術、方法或其他。也就是說,人們已經很頻繁的使用這個詞,以致於我想我已經無法去有效討論這個問題。
朋友們有一個共識。“看”,他困惑道,“這些部署是危險的。坦白講,每次我們實施的時候都在拿公司的資料,甚至我的工作在冒險。每隔幾個月做一次已經壓力很大了,還需要更頻繁的做嗎?不!先生,那是不負責任!”
在之前的專欄中提到(小批量原則[1]),當一件事情是高風險的時候,我們會傾向於更少的去嘗試做它。但與常識相悖的是,這種做法恰恰會增加風險。下次當你做危險的事情時,你應該更多的實踐,針對周圍環境而累積的變化會越來越大,越容易造成因為未知的副作用導致失敗。相應的,DevOps 採取的做法是,危險的事情應該更頻繁的去做。高頻次的做法會儘早暴露大大小小的問題,而不是每年一次的大掃除。它強迫我們將過程自動化,自動測試整個過程,讓過程更流暢,這樣風險會降低。它讓人們更多參與到實踐中。實踐出精品。不再迴避我們恐懼的東西,幫助我們跨越危險,剋服挑戰。像任何經歷過術後恢復的人一樣,我們不斷的實踐直到它不再痛苦為止。
部署有一些固定成本。原則上,你需要逐漸將這些固定成本降低。如果增加部署頻率而不降低固定成本,則會傷害業務,這是不負責任的。
該文章其餘部分描述了兩種實踐,讓你即使使用 SQL 也可以快速發佈。實施它們需要開發者、質控、運維走出各自的筒倉,彼此協作,這也是 DevOps 的實質。這樣的結果是讓業務更平滑、更少痛苦、更少壓力。

 

技術1:自動 Schema 更新

 

在這個古老的方法論中,當團隊中的專家(通常為 DBA)手工修改 Schema 時,任何 Schema 的變化都需要整個應用系統關閉。如果你希望實現自動化部署,你需要自動化實現 Schema 的更新。
因此,應用需要管理 Schema。每個 Schema 的版本都需要記錄。應用從 Schema V1 版開始。這個值存在資料庫中(大致為 1 列的表,該列欄位儲存為 1)。當應用啟動時,它需要知道兼容 V1 的 Schema,如果它在資料庫中找不到這個版本,它可以拒絕運行。
為了自動更新 Schema,下一版本的軟體發佈時需要知道自己要求 V2 版的 Schema,知道 SQL 命令會將 V1版的 Schema 升級到 V2版。在啟動時,它看到版本為 1,然後運行響應的 Schema 升級命名,並將存在資料庫中的版本號升級為 2,之後繼續運行應用。
執行這個操作的軟體通常還有一個儲存 SQL Schema 升級命令表。這些命令以陣列儲存,索引 n 表示其是支持從 Vn-1 升級到 Vn。這樣,某個版本找不到也沒有關係,軟體能將資料庫恢復到任意需要的 Schema 版本。 實際上,如果發現有沒有初始化的資料庫(如在測試環境中),它會迴圈執行若干 Schema 升級,直到獲取到最新的版本。不是每個軟體的發佈都需要升級 Schema,因此隔離 Schema 和軟體的版本號。
已經有一些開源的和商業的系統實踐了這個過程。他們的一些產品比其他人更複雜,如支持多語言、多資料庫、錯誤處理及是否支持回滾等。從一項關於“SQL 自動化更新”的研究中,你會發現更多信息。我最熟悉的是面向.NET 代碼的開源專案 Mayflower[2] 和麵向 Go 的 Goose[3]。
Schema 的修改會導致資料庫被鎖定幾分鐘甚至幾個小時。這會引起應用系統的超時甚至故障。現代 SQL 資料庫已經減少此類問題了,需要感謝無鎖 Schema 升級和在線重建索引的特性。這些特性可以在現在 SQL 資料庫產品中找到,包括開源產品如 MariaDB、MySQL、PostgresSQL。查閱相關文件,以瞭解操作時的註意事項。
一旦你的軟體使用了這些技術,採用 CI(持續集成)會變得非常容易。你的自動化測試環境可以包含測試使用舊的 Schema 的資料庫並升級它,然後運行新版軟體。你的 Schema 升級過程可以在發佈到生產環境前,進行數百次的測試。這會為該過程帶來更多的信心,減少 Schema 升級的危險,解藕 DBA 本人直接參与到升級。他們可以享受原本屬於他們的周末了。
我對此技術最感興趣的是,你的 Schema 可以按代碼一起管理了。大幅減少了控制臺上的手工操作,可以在開發環境、測試環境、UAT(用戶驗收測試)環境、生產環境中不斷演練整個過程。你可以重覆執行這個過程,完善它。既然它是代碼,你可以應用代碼管理的實踐和軟體工程的計算去管理它。

 

技術2:針對多 Schema 編碼

 

在分佈式計算環境中,如何升級資料庫的 Schema 呢?
典型的網站系統前端是負載均衡服務,後端運行相同軟體的多個實體或副本。每個實體都承擔一部分 HTTP 負載,也訪問相同的資料庫實體。
如果軟體和資料庫 Schema 緊密耦合,而軟體升級又需要資料升級 Schema 時,操作會變得難以操作。如果你先改變 Schema,應用實體會故障或者至少會因此產生混亂;你需要盡可能快的升級實體,但是其實你已經輸了這場游戲,因為你已經在承受怒火。
為什麼不限升級應用實體呢?!悲劇的是,如果你逐一升級應用實體,最新升級的實體會不能啟動,因為它檢測到的是錯誤的 Schema。只有 Schema 與應用系統匹配時,你才能應用拉起,而也在這個時候停機時間才結束。
最直接的解決方案是,無視現實規律,改變資料庫的 Schema,並同時升級所有應用實體的版本。如果條件真的允許你這樣做,一切真可以這樣搞定。
遺憾的是,ACM 有套策略來應對現實法則,同樣大部分企業雇主也類似。這也是為什麼傳統方法是關閉整個應用,升級所有東西,然後再重新上線。這種最佳實踐延續直至 IEEE 的朋友核算瞭如何暫停的過程。
無論是違法現實規律還是計劃的停機,每次停機都會引入更大的問題:你完成了許多的獨立系統變更,但只有讓系統重新運行後才知道它們是否都還正常。同樣也不知道這些累計的改變會引發什麼樣的破壞。
大爆炸式升級變更是很危險的,而每次僅做一個變更,並驗證這個變更,就會降低風險。如果一次執行多個變更,此時碰到問題,必須逐一運行程式以確定哪個變更引起的問題。如果每次只做一個變更,即使碰到問題,排查會很簡單,也很容易回退變更。
即使是像谷歌這樣的擁有極其複雜成熟的測試技術和方法,如果不能理解預發佈環境和生產環境間細微的差別也可能會導致部署失敗。他們採用“金絲雀”發佈形式:升級其中一個實體,然後觀察是否運行是否正常,如果沒有問題,則繼續逐漸而有序的升級其餘的實體。這其實不是一項測試技術,而是應對測試不充分而採取的保障策略。需要說明的是,不是因為測試人員不優秀,而是沒有人是完美的。金絲雀技術目前已經是產業界流行的最佳實踐,而且已經被嵌入到 Kubernetes 系統中。(金絲雀一詞來源於煤礦業中金絲雀。煤礦礦工通常會帶著鳥進礦,一般會選擇金絲雀,因為這種鳥對人體有害的氣體非常敏感,如果在礦里有鳥死亡,則被視為危險的信號,提醒大家要撤離。)
由於軟體引起的問題通常與特定的 Schema 相關,解決方案就是松耦合。在設計階段去解除耦合,讓軟體可以同時支持多個版本的 Schema,以實現獨立激活或者回滾。
第一階段是編碼不再假定表中的欄位。用 SQL 術語說就是,SELECT 陳述句需要精確指出需要的欄位,而不是泛泛的使用 SELECT *。如果需要使用 SELECT *,則不要假定欄位按特定的順序排列。 LAST_NAME 也許今天是第三個欄位,但明天就不一定了。
基於這個原則,從 Schema 中刪除欄位也會簡單一些。新版部署後不使用相關欄位,一切都可以正常運轉。在所有實體都升級至新版後,再變更 Schema。實際上,冗餘的欄位可以先忽略,稍後再移除他們,甚至等到下次 Schema 變更時再進行清理工作。
添加欄位也會變得簡單,即在第一個實體部署前在 Schema 中添加新欄位即可。如前所述,我們採用技術 1(應用管理自己的 Schema),部署一個新發佈,它會修改 Schema,但不會使用這個新欄位。由於合適的事務鎖會控制住並行處理,第一個新升級的實體重啟後會更新 Schema。如果有問題,則金絲雀會宕掉。你就可以修複軟體,然後實驗新的金絲雀。而回退 Schema 的變更也是一個可選的方案。
由於 Schema 和軟體結構,開發者可以按需啟動新欄位。過去的升級需要考慮多個團隊的需求後才能確定一個維護視窗,而現在流程解耦,所有相關方可以有序的工作,而不必相互鎖定。
更多複雜的變更需要更多的規劃。當拆分欄位,移除欄位,添加欄位等時,收益才真正體現。
第一,軟體必須按同時支持新舊 Schema 的需求進行編寫,更為重要的是必須能處理變化階段。假定你需要從儲存人全名的欄位遷移至拆分為幾個單個欄位(第一個名,中間名,最後的名,頭銜等)。軟體需要檢查哪個欄位存在,且可用。在資料庫轉換過程中,它必須可用正常運行,這個過程中兩套欄位都會存在。一旦兩套欄位都存在時,一個批處理任務會將全名拆解為多個部分,然後將原欄位置為 NULL 值。代碼需要處理這種特殊場景,即有些行已經完成轉換,而有些還沒有完成轉換。
處理這個過程方案可稱之為“五階段在線 Schema 變更”。它有很多階段,包括創建新欄位,升級軟體,遷移資料,移除舊欄位。它又被成為McHenry 技術(詳見《雲系統管理實踐》),或者叫《Expand/Contract in Release It!: Design and Deploy Production》)
在線 Schema 變更的五個階段:
  1. 運行的代碼讀寫舊 Schema,即從表、視圖中選擇需要的欄位。這是初始狀態。

  2. 擴展:Schema 被修改,即增加新欄位,但不刪除任何舊欄位。沒有代碼需要變更。由於此時新欄位沒有被使用,所以如果需要回滾也不會很痛苦。

  3. 代碼被修改以使用 Schema 中的新欄位,併發布至生產環境。如果此時發生回滾,只需回滾至第2階段。這個時候,執行資料轉換操作。

  4. 簽約:訪問不再使用的舊欄位的代碼會被清除,且被髮布至生產環境。如果此時發生回滾,只需回滾至第 3 階段。

  5. 從 Schema 中移除舊的不再使用的欄位。這個階段如果發生罕見的回滾事件,則資料庫可簡單回滾至第 4 階段。

這些技術足夠處理在線分佈式系統中大部分複雜的 Schema 變更。而且,每個變更都可以獨立回滾。
針對特定場景階段的數量可以裁剪。如果只是加欄位,則階段 5 可以忽略,因為沒有需要移除的。四、五階段可以合併或重疊。第 5 階段可以合併至另一次 Schema 變更中的第 2 階段。
基於這些技術,你可以處理最複雜的 Schema 變更而不會有停機時間。

 

總結

 

使用關係資料庫並不妨礙推行 DevOps。自動化 Schema 管理加上一些開發原則就可以強化測試、縮短髮布周期、降低商業風險。
自動化發佈解放了我們。它將痛苦的、鴨梨山大、人工的升級過程,轉換為常規事件,且沒有事故。它減輕了商業風險,但更重要的是,創建了一個更可持續的工作環境。
當你可以充滿自信的部署新版時,你就可以更高頻率的去發佈。以前需要等數周甚至數月的新特性可以更早接觸用戶。問題修複更快。安全漏洞也及時解決。從而使企業能為客戶提供更多價值。
相關鏈接:
  1. https://queue.acm.org/detail.cfm?id=2945077

  2. https://github.com/bretcope/Mayflower.NET

  3. https://bitbucket.org/liamstask/goose

原文鏈接:https://queue.acm.org/detail.cfm?id=3300018
譯文鏈接:https://www.jianshu.com/p/5a0af8cecdaf
譯者:黃軍雷,國內知名資料庫廠商產品研發專案總監、MBA、PMP、CSM。

 

赞(0)

分享創造快樂