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 相关文章推荐
SpringBoot 集成Redis 过程
Jun 02 Redis
解析高可用Redis服务架构分析与搭建方案
Jun 20 Redis
压缩Redis里的字符串大对象操作
Jun 23 Redis
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
Redis三种集群模式详解
Oct 05 Redis
SpringBoot集成Redis的思路详解
Oct 16 Redis
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 Redis
解决 Redis 秒杀超卖场景的高并发
Apr 12 Redis
Grafana可视化监控系统结合SpringBoot使用
Apr 19 Redis
redis lua限流算法实现示例
Jul 15 Redis
Redis过期数据是否会被立马删除
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
咖啡知识大全
2021/03/03 新手入门
php防攻击代码升级版
2010/12/29 PHP
PHP 伪静态技术原理以及突破原理实现介绍
2013/07/12 PHP
php中array_column函数简单实现方法
2016/07/11 PHP
功能强大的PHP发邮件类
2016/08/29 PHP
innerhtml用法 innertext用法 以及innerHTML与innertext的区别
2009/10/26 Javascript
精通Javascript系列之数值计算
2011/06/07 Javascript
Javascript面向对象设计一 工厂模式
2011/12/20 Javascript
jquery事件机制扩展插件 jquery鼠标右键事件
2011/12/21 Javascript
json属性名为什么要双引号(个人猜测)
2014/07/31 Javascript
js 动态修改css文件的方法
2014/08/05 Javascript
js自动生成的元素与页面原有元素发生堆叠的解决方法
2014/09/04 Javascript
avalon js实现仿google plus图片多张拖动排序附源码下载
2015/09/24 Javascript
深入浅析JavaScript中的作用域和上下文
2016/03/26 Javascript
浅谈JavaScript中小数和大整数的精度丢失
2016/05/31 Javascript
JavaScript实现url参数转成json形式
2016/09/25 Javascript
使用Fullpage插件快速开发整屏翻页的页面
2017/09/13 Javascript
jQuery实现定时隐藏对话框的方法分析
2018/02/12 jQuery
layui实现根据table数据判断按钮显示情况的方法
2019/09/26 Javascript
JavaScript实现联动菜单特效
2020/01/07 Javascript
Python使用Scrapy爬取妹子图
2015/05/28 Python
python 读取竖线分隔符的文本方法
2018/12/20 Python
对Python3 序列解包详解
2019/02/16 Python
Python计算一个点到所有点的欧式距离实现方法
2019/07/04 Python
基于python实现音乐播放器代码实例
2020/07/01 Python
Python 实现一个计时器
2020/07/28 Python
使用scrapy ImagesPipeline爬取图片资源的示例代码
2020/09/28 Python
使用CSS3实现字体颜色渐变的实现
2020/08/10 HTML / CSS
RealTek面试题
2016/06/28 面试题
程序员岗位职责
2013/11/11 职场文书
挂职思想汇报
2013/12/31 职场文书
高一学生期末评语
2014/04/25 职场文书
住宅质量保证书
2014/04/29 职场文书
我们的节日元宵活动方案
2014/08/23 职场文书
高中政治教学反思
2016/02/23 职场文书
Python捕获、播放和保存摄像头视频并提高视频清晰度和对比度
2022/04/14 Python