作者 | Roberto Vitillo
譯者 | 彎月,責編 | 屠敏
頭圖 | CSDN 下載自視覺中國
出品 | CSDN(ID:CSDNnews)
以下為譯文:
想象一下,給變量賦值,然后立即讀取,卻發現剛剛的寫入根本不起作用,是不是很抓狂?
x = 42
assert(x == 42) # 拋出異常
在使用一致性保證較弱的分布式數據存儲時,就有可能遇到這種情況。你可能會問:“等等,難道數據庫不是應該為我解決一致性的問題嗎?”執行更新操作后,實際的數據會立即被更新還是需要等待一段時間,取決于數據庫是否提供這種保證。
有些數據庫提供的一致性保證有點違反直覺,但其目的是提供高可用性和高性能。還有一些數據庫可以讓你選擇想要更好的性能還是更強的保證,例如Azure的Cosmos DB和Cassandra。因此,你需要了解其中的利弊。
數據庫請求剖析
下面我們來看看,當你將請求發送到數據庫后,接下來會發生什么。在理想的情況下,你的請求會被立即執行:
但是,我們并非生活在理想世界中,你的請求需要送到數據存儲,然后經過處理,最后再將響應發送給你。所有這些操作都需要一定的時間,無法在瞬間完成:
數據庫可以提供的最佳保證是,在調用和完成之間的某個時間點上執行請求。你可能會認為這也沒什么大不了,畢竟你在編寫單線程應用程序時就習慣了這一點,例如將1賦給x,然后讀取x的值,那么必然會得到1,當然前提是沒有其他線程寫入同一變量。然而,當你使用的數據存儲為了實現高可用性和可伸縮性,將數據狀態復制到多臺計算機上,那么一切都變成了未知數。為了理解為什么會出現這種情況,我們來探討一下在分布式數據庫的簡化模型中,系統設計師在實現讀取的時候必須權衡的利弊。
假設我們有一個分布式鍵值存儲,它由一組副本組成。副本之間會選出一個領導者,這是唯一可以接受寫入的節點。在領導者收到寫請求后,它會通過異步將數據寫入到其他副本。盡管所有副本都以相同的順序收到相同的更新,但是它們接收到的時間點不同。
你的任務是想出一種策略來處理讀取請求,你該怎么辦?你可以從領導者或其他副本中讀取數據。如果所有讀取都經由領導者,那么吞吐量就會成為瓶頸,無法超過單個節點可以處理的數據量。如果任何副本都可以服務請求讀取,那么吞吐量就可以得到極大地提升,但這種情況下兩個客戶端(觀察者)獲取的系統狀態就有可能不一致,因為領導者和副本之間以及各個副本之間可能出現延遲。
簡單來說,我們需要在觀察者看到的系統的一致性與系統的性能和高可用性之間進行權衡利弊。為了理解這種關系,我們需要準確地定義一致性。我們可以參考一致性模型(https://jepsen.io/consistency),該模型定義了系統狀態觀察者可能體驗到的系統狀態視圖。
強一致性
如果客戶端的寫和讀操作只能發送給領導者,則似乎每個請求都在特定的時間點以原子的方式進行,就好像只有一個數據副本。無論有多少個副本,也無論每個副本延遲多少,只要客戶端直接查詢領導者,那么從他們的角度來看,只有一個數據副本。
由于請求不會立即被服務,而且只有一個節點提供服務,所以請求必然在調用和完成期間內執行。另一種思考方式是,在請求完成后,所有觀察者都可以看到它的副作用:
由于在請求調用和完成之間,其他參與者都可以看到這個請求,因此實時性必須得到保證。這種保證有一個理論上的一致性模型,名叫線性一致性,又稱強一致性。線性一致性是系統可以為單個對象請求提供的最強一致性。
如果客戶端向領導者發送了讀取請求,但在請求到達時,這個領導者已被廢除,但收到請求的服務器認為它仍然是領導者,該怎么辦?如果由前一個領導者處理請求,則無法保證系統的強一致性。為了防止出現這種情況,這個假定的領導者首先需要聯系大多數副本,確認自己是否仍然是領導者。只有自己仍是領導者的時候,它才能執行請求并將響應發送回客戶端。這個過程會大幅增加讀取所需的時間。
順序一致性
到此為止,我們討論了由領導者按照順序處理讀取的做法。但是這種做法會產生一個瓶頸,從而限制系統的吞吐量。最重要的是,領導者還需要聯系大多數副本才能處理讀取。為了提高讀取性能,我們應該允許副本處理請求。
盡管副本會落后于領導者,但它接收到更新的順序與領導者相同。如果客戶端A僅查詢副本1,而客戶端B僅查詢副本2,則由于副本不完全同步,這兩個客戶端在不同時間點看到的狀態也有所不同:
在這個一致性模型中,對于所有觀察者來說,操作發生的順序相同,但操作的副作用何時會被觀察者看到,該模型不能提供任何實時性的保證。該模型稱為順序一致性。順序一致性與線性一致性之間的差異就在于前者缺乏實時性保證。
這種模型的一個簡單應用是與隊列同步的生產者/消費者系統:生產者節點負責寫入隊列,而消費者則負責讀取。生產者和消費者看到隊列各項的順序相同,但消費者落后于生產者。
最終一致性
盡管我們設法提高了讀取吞吐量,但我們不得不將客戶端固定到某個副本上,此時如果副本出現故障該怎么辦?為了提高存儲的可用性,我們可以通過允許客戶端查詢任意副本。但是,就一致性而言,這一步需要付出高昂的代價。假設有兩個副本1和2,其中副本2落后于副本1。如果客戶端在查詢了副本2之后,緊接著又查詢副本1,那么它將看到過去的狀態,這可能會令人非常困惑。客戶端擁有的唯一保證是,如果系統的寫入停止,則所有副本最終都將收斂到最終狀態。這種一致性模型稱為最終一致性。
在最終一致性的數據存儲之上構建應用程序非常困難,因為其行為與你所習慣的編寫單線程應用程序的行為不同。任何一個細小的錯誤都可能逐步蔓延,而且很難調試和重現。然而,并非所有應用程序都需要線性一致性,所以最終一致性也有一定的用武之地。你需要做出明智的選擇,仔細考慮你的數據存儲提供的保證是否能夠滿足應用程序的需要。如果你想記錄網站的訪問量,那么最終一致性將是你的首選,因為讀取返回的數字是否有些過時并不重要。但對于支付系統來說,強一致性絕對不可或缺。
PACELC定理
除了本文介紹的模型之外,還有很多有關一致性的模型。但其背后的基本思想萬變不離其宗:一致性保證越強,單個操作的等待時間就越長,而且發生故障時存儲的可用性越低。這種關系又稱之為PACELC定理:在分布式計算機系統中進行網絡分區(Partitioning,即P)的時候,我們必須在可用性(Availability ,即A)和一致性(Consistency ,即C)之間進行選擇,否則(Else,即E)即便系統沒有任何分區,我們也必須在延遲(Latency ,即L)和一致性(Consistency,即C)之間進行選擇。
原文:https://robertovitillo.com/what-every-developer-should-know-about-database-consistency/
本文為 CSDN 翻譯,轉載請注明來源出處。