編者按:一般來說,大型共享數據庫的管理會遇到不少麻煩,比如維護費用、剩余垃圾等等,但做好一些事情之后,這些麻煩就會不那么讓人頭疼了。
編譯 | 彎月 責編 | 張文
頭 圖 | CSDN 下載自東方 IC
出品 | CSDN(ID:CSDNnews)
以下為譯文:
數據庫是業務系統的基礎,不僅提供數據訪問,還需要在線數據庫定義語言、高可用性、存檔、異地磁帶輪換和管理、在線備份、訪問控制、監視和操作支持,以及復制到遠程數據中心。
通常,數據庫規模龐大,功能強大且成本昂貴。即便是集團級別的企業一般也只能負擔得起一個大型數據庫。此外,大型主機計算機系統及其數據庫非常昂貴,常常需要專門的工程師來構建能夠滿足企業需求的應用程序。
因此,很多公司的數據庫都會面臨一些相同的問題:每個應用程序都希望共享相同的數據庫,且數據庫內容納了許多垃圾?!肮氐谋瘎 痹跀祿熘幸矊映霾桓F。
因此,在本文中,我們就來探討一下如何在多個應用程序之間合理地共享數據庫,同時確保數據庫不會被文檔、照片、音頻文件和電影等非關系型的數據占據。
獨享數據庫與共享數據庫
在公司招收若干數據庫管理員,并建立起數據中心之后,公司的每個新應用程序都將使用這個數據庫。這也無可厚非,因為一般公司都無力再負擔另一個大型的主機。
很快,數據管理員就需要不斷設計各種的表結構,同時各種新功能和新應用程序也將源源不斷地添加到這個共享的系統。隨著公司的發展,這個數據庫變得越來越重要,數據庫管理員必須確保數據庫 24 小時無間斷運行。一旦數據庫出現任何問題,公司的業務也會立即受到影響。公司的所有數據都集中在這個共享數據庫。數據庫管理員需要確保所有的數據都安全,萬無一失。
剛開始的時候,每個應用程序都使用各自的表,服務各自的業務。然而,隨著應用程序之間的交互越來越多,程序員很快就會放棄繁瑣的消息機制或 API 調用,轉而直接讀取其他應用程序的表。這樣有什么壞處?各個應用程序和表之間的關系會越來越復雜,糾纏不清。通過一個事務更新多個應用程序的表的現象也會非常普遍。這時,數據庫的整潔性也會蕩然無存。
非關系型的數據應該保存在何處?
非關系型的數據,例如文檔、照片、音頻以及視頻資料也需要妥善地保存起來。程序員常常利用 SQL 的 Blob 類型來存儲大量的數據。雖然數據庫是保存這類數據的一個好地方,但從數據庫本身來看,這種做法弊端很多。
對數據庫的使用者來說,將大量的數據塞入數據省時又省心,不僅可以輕松地存儲數據,而且還有備份,保證了高可用性。此外,這些數據的更新也可以通過事務保證一致性。
然而,對于數據庫管理員來說,這就是噩夢!隨著大量數據的“入駐”,數據庫會變得臃腫不堪。將文檔、照片、視頻這類的不可變數據存儲在數據庫底層昂貴的存儲上,簡直就是浪費。
此外,從數據庫中提取這類龐大的數據并不是一件易事。也許我們可以利用掃描后的紙質文檔和其他介質的不可變的性質來幫助我們完成這一操作。你可以為文檔分配 128 位 UUID,并將文檔存儲在其他位置,而數據庫只需在相關的記錄中保存該標識符。
不過,很快你就會發現,將這些數據轉移到更便宜的存儲介質上時會遇到很多困難。
首先,無法保證數據更新的事務性。通常,你需要通過以下方式更新這些數據:
通過數據庫的事務 1 更新關系系統,即將 UUID-X 對象插入應用程序的表中,并通過另一列管理不可變對象的狀態。
使用 UUID-X 將不可變對象復制到新的存儲中。
通過數據庫的事務 2 更新對象的狀態(表示應用程序可以使用不可變對象了)。
這中間會出現什么問題?
如果在第 1 步和第 2 步之間出現問題,那就會導致表中出現不完整的數據,應用程序雖然可以讀取數據,但外部存儲中卻沒有實際的文件。與之類似,如果第2 步和第 3 步之間出現錯誤,就會導,外部存儲中的不可變對象就會變成永遠無法訪問的“垃圾”。
這個問題的解決方法是,使用另一個表來記錄正在進行的插入或者刪除。至少,這個表的狀態與應用程序表中的插入或刪除操作的狀態是同步的。而且,正在進行的插入或刪除應該帶有時間戳,這樣萬一出現了失敗的插入,你可以通過時間戳來判斷是否已經過了足夠長的時間,從而決定是否應該進行清理。這樣你的數據庫就相對穩定了。
另一個可能出現的問題是,過了幾個月或幾年后,存儲不可變數據的 blob 即將達到容量上限。為了在多個存儲中不可變數據,你不得不修改數據庫中所有保存了 UUID 的表,為它們添加一列來記錄存儲庫的 ID。結果,你會發現你不知道數據庫的哪些表存儲了不可變對象!因為各個應用程序在添加不可變對象的時候,并沒有彼此協調。
這個問題的解決方法就是,專門建立一個表來管理不可變對象,并通過一個專門的模塊來封裝更新操作。這樣只要新的應用程序遵守這個規則,采用間接的方式來使用不可變對象即可。但是,那些依然在直接訪問不可變對象的舊應用程序就只能聽之任之了,因為幾乎不可能把所有的直接訪問都找出來并改掉。
你可以通過這種間接的手段,跨數據中心復制數據,甚至可以將不可變的 Blob存儲遷移到新的數據中心。
分割系統
軟件工程中最大的問題之一就是解耦合。我們的系統包含成千上萬的軟件工程師傾注大量心血編寫的代碼,這些代碼相互交織,又互依互存。
然而,很多時候,共享寶貴的數據庫會讓我們的系統深陷巨大的泥潭。一旦各個應用程序之間開始互相訪問表,就很難將它們隔離開來。
為了實現應用程序之間的解耦合,我們需要循序漸進:
創建渠道,方便跨應用程序之間的異步工作。
禁止跨應用程序訪問表。應用程序可以保留表的只讀副本,并在表的“主人”更新數據時,異步更新這些副本。而其他應用程序只可訪問這些副本。
這種方式需要堅持多年才能實現解耦合。
通常,這些工作都需要系統架構師來推動,由他們從整體角度出發,設計各個應用程序的基礎架構。很多時候,系統的整體架構設計會與實際的工作發生沖突,公司領導需要高度重視公司文化的轉變與培養。在達成目標時成功給予公平合理的獎賞,并在構建新功能時優先考慮解耦合,避免解耦合成為團隊前進的阻力。
從某些方面來看,分割一個大型的應用程序要比將兩個遺留系統以解耦合的方式合并在一起簡單得多。在收購另一家公司時,他們的應用程序基于完全不同的公司文化,對最基本的概念(比如客戶)都有不同的理解,如果將這些應用程序的元數據直接融合到你的數據庫中,那么勢必引發巨大的混亂。
分割巨大的應用程序就像規劃一座城市的道路一樣,如何能夠像古代長安一樣,規劃出整齊劃一又四通八達的街道,這本身就是一項巨大的挑戰。一不小心,城市中心就會被一座座摩天大樓割裂,道路崎嶇,交通堵塞。
數據庫的擴張
隨著公司業務的發展,數據庫必將不斷增長。即使將 Blob 等大型數據存儲在其他地方,實現各個應用程序之間的解耦合,相關的數據仍然在同一個數據庫中。
通常,數據庫的擴張可以朝著兩個方向發展:
向上擴展:購買更多更好的硬件,擴展數據庫的底層設施。
向外擴展:嘗試向外擴展數據庫,通過多臺計算機組成的集群來構建更大規模的數據庫。
規模擴大意味著保存的數據更多,處理的事務也更多。應用程序是在平臺上開發出來的,而數據庫是平臺的主要組成部分。將某個大型應用程序移至新數據庫的難度非常大,且風險極高。
數據庫中有許多微妙的方面,會對應用程序大量的代碼產生影響,比如:
使用非標準數據庫功能。每個數據庫供應商都會提供一些專屬的非標準功能。在大多數情況下,代碼中會存在大量使用這些非標準功能的代碼,而且經過長年累月的修改和重構,這些代碼已經深深扎根于代碼庫中,而最初的作者早已離職,根本沒辦法找出當初這樣做的原因。
并發語義。 多版本并發控制、可序列化性、可重復讀取以及讀取已提交數據這些并發模型之間的細微差別并不是無中生有。 有關這方面的討論層出不窮,在大型應用程序保證并發語義的一致性非常關鍵。
樂觀并發控制與悲觀并發控制。 樂觀并發控制假設多用戶并發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自的數據。如果發生沖突,則事務中止。大多數時候,這種做法更有效率。在悲觀并發控制中,如果一個事務執行的操作讀某行數據應用了鎖,那么只有當這個事務釋放鎖,其他事務才能夠執行與該鎖沖突的操作。盡管悲觀并發控制的代價更高,但這種方式可以防止陷入由于沖突而反復提交失敗的困境。雖然樂觀和悲觀之間的選擇并不會影響應用程序所做更改的正確性,但數據庫行為更改為超出應用程序預期的范圍可能會引發很多性能問題。
數據庫遷移的難度不容小覷。而將數據庫從集中式系統轉移到分布式系統,雖然規模的擴張毋庸置疑,但其性能肯定會受到某些影響。協調鎖和并發將面臨各種的性能挑戰,而整個過度的過程也將充滿艱險。因此,將應用程序遷移到分布式數據庫上,需要耐心和洞察力。
總結
人們對于數據庫往往是愛與痛并存。讓每個應用程序獨享數據庫會導致公司內產生很多數據庫,不僅會加重成本的負擔,而且也會為數據庫的操作和管理帶來挑戰。然而,共享數據庫時,應用程序之間又面臨解耦合與交互的問題。因此,我們需要謹慎地使用數據庫:
管理 Blob。創建一種形式化的機制來處理 Blob,即使目前你將這些數據保存在數據庫中,以后也可以移至其他地方。
保證應用程序之間的解耦合,讓每個應用程序都遠離其他應用程序的表。
利用消息機制實現應用程序之間的交互。通過某種形式的異步消息將應用程序連接起來,結合應用程序的解耦合,可以降低數據庫的遷移難度。