需要數據庫分片嗎?怎麼分

原文鏈接:https://github.com/xitu/gold-miner/blob/master/TODO1/understanding-database-sharding.md

概述

任何蓬勃發展的應用或者網站,最終都需要擴容開來以適應流量的增長。對於數據驅動的應用和網站來說,以一種能夠保障數據安全和完整性的方式進行擴容尤爲重要。要預測一個網站或應用將來會有多火,以及它能火多久是非常困難的,因此一些團隊選擇的數據庫架構都支持動態擴展,

在本篇概念文中,我們將會討論一種支持動態擴展的數據庫架構:分片數據庫。近年來,數據庫分片技術非常抓人眼球,但很多人並不瞭解它到底是什麼,以及什麼場景下對數據庫進行分片才能物盡其用。我們將在文中討論什麼是數據庫分片,分析其優劣以及一些常見的數據庫分片方法。

什麼是數據庫分片?

數據庫分片是一種橫向分區的數據庫架構模式,所謂的橫向分區技術就是將一個表中的數據按行拆分爲多個不同的表的實踐方式,這些不同的表被稱作分區。每個分區都擁有相同的模式和相同的列,但是數據行卻完全不同。同樣的,每一個分區中的數據都是唯一的,並且獨立於其他的分區。

結合縱向分區來理解橫向分區或許會更容易一些。在縱向分區的表中,成列的數據被分離到另外的新表中。每個縱向分區中的數據都獨立於其他分區,並且每個分區的行和列都不相同。下面的圖展示瞭如何對錶進行橫向和縱向分區:

例:表的橫向分區與縱向分區

數據庫分片包括將數據分成若干個子集,稱爲邏輯分片。邏輯分片分佈在不同的數據庫節點上,這些節點被稱爲物理分片。因此,每個物理分片能夠被劃分爲多個邏輯分片。儘管如此,所有這些分片集合中的數據一起表現出來在邏輯上就如同一整個數據集。

數據庫分片是無共享架構的一個例證。這意味着分片行爲是自治行爲;各個分片之間不會共享任何數據或者計算資源。然而在某些情況下,將某些特定的數據表作爲引用表複製到各個分片中也非常有用。比如,某個應用依賴固定的換算率在不同的重量單位之間換算。設計數據庫的時候,把保存所需的換算率數據的表複製到每個分片中,將會幫助確保在每個分片中都保存着換算查詢所需要的數據。

數據庫分片通常在應用級別實現,應用裏的代碼定義了用於讀、寫的數據庫分片。然而,一些數據庫管理系統內置了分片功能,允許你直接在數據庫級別實現分片操作。

瞭解了以上關於數據庫分片的基本知識,我們來看這種數據庫架構下的優點和缺點。

數據庫分片的優點

數據庫分片技術最吸引人的要數它能簡化橫向擴展的工作,也被稱爲擴張(scaling out)。橫向擴展指的是在已有的基礎上增加更多的機器來疏散負載,從而承載更多的流量、實現更快的處理速度。經常拿來與之比較的是縱向擴展,或者叫做擴容(scaling up),指的是升級現有機器的硬件配置,通常指加內存、加 CPU。

我們可以很容易地在某一臺機器上運行一個關係型數據庫實例,只需要通過升級機器的計算資源即可對其進行擴容。然而,所有非分佈式的數據庫最終都會受限於機器的存儲容量和計算力,因此能夠自由地橫向擴展將使你的應用變得更加靈活。

另一個選擇數據庫分片架構的原因是提高查詢的響應速度。假如不曾對數據庫做分片處理,當你向數據庫發送查詢指令的時候,可能需要查詢表中的所有行才能找到需要的數據。對於擁有龐大的單個數據庫的應用來說,查詢將會變得非常慢。通過分片技術,將數據分散到多個表,查詢的時候將會在少部分數據中查找需要的數據,同時能夠更快地返回結果集。

數據庫分片還能降低宕機的影響,從而使應用更加穩定。如果你的應用或網站依賴於一個未經分片的數據庫,宕機有可能會使得整個應用都不可用。但假如是分片的數據庫,宕機也只會影響其中的一個分片。儘管我們的應用或網站的某些功能可能無法被一些用戶訪問,但總的影響仍然會比整個數據庫都掛掉要小很多。

數據庫分片的缺點

儘管對數據庫分片能夠易於擴展、提高性能,但是它也有一些侷限性。我們將在這部分討論其部分侷限性,並解釋爲什麼不應該一股腦兒地對所有的數據庫做分片處理。

數據庫分片中遇到的第一個困難就是正確地實現數據庫分片架構相當複雜。如果沒能正確操作,你將會承受極大的風險,因爲在數據庫分片過程中可能會造成數據丟失、數據表損壞等後果。即使成功了,數據庫分片操作很可能會極大地影響團隊的工作流。大家不再通過單點來讀取、管理數據,他們將跨多個數據庫分片來管理數據,這可能會在一些團隊中造成混亂的局面。

