簡介
在前面的文檔中,我們介紹了RVN的概念,通過RVN可以解決的某類問題和使用技巧,以及處理RVN的邏輯的具體實現。在本文中,我們將要介紹關于如何使用RVN解決另一種在分布式系統中常出現的問題。
問題
假設我們創建了一個service來維護某種record。我們的service允許client獲取record,并且基于現有record的內容對record進行修改。舉例說,假設我們的record記錄了client一方的某種操作的數量。Client每完成一次操作就將service一側的count加1。當然我們有其它的方法,但是我們現在要求count的計算部分在client一側完成。具體來說,我們的record可以定義為:
Record {int ID;int count;
}
Service提供的API 為:
void updateRecord(Record record);
但是在分布式系統中,可能有多個client同時試圖更新同一個record,這樣這兩個client的update就會互相覆蓋,從而使最終的結果錯誤。例如在下圖,我們數據庫中保存的ID “1”的數量是10。現在有兩個client同時獲取了這個記錄,然后同時試圖將數量改為11。最終兩個帶有11的結果將互相覆蓋,從而我們錯誤的保存了11,而不是12。我們將如何避免這個問題?
解決方法
這個問題同樣可以使用RVN來解決。具體來說,我們在record里加入RVN,代表某條記錄的版本號。對于每次更新,client都要先獲取當前記錄以及它的版本號,然后將版本號加1寫入到update record的request里。而service端需要檢查request的RVN,確保該RVN大于當前保存的保本好,最后再將該記錄和RVN寫入到數據庫。我們詳細描述該過程如下:
現在client1和client2都獲取了RVN為1的記錄,然后都將RVN更新為2,發送request去試圖更新service一側的數據。Service一側的邏輯如下:
在這里我們需要特別說明幾點。為了保證處理的正確性,service必須保證在處理record的過程中RVN一直是合理的,否則就可能出現兩個thread都認為自己的RVN是正確的,從而仍然互相覆蓋。這樣我們可以使用lock住record的ID,并且在處理完record之后再unlock,來保證處理的正確性。我們也可以使用DynamoDB的condition update來達到相同的目的,具體可以參見《關于在分布式環境中RVN和使用場景的介紹3》。
在這種邏輯下,后獲得鎖的thread將會發現它所持有的RVN已經不是合理的RVN了,所以它會拒絕處理它持有的request,并且向client匯報這一情況(比如可以throw exception)。而client可以重新從service獲得最新的RVN的record,再次嘗試根據最新的記錄進行更新。
問題擴展
在我們討論的解決方案里,我們期望service和client可以遵守共同的規則在一起工作,比如期望所有的client都可以基于獲取的RVN每次增加1。只有在這種情況下,我們的數據才能被維護正確。假如,我們的API是公開的API,也就是說client并不總是可信的。Client可能會破壞規則給RVN增加2或者更多來試圖非法獲取修改數據的規則。在這種情況下,我們可以給每一個record version生成一個UUID來代替RVN。Client必須提供當前version的UUID以獲取修改當前record的資格。在每次record被改變時都生成新的UUID。
參考文檔
《關于在分布式環境中RVN和使用場景的介紹1》
《關于在分布式環境中RVN和使用場景的介紹2》
《關于在分布式環境中RVN和使用場景的介紹3》