Redis如何使用乐观锁(CAS)保证数据一致性


Posted in Redis onMarch 25, 2022

场景

在 Redis 中经常会存在这么一种情况,读取某一个 key 的值,做一些业务逻辑处理,然后根据读取到的值来计算出一个新的值,重新 set 进去。

如果客户端 A 刚读取到 key 值,紧接着客户端 B 就修改这个 key 的值,那么就会存在并发安全的问题。

问题模拟

假设 Redis Server 有个键名为 test 的key,里面存放的是一个 json 数组 [1, 2, 3]。

Redis如何使用乐观锁(CAS)保证数据一致性

下面让我们模拟一下,客户端 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” 里的内容最终如下:

Redis如何使用乐观锁(CAS)保证数据一致性

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” 的内容如下:

Redis如何使用乐观锁(CAS)保证数据一致性

可见我们通过使用 WATCH 和 TRANACTION 命令,采用 CAS 乐观锁的方式实现了数据的一致性。

到此这篇关于Redis如何使用乐观锁(CAS)保证数据一致性的文章就介绍到这了,更多相关Redis 乐观锁保证数据一致性内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
Redis遍历所有key的两个命令(KEYS 和 SCAN)
Apr 12 Redis
基于Redis过期事件实现订单超时取消
May 08 Redis
Java Socket实现Redis客户端的详细说明
May 26 Redis
Redis集群的关闭与重启操作
Jul 07 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
在项目中使用redis做缓存的一些思路
Sep 14 Redis
Redis命令处理过程源码解析
Feb 12 Redis
Redis 操作多个数据库的配置的方法实现
Mar 23 Redis
Redis分布式锁的7种实现
Apr 01 Redis
Redis批量生成数据的实现
Jun 05 Redis
基于redis+lua进行限流的方法
Jul 23 Redis
Redis 操作多个数据库的配置的方法实现
Mar 23 #Redis
Redis安装使用RedisJSON模块的方法
Mar 23 #Redis
解决redis批量删除key值的问题
Mar 23 #Redis
源码分析Redis中 set 和 sorted set 的使用方法
Redis监控工具RedisInsight安装与使用
在Centos 8.0中安装Redis服务器的教程详解
redis数据结构之压缩列表
Mar 21 #Redis
You might like
解析PayPal支付接口的PHP开发方式
2010/11/28 PHP
几种有用的变型 PHP中循环语句的用法介绍
2012/01/30 PHP
字符串长度函数strlen和mb_strlen的区别示例介绍
2014/09/09 PHP
PHP中让curl支持sock5的代码实例
2015/01/21 PHP
PHP中的事务使用实例
2015/05/26 PHP
php基于单例模式封装mysql类完整实例
2016/10/18 PHP
JS+PHP实现用户输入数字后显示最大的值及所在位置
2017/06/19 PHP
PHP根据key删除数组中指定的元素
2019/02/28 PHP
JavaScript中的property和attribute介绍
2011/12/26 Javascript
jquery 延迟执行实例介绍
2013/08/20 Javascript
jquery使用jxl插件导出excel示例
2014/04/14 Javascript
javascript中parseInt()函数的定义和用法分析
2014/12/20 Javascript
jQuery实现信息提示框(带有圆角框与动画)效果
2015/08/07 Javascript
javascript跨域的方法汇总
2015/10/23 Javascript
jQuery3.0中的buildFragment私有函数详解
2016/08/16 Javascript
轻松掌握JavaScript享元模式
2016/08/27 Javascript
Bootstrap入门教程一Hello Bootstrap初识
2017/03/02 Javascript
jQuery修改DOM结构_动力节点Java学院整理
2017/07/05 jQuery
vue webpack实用技巧总结
2018/04/24 Javascript
vue项目实现github在线预览功能
2018/06/20 Javascript
element-ui表格数据转换的示例代码
2018/08/24 Javascript
Vue 3.0双向绑定原理的实现方法
2019/10/23 Javascript
Openlayers实现地图全屏显示
2020/09/28 Javascript
布同 Python中文问题解决方法(总结了多位前人经验,初学者必看)
2011/03/13 Python
复习Python中的字符串知识点
2015/04/14 Python
用Python批量把文件复制到另一个文件夹的实现方法
2019/08/16 Python
Python使用matplotlib 画矩形的三种方式分析
2019/10/31 Python
python3 requests库实现多图片爬取教程
2019/12/18 Python
Python Print实现在输出中插入变量的例子
2019/12/25 Python
python实现3D地图可视化
2020/03/25 Python
html5的新玩法——语音搜索
2013/01/03 HTML / CSS
毕业生自我鉴定
2013/12/04 职场文书
反腐倡廉警示教育活动心得体会
2014/09/04 职场文书
80后婚前协议书范本
2014/10/24 职场文书
解决Windows Server2012 R2 无法安装 .NET Framework 3.5
2022/04/29 Servers
oracle delete误删除表数据后如何恢复
2022/06/28 Oracle