Home  >  Article  >  Database  >  How Redis uses optimistic locking to ensure data consistency

How Redis uses optimistic locking to ensure data consistency

WBOY
WBOYforward
2023-06-02 16:52:42686browse

Scenario

In Redis, there is often a situation where the value of a certain key is read, some business logic processing is performed, and then a new value is calculated based on the read value. Set it in again.

If client A has just read the key value, and then client B modifies the key value, then there will be a concurrency security issue.

Problem simulation

Assume that Redis Server has a key named test, which stores a json array [1, 2, 3].

How Redis uses optimistic locking to ensure data consistency

Let us simulate the situation where client A and client B access modifications at the same time. The code is as follows:

Client 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")
}

Client 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")
}

Client A blocked for 2 seconds to simulate the processing of time-consuming business logic. Client B accessed "test" during processing and added id:5.

When client A finishes processing the time-consuming business logic, id:4 is added and id:5 will be overwritten.

The final content in "test" is as follows:

How Redis uses optimistic locking to ensure data consistency

CAS to ensure data consistency

Redis' WATCH command Check and Set (CAS) behavior is provided for use with Redis transactions. WATCHed keys will be monitored and it will be discovered whether they have been modified. If at least one monitored object is modified before EXEC is executed, the entire transaction will be canceled and EXEC will return Null replay to indicate transaction execution failure. We just need to repeat the operation and hope that there will be no new competition during this time period. This form of locking is called optimistic locking, and it is a very powerful locking mechanism.

So how to implement CAS? We only need to modify the code in the update() method of RedisClientA as follows:

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
        }
    }

}

The final content of "test" is as follows:

How Redis uses optimistic locking to ensure data consistency

It can be seen that we use The WATCH and TRANACTION commands use CAS optimistic locking to achieve data consistency.

The above is the detailed content of How Redis uses optimistic locking to ensure data consistency. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete