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存储数据类型及存取值方法
May 08 Redis
redis内存空间效率问题的深入探究
May 17 Redis
5分钟教你docker安装启动redis全教程(全新方式)
May 29 Redis
Redis主从配置和底层实现原理解析(实战记录)
Jun 30 Redis
Redis性能监控的实现
Jul 09 Redis
基于Redis的List实现特价商品列表功能
Aug 30 Redis
关于SpringBoot 使用 Redis 分布式锁解决并发问题
Nov 17 Redis
SpringBoot整合Redis入门之缓存数据的方法
Nov 17 Redis
Redis实现一个账号只能登录一个设备
Apr 19 Redis
Redis 限流器
May 15 Redis
Redis Lua脚本实现ip限流示例
Jul 15 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
php中判断字符串是否全是中文或含有中文的实现代码
2011/09/16 PHP
zf框架的registry(注册表)使用示例
2014/03/13 PHP
thinkPHP下ueditor的使用方法详解
2015/12/26 PHP
PHP+AJAX 投票器功能
2017/11/11 PHP
PHP大文件分块上传功能实例详解
2019/07/22 PHP
php swoole多进程/多线程用法示例【基于php7nts版】
2019/08/12 PHP
WordPress JQuery处理沙发头像
2009/06/22 Javascript
基于jquery自定义图片热区效果
2012/07/21 Javascript
仿当当网淘宝网等主流电子商务网站商品分类导航菜单
2013/09/25 Javascript
对比分析json及XML
2014/11/28 Javascript
javascript实现删除前弹出确认框
2015/06/04 Javascript
AngularJS 简单应用实例
2016/07/28 Javascript
AngularJS实现路由实例
2017/02/12 Javascript
微信小程序 MD5的方法详解及实例代码
2017/03/10 Javascript
node作为中间服务层如何发送请求(发送请求的实现方法详解)
2018/01/02 Javascript
Vue 全家桶实现移动端酷狗音乐功能
2018/11/16 Javascript
Vue触发式全局组件构建的方法
2018/11/28 Javascript
JavaScript迭代器的含义及用法
2019/06/21 Javascript
JavaScript获取时区实现过程解析
2020/09/24 Javascript
Python简单获取自身外网IP的方法
2016/09/18 Python
python实现给微信公众号发送消息的方法
2017/06/30 Python
spyder常用快捷键(分享)
2017/07/19 Python
python PrettyTable模块的安装与简单应用
2019/01/11 Python
python将视频转换为全字符视频
2019/04/26 Python
Python3.5集合及其常见运算实例详解
2019/05/01 Python
numpy中的ndarray方法和属性详解
2019/05/27 Python
python TF-IDF算法实现文本关键词提取
2019/05/29 Python
nginx+uwsgi+django环境搭建的方法步骤
2019/11/25 Python
python中count函数简单的实例讲解
2020/02/06 Python
python用tkinter实现一个gui的翻译工具
2020/10/26 Python
教育专业个人求职信
2013/12/02 职场文书
人力资源专员岗位职责
2014/01/30 职场文书
女子职高个人自荐书
2014/02/01 职场文书
认识深刻的检讨书
2014/02/16 职场文书
考试作弊万能检讨书
2014/10/19 职场文书
幼儿园园长新年寄语2015
2014/12/08 职场文书