很多小型網站的開發人員一開始將注意力放在產品需求設計上,這本無可厚非。但如果忽視整體性能、可擴展性等方面的考慮,眼看着訪問量一天天往上爬,可突然發現有一天網站因為訪問量過大而崩潰了,到時候哭都來不及。

我在後端設計中曾經提到,對於高並發高訪問的Web應用來說,數據庫存取瓶頸一直是個令人頭疼的問題。特別當你的程序架構還是建立在單數據庫模式,而一個數據池連接數峰值已經達到500的時候,那你的程序運行離崩潰的邊緣也不遠了。在Web網站的規模從小到大不斷擴展的過程中,數據庫的架構也需要動態擴展,每一次擴展性能上都可以得到數量級的提升。

1. Web應用和數據庫部署在同一台服務器上

Web應用和Database在同一台服務器

Web應用和Database在同一台服務器

在用戶量、數據量、並發訪問量都比較小的情況下可以採取這種模式。不過隨着訪問量增加,應用程序和數據庫都搶用有限的系統資源,很快會遇到性能問題。

2. Web應用和數據庫部署在各自獨立的服務器上

WEB應用和數據庫部署在各自獨立的服務器上

WEB應用和數據庫部署在各自獨立的服務器上

服務分離,Web應用和數據庫分開部署,服務器各司其職,在訪問量增加時可以根據情況分別升級應用服務器和數據庫服務器。這是一般小規模網站的典型部署方式,在將應用程序進行性能優化並且使用數據庫對象緩存策略的情況下,可以承載較大的訪問量,比如2000用戶,200個並發,百萬級別的數據量。

3. 數據庫服務器採用集群方式部署

一個數據庫多個實例

一個數據庫多個實例

比如Oracle的一個數據庫多個實例的情況。數據庫物理介質為一個磁盤陣列,多個數據庫實例以虛擬IP方式向外部應用服務器提供數據庫連接服務。這種部署方式基本上可以滿足絕大多數的Web應用需求。

4. 數據庫的主從複製模式

數據庫的主從複製

數據庫的主從複製

在數據庫訪問中存在眾多的查詢操作,在多數情況下一個顯著特點就是讀操作遠大於寫操作,而且查詢條件相對複雜,數據庫的大部分性能實際消耗在查詢上。假如能將數據庫的讀寫操作分離,對於系統性能來講會有一個很大的提高空間。

幾乎所有的主流數據庫都支持複製,以Mysql為例,只需要開啟主服務器上的二進制日誌以及在主服務器和從服務器上分別進行簡單的配置和授權。主從複製是依據主服務器的日誌文件進行的,主服務器日誌中記錄的操作會定時在從服務器上重放,從而實現複製,所以主服務器必須記錄所有對於主數據庫的更新操作。主從複製用於自動備份,但在這裡我們的目的是讀寫分離。為保證數據的一致性,我們要求所有對於數據庫的更新操作都是針對主數據庫的,但是讀操作可以針對從數據庫來進行。

主從複製數據是異步完成的,這就導致主從數據庫中的數據有一定的延遲,在讀寫分離的設計中必須要考慮這一點。以博客為例,博主發表了一篇文章,他需要馬上看到自己的文章,但是對於其它讀者來講是可以允許延遲一段時間的。所以其他訪問量更大的外部用戶就可以讀從數據庫。

5. 數據庫垂直分割

主從部署方式適用於讀操作比寫操作更加密集的前提,如果寫操作佔了主數據庫CPU消耗的50%以上,我們再增加從服務器就沒有意義了,因為所有的從服務器的寫操作也將佔到CPU消耗的50%以上,從服務器所能為查詢操作提供的資源非常有限。數據庫需要重新架構,我們需要採用數據庫垂直分區技術。

最簡單的垂直分區方式是將原來的數據庫中獨立的業務進行分拆,被分拆出來的部分與其它部分不需要進行Join連接查詢操作。比如Web站點的Blog和論壇,是相對獨立的,他們之間數據的關聯性不是很強,這時可以將原來的的數據庫拆分為一個Blog庫,一個論壇庫,以及剩餘的表所組成的庫。這三個庫再各自進行主從數據庫方式部署,這樣整個數據庫的壓力就分擔啦。

查詢擴展性也是採用數據庫分區最主要的原因之一,將一個大的數據庫分成多個小的數據庫可以提高查詢的性能。事實上,當前十分火熱的NOSQL技術就是採用空間換時間,使用key-value型的存儲結構,即使有很多數據冗餘,但由於避免了Join操作從而提升了性能,NOSQL在高並發高負載的網站廣泛應用。

6. 數據庫水平分割

在數據庫的垂直分區之後,如果仍無法應對大量的寫操作,這時我們需要的是水平分區。

水平分區意味着我們將同一個數據庫表中的記錄通過特定的算法進行分離,分別保存在不同的數據庫表中,從而可以部署在不同的數據庫服務器上。很多的大規模的站點基本上都是主從複製+垂直分區+水平分區這樣的架構。水平分區並不依賴什麼特定的技術,完全是邏輯層面的規劃。對於那些頻繁訪問導致站點接近崩潰的熱點數據,我們必須分區。

在對數據水平分區的時候,我們要找一個索引字段,比如USER_ID,它必須和所有的記錄都存在關係,是分區數據庫中的核心表的主鍵,在其它表中作為外鍵,並且在使用主鍵的時候,該主鍵不能是自增長的,必須是業務主鍵才可以。具體的分區方式有:

  • 餘數分區:將User_ID%10後的值為依據存入到不同的分區數據庫中,該算法簡單高效,但是在分區數據庫個數有變動的時候,整個系統的數據需要重新分布。
  • 範圍分區:將User_ID的範圍進行分區,比如1-100000範圍為一個分區數據庫,100001-200000範圍為一個分區數據庫,該算法在分區數據庫個數有變動的時候,系統非常有利於擴展,但容易導致不同分區之間的壓力不同,例如老用戶所在的分區數據庫的壓力很大,但是新用戶的分區數據庫的壓力偏小。
  • 映射關係分區:將對分區索引字段的每個可能的結果創建一個分區映射關係,這個映射關係非常龐大,需要將它們寫入數據庫中。比如當應用程序需要知道User_id為10的用戶的BLOG內容在那個分區時,它必須查詢數據庫獲取答案,當然,我們可以使用緩存來提高性能。這種方式詳細保存了每一個記錄的分區對應關係,所以各個分區有非常強的可伸縮性,可以靈活的控制,並且將數據庫從一個分區遷移到另一個分區也很簡單,也可以使各個分區通過靈活的動態調節來保持壓力的分布平衡。

參考鏈接:http://blog.csdn.net/zhangzhaokun/archive/2009/10/22/4711693.aspx