全部產品
Search
文件中心

:基於TairString實現高效能樂觀鎖

更新時間:Jun 30, 2024

在大量請求並發訪問和更新Tair中儲存的共用資源時,必須有一種精準高效的並發控制機制來防止邏輯異常和資料錯誤,樂觀鎖就是這樣一種機制。比起原生Redis,雲原生記憶體資料庫Tair的TairString模組能協助您實現效能更高、成本更低的樂觀鎖。

並發與Last-Writer-Win

下圖展示了一個典型的並發導致資源競爭的情境:

  1. 初始狀態,string類型資料key_1的值為hello

  2. t1時刻,App1讀取到key_1的值hello

  3. t2時刻,App2讀取到key_1的值hello

  4. t3時刻,App1將key_1的值修改為world

  5. t4時刻,App2將key_1的值修改為universe

key_1的值是由最後一次寫入決定的,到了t4時刻,App1對key_1的認知已經出現了明顯的誤差,後續操作很可能出現問題,這就是所謂的Last-Writer-Win。要解決Last-Writer-Win問題,就需要保證訪問並更新String資料這個操作的原子性,或者說,將作為共用資源的String資料轉變為具有原子性的變數。您可以使用exString資料結構,構建高效能的樂觀鎖來達成這個效果。

使用TairString實現樂觀鎖

TairString,又稱為exString(extended string),是一種帶版本號碼的String類型資料結構。原生Redis String僅由Key和Value組成,而TairString不僅包含Key和Value,還攜帶了版本(Version),非常適合樂觀鎖等情境。更多介紹及命令詳情資訊請參見exString

說明

TairString與Redis原生String是兩種不同的資料結構,相關命令不可混用。

TairString有以下特性:

  • 每個Key都帶有Version,用於說明當前Value的版本。使用EXSET命令建立Key時,預設Version為1。

  • 使用EXGET命令查詢Key時,可以擷取到Value和Version兩個欄位。

  • 更新Value時,需要校正Version,如果校正失敗會返回異常資訊ERR update version is stale;校正成功則更新Value,且自動將Version加1。

  • 除了位元位(bit)相關操作外,TairString可以覆蓋原生Redis String的所有其它功能。

因為這些特性,使得TairString類型資料具有了鎖的機制,使用TairString實現樂觀鎖就非常方便了,樣本如下:

while(true){
    {value, version} = EXGET(key);      // 擷取Key的Value和Version
    value2 = update(...);               // 先將新Value儲存到Value2
    ret = EXSET(key, value2, version);  // 嘗試更新Key並將傳回值賦予變數ret
    if(ret == OK)
       break;                           // 如果傳回值為OK則更新成功,跳出迴圈
    else if (ret.contanis("version is stale"))     
       continue;                        // 如果傳回值包含"version is stale"則更新失敗,重複迴圈
}
說明
  • 刪除TairString後,即便以相同的key重新設定一條TairString,其Version也會是1,而不會繼承原TairString的Version。

  • 使用ABS選項可以跳過Version校正強行覆蓋Version並更新TairString,詳情參見EXSET

降低樂觀鎖的效能消耗

前文的範例程式碼中,如果在執行EXGET後該共用資源被其它用戶端更新了,當前用戶端會擷取到更新失敗的異常資訊,然後重複迴圈,再次執行EXGET擷取共用資源的當前Value和Version,直到更新成功,這樣每次迴圈都有兩次訪問Redis的IO操作。如果使用TairString的EXCAS命令,可以將兩次訪問減少為一次,極大地節約系統資源消耗,提升高並發情境下的服務效能。

EXCAS命令可以在調用時攜帶一個用於校正的Version值,如果校正成功則直接更新TairString的Value,如果校正失敗則返回三個欄位:

  • "ERR update version is stale"

  • Value

  • Version

更新失敗後可以直接得到TairString當前的版本,無需重新查詢,將原本每個迴圈需要進行兩次的訪問減少到一次。樣本如下:

while(true){
    {ret, value, version} = excas(key, new_value, old_version)    // 直接嘗試用CAS命令置換Value
    if(ret == OK)
       break;                                                     // 如果傳回值為OK則更新成功,跳出迴圈
    else (if ret.contanis("update version is stale"))             // 如果傳回值包含"update version is stale"則更新失敗,更新兩個變數
       update(value);
       old_version = version;
 }