還有一個問題,大家可能在數據庫分片之後遇到分片不平衡的現象。舉個例子,假如你的數據庫有兩個單獨的分片,一個保存着姓名以 A 到 M 字母開頭的客戶,另一個保存着姓名以 N 到 Z 字母開頭的客戶。然而,你的應用有大量的姓名以 G 字母開頭的用戶。因此,A-M 分片逐漸產生了比 N-Z 分片更多的數據,導致了應用變得很慢,甚至在部分用戶的使用中卡住。這裏 A-M 分區就是所謂的數據庫熱點。這個例子中,所有數據庫分片帶來的好處因爲應用的反應慢和崩潰而變得一文不值。因此,數據庫需要被修復,被重新分片來達到更加合理的數據分佈。

另一個重要的缺點就是,一旦數據庫分片完成了,就很難恢復到分片之前的架構了。分片操作之前所做的任何備份都不包含分區之後寫入的數據。因此,要重建原始的未分片的數據庫架構就必須將新分區的數據和舊的備份數據融合在一起,或者將各個分區的數據庫導入到單個數據庫中。這兩種方式都費時費力,代價不菲。

最後一個需要考量的缺點,並非所有的數據庫引擎都原生地支持分片操作。比如,PostgreSQL 沒有自動分片的功能,因此我們只能手動地給 PostgreSQL 數據庫分片。雖然有很多 Postgres 的分支版本具備自動分片的功能,但是這些版本的發佈通常都晚於最新的 PostgreSQL 版本,並且會缺少某些功能。一些專業的數據庫技術 - 比如 MySQL 集羣或者某些數據庫服務產品(比如 MongoDB Atlas)- 具備自動分片的功能,但是普通版本的數據庫關係系統中並不具備這樣的功能。正因如此,數據庫分片操作通常需要你自己動手,這也意味着很難找到關於數據庫分片操作的文檔或者排查問題的建議。

當然,這些僅僅是數據庫分片操作之前需要考慮的一些常見問題。面對不同的使用場景,數據庫分片操作可能會遇到更多的問題。

至此我們討論了一些數據庫分片的優缺點,接下來我們將介紹幾種數據庫分片的架構方式。

數據庫的分片架構

一旦你決定要對數據庫進行分片,接下來你要做的就是弄清楚如何去實現它。查詢數據的時候或者保存數據到分片的數據庫或數據表中的時候,選用正確的分區至關重要。否則,將會造成數據丟失或者令人痛苦的慢查詢。在這部分,我們將要介紹一些常用的數據庫分片架構,每種架構在不同分片之間保存數據的方式都略有不同。

基於鍵的數據庫分片

基於鍵的分片也叫基於哈希的分片,使用新寫入數據的值 —— 比如客戶 ID、客戶端 IP、ZIP 碼等等 —— 通過哈希函數決定保存的分片位置。哈希函數將輸入的數據(如用戶的郵件地址)轉換成離散數據(也叫做哈希值)輸出。在數據庫分片中,哈希值作爲數據庫分片 ID 將數據保存到對應的分片中。總的來說,整個過程是這樣的:

例:基於鍵的數據庫分片圖示

爲了保證所有數據的保存位置正確,保存行爲一致(即相同的數據每次都保存到相同的位置,譯者注),哈希函數的輸入值應當爲同一列數據。這列數據被稱爲分片鍵。簡單一點,分區鍵就類似於主鍵,兩者都是用來給每個數據/分片創建一個唯一標識的。一般來說,分片鍵應該是靜態的,也就是說它不應該保存可變的數據。否則,更新數據的時候將會產生更多的工作量,還可能會降低性能。

儘管基於鍵的數據庫分片是一種非常常見的數據庫分片架構方式,在這種架構上動態地增加或移除數據庫服務器卻是一件困難的事情。當你增加服務器的時候,每個數據庫分片的哈希值都需要調整,因此,即使不是全部數據,也會有很多數據需要重新映射到正確的哈希值,然後遷移到正確的服務器。當你對數據進行均衡性調整的時候,新舊哈希函數都將失效。因此,在數據遷移期間,數據庫服務器將不能寫入任何數據,應用也將暫停服務。

這種策略最吸引人的是它能夠將數據平均地分配,從而避免數據熱點的出現。另外,因爲它使用算法來分配數據,無需像其他策略(基於範圍或基於目錄的數據庫分片策略)那樣需要維護一個數據分佈的映射關係。

基於範圍的數據庫分片

基於範圍的數據庫分片基於給定數據的範圍來對數據做分片處理。這麼說吧,假如你有一個保存着零售店的商品信息的數據庫,你可以創建一些數據庫分片,然後根據價格區間將商品數據分佈到不同的分片中,像這樣:

例:基於範圍的數據庫分片圖示

基於範圍的數據庫分片最大的好處就是實行起來相對簡單一些。每個分片都保存着不同的數據集,但它們卻有着與衆不同的模式,當然也不同於原始的數據庫。應用代碼中只需讀取數據落在了哪個區間,並且將其寫入對應的分區即可。

