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如何一键部署脚本
Apr 12 Redis
redis配置文件中常用配置详解
Apr 14 Redis
Redis6.0搭建集群Redis-cluster的方法
May 08 Redis
解析Redis Cluster原理
Jun 21 Redis
Window server中安装Redis的超详细教程
Nov 17 Redis
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Mar 16 Redis
Redis集群节点通信过程/原理流程分析
Mar 18 Redis
Redis 操作多个数据库的配置的方法实现
Mar 23 Redis
Redis如何使用乐观锁(CAS)保证数据一致性
Mar 25 Redis
Redis分布式锁的7种实现
Apr 01 Redis
Redis超详细讲解高可用主从复制基础与哨兵模式方案
Apr 07 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中在数据库中保存Checkbox数据(1)
2006/10/09 PHP
PHP中替换换行符的几种方法小结
2012/10/15 PHP
解析zend studio中直接导入svn中的项目的方法步骤
2013/06/21 PHP
PHP实现数字补零功能的2个函数介绍
2014/05/12 PHP
学习thinkphp5.0验证类使用方法
2017/11/16 PHP
PHP实现对图片的反色处理功能【测试可用】
2018/02/01 PHP
php递归函数怎么用才有效
2018/02/24 PHP
JS 自定义带默认值的函数
2011/07/21 Javascript
javascript轻松实现当鼠标移开时已弹出子菜单自动消失
2013/12/29 Javascript
JQuery异步获取返回值中文乱码的解决方法
2015/01/29 Javascript
js带前后翻页的图片切换效果代码分享
2015/09/08 Javascript
javascript实现input file上传图片预览效果
2015/12/31 Javascript
jQuery 3.0 的变化及使用方法
2016/02/01 Javascript
老生常谈遮罩层 滚动条的问题
2016/04/29 Javascript
vue2.0之多页面的开发的示例
2018/01/30 Javascript
Javascript格式化并高亮xml字符串的方法及注意事项
2018/08/13 Javascript
jQuery实现简单三级联动效果
2020/09/05 jQuery
Vue使用Ref跨层级获取组件的步骤
2021/01/25 Vue.js
在Python的Django框架中simple-todo工具的简单使用
2015/05/30 Python
详解python里使用正则表达式的分组命名方式
2017/10/24 Python
Python使用Dijkstra算法实现求解图中最短路径距离问题详解
2018/05/16 Python
Python中修改字符串的四种方法
2018/11/02 Python
Python minidom模块用法示例【DOM写入和解析XML】
2019/03/25 Python
Python学习笔记之字符串和字符串方法实例详解
2019/08/22 Python
python 五子棋如何获得鼠标点击坐标
2019/11/04 Python
python dataframe NaN处理方式
2019/12/26 Python
基于python实现地址和经纬度转换
2020/05/19 Python
使用已经得到的keras模型识别自己手写的数字方式
2020/06/29 Python
家得宝加拿大家装网上商店:The Home Depot加拿大
2016/08/27 全球购物
极度干燥澳大利亚官方网站:Superdry澳大利亚
2019/03/28 全球购物
美国Max仓库:Max Warehouse
2020/05/31 全球购物
少先队入队活动方案
2014/02/08 职场文书
2014年学校党建工作总结
2014/11/11 职场文书
2016年寒假学习心得体会
2015/10/09 职场文书
一个家长教育孩子的心得体会
2016/01/15 职场文书
CSS中理解层叠性及权重如何分配
2022/12/24 HTML / CSS