多個數據庫服務器可以協同工作,比如在主服務器失效的時候備份服務器立即取代它的位置(高可用性),或者幾台機器同時服務於同一個數據庫(負載均衡)。理想狀態多台服務器之間可以無縫協作。為靜態頁麵提供服務的 Web 服務器可以輕鬆的通過將 web 請求分攤到多台機器從而實現負載均衡。事實上,隻讀數據庫也能輕鬆的以相同的方法實現負載均衡。不幸的是,大多數數據庫服務器都需要同時處理混合的讀/寫請求,將這些數據庫聯合起來工作是件很麻煩的事。雖然隻讀數據隻需要在每台服務器上複製一份即可,但是在任何一台服務器上的寫動作都必須傳播到其它所有服務器上,這樣才能保證將來對這些已修改數據的讀取返回一致的結果。
這個寫同步問題就是導致多台服務器協同工作麻煩重重的最基本原因。有多種解決此問題的方法,其思路也各不相同,但都不是既簡單又高效的方案。
有一種解決方案是僅允許單獨的一台"主"服務器修改數據,其它"從"服務器隻能讀取數據,還可能存在平時不允許訪問、僅在失效切換後代替主服務器的"備用"服務器。
一些失效切換和負載均衡方案是"同步的",意思是直到所有服務器都完成了某個修改數據的事務之後,該事務才被認為是已經完成的。這將確保失效切換不會丟失任何數據並且所有服務器都將返回一致的結果。另一些方案是"異步的",這種方案允許在事務提交之後與傳播到所有其它服務器之間有一小段延時,但是在切換到備份服務器的時候某些事務可能會丟失,並且不同的服務器可能返回不一致的結果。當同步可能會很慢的時候可以使用異步通信。
還可以按照粒度對解決方案進行分類。某些方案隻能將整個數據庫集群作為一個整體,而某些方案可以針對每個數據庫或每張表分彆做不同的處理。
在選擇任何失效切換或負載均衡方案的時候都必須考慮性能因素。功能和性能不可兼得,比如,一個完全同步的解決方案在慢速網絡上可能削減性能一半以上,而完全異步的方案可能僅對性能有極其微小的影響。
下麵的部分大致描述了各種常見的失效切換、複製、負載均衡方案。
共享磁盤失效切換通過僅保存一份數據庫副本來避免花在同步上的開銷。這個方案讓多台服務器共享使用一個單獨的磁盤陣列。如果主服務器失效,備份服務器將立即掛載該數據庫,就像是從一次崩潰中恢複一樣。這個方案允許快速的失效切換並且不會丟失數據。
共享硬件的功能通常由網絡存儲設備提供,也可以使用完全符合 POSIX 行為的網絡文件係統(NFS)。這種方案的局限性在於如果共享的磁盤陣列損壞了,那麼整個係統將會癱瘓。另一個局限是備份服務器在主服務器正常運行的時候不能訪問共享的存儲器。
一種改進的方案是文件係統複製:對文件係統的任何更改都將鏡像到備份服務器上。這個方案的唯一局限是必須確保備份服務器的鏡像與主服務器完全一致,特彆是寫入順序必須完全相同。DRBD 是 Linux 上的一種流行的文件係統複製方案。
熱備份服務器(參見節23.4)可以通過讀取 WAL 記錄流來保持數據庫的當前狀態。如果主服務器失效,那麼熱備份服務器將包含幾乎所有主服務器的數據,並可以迅速的將自己切換為主服務器。這是一個異步方案,並且隻能在整個數據庫服務器上實施。
這個方案將所有修改數據的請求發送到主服務器。主服務器異步向從服務器發送數據的更改信息。從服務器在主服務器運行的情況下隻應答讀請求。對於數據倉庫的請求來說,從服務器非常理想的。
Slony-I 是這個方案的一個例子,它支持針對每個表的粒度並支持多個從服務器。因為它異步、批量的更新從服務器,在失效切換的時候可能會有數據丟失。
可以使用一個基於語句的複製中間件程序截取每一個 SQL 查詢,並將其發送到某一個或者全部服務器。每一個服務器都獨立運行。寫請求發送給所有服務器,讀請求則僅發送給某一個服務器,從而實現讀取的負載均衡。
如果隻是簡單的廣播修改數據的 SQL 語句,那麼類似 random()
, CURRENT_TIMESTAMP
以及序列函數在不同的服務器上將生成不同的結果。這是因為每個服務器都獨立運行並且廣播的是 SQL 語句而不是如何對行進行修改。如果這種結果是不可接受的,那麼中間件或者應用程序必須保證始終從同一個服務器讀取這些值並將其應用到寫入請求中。另外還必須保證每一個事務必須在所有服務器上全部提交成功或者全部回滾,或者使用兩階段提交(PREPARE TRANSACTION 和 COMMIT PREPARED)。Pgpool 和 Sequoia 是這種方案的實例。
在這種方案中,每個服務器都可以接受寫入請求,修改的數據將在事務被提交之前必須從原始服務器廣播到所有其它服務器。過多的寫入動作將導致過多的鎖定,從而導致性能低下。事實上,在多台服務器上同時寫的性能總是比在單獨一台服務器上寫的性能低。讀請求將被均衡的分散到每台單獨的服務器。某些實現使用共享磁盤來減少通信開銷。同步多主服務器複製方案最適合於讀取遠多於寫入的場合。它的優勢是每台服務器都能接受寫請求因此不需要在主從服務器之間劃分工作負荷。因為在服務器之間發送的是數據的變化,所以不會對非確定性函數(比如 random()
)造成不良影響。
PostgreSQL 不提供這種類型的複製。但是 PostgreSQL 的兩階段提交(PREPARE TRANSACTION 和 COMMIT PREPARED)可以用於在應用層或中間件代碼中實現這個功能。
對於那些不規則連接的服務器(比如筆記本電腦或遠程服務器),要在它們之間保持數據一致是很麻煩的。在這個方案中,每台服務器都獨立工作並周期性的與其他服務器通信以識彆相互衝突的事務。可以通過用戶或者衝突判決規則處理出現的衝突。
數據分區將一張表分解為多個數據集合,每個集合都僅可以被單獨一台服務器修改。例如,數據可以按照辦公室劃分:倫敦和巴黎的辦公室各自使用自己的服務器。如果某個查詢需要同時檢索倫敦和巴黎的數據,應用程序可以同時查詢兩個服務器或者在每一台服務器上使用主/從複製來保持一份其它服務器上數據的隻讀副本。
許多前述方案都允許多台服務器處理多個請求,但是冇有一個方案允許多台服務器同時處理一個請求以加快速度。這個方案就允許多台服務器同時服務於一條查詢語句。通常的做法是將數據在多個服務器上進行分割,然後每個服務器執行與其所含數據相關的查詢部分並將結果返回給中心服務器組裝成最終結果,然後再返回給用戶。Pgpool-II 就能實現這個功能。
因為 PostgreSQL 是開放源代碼並且很容易被擴展,許多公司在 PostgreSQL 的基礎上創建了商業的閉源解決方案,提供獨特的失效切換、複製、負載均衡功能。