首頁 > 軟體

Redis如何使用樂觀鎖(CAS)保證資料一致性

2022-03-25 16:00:27

場景

在 Redis 中經常會存在這麼一種情況,讀取某一個 key 的值,做一些業務邏輯處理,然後根據讀取到的值來計算出一個新的值,重新 set 進去。

如果使用者端 A 剛讀取到 key 值,緊接著使用者端 B 就修改這個 key 的值,那麼就會存在並行安全的問題。

問題模擬

假設 Redis Server 有個鍵名為 test 的key,裡面存放的是一個 json 陣列 [1, 2, 3]。

下面讓我們模擬一下,使用者端 A 與 使用者端 B 同時存取修改的情況,程式碼如下:

使用者端 A:

class RedisClientA(username: String, password: String, host: String, port: Int) {
    val jedis: Jedis

    init {
        val pool = JedisPool(JedisPoolConfig(), host, port)
        jedis = pool.resource
        jedis.auth(username, password)
    }

    fun update(key: String) {
        val idStr = jedis.get(key)
        val idList = Json.decodeFromString<MutableList<Int>>(idStr)

        // 等待2秒,模擬業務
        TimeUnit.SECONDS.sleep(2L)

        idList.add(4)
        println("new id list: $idList")

        jedis.set(key, Json.encodeToString(idList))
    }

    fun getVal(key: String): String? {
        return jedis.get(key)
    }
}

fun main() {
    val key = "test"
    val redisClientA = RedisClientA("default", "123456", "127.0.0.1", 6379)
    redisClientA.update(key)
    val res = redisClientA.getVal(key)
    println("res: $res")
}

使用者端 B:

class RedisClientB(username: String, password: String, host: String, port: Int) {
    val jedis: Jedis

    init {
        val pool = JedisPool(JedisPoolConfig(), host, port)
        jedis = pool.resource
        jedis.auth(username, password)
    }

    fun update(key: String) {
        val idStr = jedis.get(key)
        val idList = Json.decodeFromString<MutableList<Int>>(idStr)

        idList.add(5)
        println("new id list: $idList")

        jedis.set(key, Json.encodeToString(idList))
    }

    fun getVal(key: String): String? {
        return jedis.get(key)
    }
}

fun main() {
    val key = "test"
    val redisClientB = RedisClientB("default", "123456", "127.0.0.1", 6379)
    redisClientB.update(key)
    val res = redisClientB.getVal(key)
    println("res: $res")
}

使用者端 A 阻塞了 2 秒,用來模擬耗時業務邏輯的處理。正在處理的時候,使用者端 B 存取了 “test”,並增加了 id:5。

在使用者端 A 耗時業務邏輯處理完的時候,增加了 id:4,並且會覆蓋掉 id:5。

最終“test” 裡的內容最終如下:

CAS 來保證資料一致性

WATCH 命令可以為 Redis 事務提供 check-and-set(CAS)行為。被 WATCH 的鍵會被監視,並會發覺這些鍵是否被改動過了。如果有至少一個被監視的建在 EXEC 執行之前被修改了,那麼整個事務都會被取消,EXEC 返回空(Null replay)來表示事務執行失敗。我們只需要重複操作,希望在這個時間段內不會有新的競爭。這種形式的鎖被稱作樂觀鎖,它是一種非常強大的鎖機制。

那麼 CAS 的方式如何實現呢?我們只需要把 RedisClientA 的 update() 方法中的程式碼修改如下:

fun update(key: String) {
    var flag = true

    while (flag) {
        jedis.watch(key)

        val idStr = jedis.get(key)
        val idList = Json.decodeFromString<MutableList<Int>>(idStr)

        // 等待2秒,模擬業務
        TimeUnit.SECONDS.sleep(2L)

        val transaction = jedis.multi()
        idList.add(4)
        println("new id list: $idList")

        transaction.set(key, Json.encodeToString(idList))

        transaction.exec()?.let {
            flag = false
        }
    }

}

最終 “test” 的內容如下:

可見我們通過使用 WATCH 和 TRANACTION 命令,採用 CAS 樂觀鎖的方式實現了資料的一致性。

到此這篇關於Redis如何使用樂觀鎖(CAS)保證資料一致性的文章就介紹到這了,更多相關Redis 樂觀鎖保證資料一致性內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com