另一方面,基於範圍的數據庫分片並不能防止數據的不均勻分佈,也不能防止出現前文所說的數據庫熱點。再來看上文的圖例,即使每個分片保存的數據量相等,但是還可能會產生某些產品比其他更加受歡迎,因此各個分片在數據的讀取上也會出現分佈不均的情況。

基於目錄的數據庫分片

若要實現基於目錄的數據庫分片,你必須創建並維護一個查找表,並在表中通過數據庫分片的鍵來記錄數據保存的位置。簡而言之,查找表就是保存着如何查找指定數據的靜態數據集的表。下圖展示了一個基於目錄的數據庫分片的簡單的例子:

例:基於目錄的數據庫分片圖示

這個例子中,Delivery Zone 列被定義爲分片的鍵,分片鍵的值和該行數據保存的分區被一起寫入查找表中。這跟基於範圍的數據庫分片類似,但是我們並沒有判斷數據落在了哪個區間,而是將每條數據綁定到了其所在的分片。相對於基於範圍的數據庫分片來說,基於目錄的數據庫分片不失爲一個更好的選擇,假如分片鍵的基數本來就很小,那麼對於數據庫分片來說,保存某個範圍的鍵將變得沒有意義。當然,基於目錄的數據庫分片也不同於基於鍵的數據庫分片,它並不使用哈希函數來處理分片鍵,而只是在查找表中檢查鍵名然後找到數據需要被寫入的位置。

基於目錄的數據庫分片架構最吸引人的地方就是靈活性。基於範圍的數據庫分片架構要求你必須制定值的區間,而基於鍵的數據庫分片架構則要求你必須使用一個固定的哈希函數,正如前文所說,要修改該函數是非常困難的一件事。而基於目錄的數據庫分片架構允許你使用任何系統、任何算法來指定數據保存的分片位置,而且添加分片也相對簡單一些。

儘管基於目錄的數據庫分片架構是目前討論過的最靈活的分片方法,但是每一次查詢、寫入數據之前都需要先查詢查找表可能會影響應用的性能。而且,查找表可能會產生單點故障:如果查找表損毀了,或者由於其他原因失效了,將會影響寫入新數據或者查詢現有數據的功能。

我應該對數據庫分片嗎?

究竟該不該採用分片的數據庫架構一直是備受爭議的問題。一些人認爲當數據庫達到一定量級的時候,採用分片架構是必然的;另一些人則認爲由於操作複雜,除非是萬不得已,否則不應該使用分片架構。

由於其複雜性,數據庫分片架構多用於處理非常大量的數據。以下這些場景中,對數據庫分片可能會非常有用:

  • 應用數據不斷增長,超出了單點數據庫的存儲能力。
  • 數據庫的讀寫超出了單點數據庫或只讀從庫(讀寫分離架構下,譯者注)的處理能力,從而導致了響應慢或超時。
  • 應用所需的網絡帶寬超出了單點數據庫或只讀從庫的可用帶寬,從而導致了響應慢或超時。

對數據庫分片之前,你最好先嚐試其他的方式來優化你的數據庫。比如:

  • 使用遠程數據庫。如果你有一個龐大的應用,其所有的組件都依賴於同一個數據庫服務器,你可以考慮將數據庫遷移到一臺單獨的機器上來提高其性能。這不會像數據庫分片那樣複雜,因爲所有的數據庫表都還是完整的。並且,這種方式還允許你能夠拋開其他基礎設施,單獨地對數據庫做縱向擴展。
  • 使用緩存。如果應用受制於讀數據的性能,使用緩存是改進性能的一種方式。緩存通過將請求過的數據暫時地保存在內存中,加速後續的數據訪問。
  • 創建若干個只讀從庫。另一種能夠改進讀取性能的方法是將數據從主庫拷貝到若干個從庫中。這樣一來,新的寫操作將使用主庫,之後再拷貝到從庫中,而讀操作將使用從庫。這種讀寫分離的模式使得每一臺機器都不會承受過大的負載,有助於防止機器變慢甚至崩潰。值得注意的是,創建只讀從庫需要更多的計算資源、花更多的錢,某些情況下,這些將會使你步履維艱。
  • 升級服務器。一些情況下,升級數據庫服務器的配置比數據庫分片簡單多了。對比創建只讀從庫,升級服務器配置可能會花更多的錢。因此,除非這真的是你最好的選擇,否則不應該對機器擴容。

謹記只有當你的應用或者網站增長超過了一定量級的時候,以上這些方法都不足以有效地改進其性能的時候,數據庫分片才真的是最佳選擇。

總結

數據庫分片對嘗試橫向擴展數據庫的人來說是一個非常好的方案。然而,它也將模型變得更加複雜,也爲應用增加了很多潛在的故障點(每個數據庫分片都有可能發生故障,譯者注)。對一些人來說,數據庫分片可能會非常有必要,但是對於另一些人來說,創建和維護數據庫分片架構所花費的時間和資源將會大於它帶來的好處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章