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.
Assume that Redis Server has a key named test, which stores a json array [1, 2, 3].
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:
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:
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!