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 13 Redis
深入浅析Redis 集群伸缩原理
May 15 Redis
详解Redis主从复制实践
May 19 Redis
聊一聊Redis与MySQL双写一致性如何保证
Jun 26 Redis
redis requires ruby version2.2.2的解决方案
Jul 15 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Mar 16 Redis
Redis基本数据类型Set常用操作命令
Jun 01 Redis
Redis基本数据类型List常用操作命令
Jun 01 Redis
Redis sentinel哨兵集群的实现步骤
Jul 15 Redis
redis protocol通信协议及使用详解
Jul 15 Redis
redis lua限流算法实现示例
Jul 15 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中利用wsdl创建标准webservice的实现代码
2011/12/07 PHP
php数据结构与算法(PHP描述) 查找与二分法查找
2012/06/21 PHP
php+ajax 文件上传代码实例
2019/03/18 PHP
php面向对象基础详解【星际争霸游戏案例】
2020/01/23 PHP
复制小说文本时出现的随机乱码的去除方法
2010/09/07 Javascript
详细介绍8款超实用JavaScript框架
2013/10/25 Javascript
javascript抖动元素的小例子
2013/10/28 Javascript
通过$(this)使用jQuery包装后的方法或属性
2014/05/18 Javascript
jQuery通过Ajax返回JSON数据
2015/04/28 Javascript
JS操作XML实例总结(加载与解析XML文件、字符串)
2015/12/08 Javascript
JavaScript定义数组的三种方法(new Array(),new Array('x','y')
2016/10/04 Javascript
javascript判断元素存在和判断元素存在于实时的dom中的方法
2017/01/17 Javascript
vue.js实现条件渲染的实例代码
2017/06/22 Javascript
帝国cms首页列表页实现点赞功能
2017/10/30 Javascript
Bootstrap treeview实现动态加载数据并添加快捷搜索功能
2018/01/07 Javascript
JS实现图片转换成base64的各种应用场景实例分析
2018/06/22 Javascript
微信公众平台 发送模板消息(Java接口开发)
2019/04/17 Javascript
微信小程序实现导航栏和内容上下联动功能代码
2020/06/29 Javascript
vue axios请求成功却进入catch的原因分析
2020/09/08 Javascript
[07:57]2018DOTA2国际邀请赛寻真——PSG.LGD凤凰浴火
2018/08/12 DOTA
Python入门之modf()方法的使用
2015/05/15 Python
详解在Python的Django框架中创建模板库的方法
2015/07/20 Python
Python实现的计数排序算法示例
2017/11/29 Python
python写入并获取剪切板内容的实例
2018/05/31 Python
用python打印1~20的整数实例讲解
2019/07/01 Python
Python tkinter 下拉日历控件代码
2020/03/04 Python
python实现控制台输出彩色字体
2020/04/05 Python
Python新手学习raise用法
2020/06/03 Python
html5 音乐播放器 audio 标签使用概述
2013/07/15 HTML / CSS
软件生产职位结构化面试主要考察要素及面试题库
2015/06/12 面试题
公司投资建议书
2014/05/16 职场文书
酒店爱岗敬业演讲稿
2014/09/02 职场文书
2014年公司工作总结
2014/11/22 职场文书
入党现实表现材料
2014/12/23 职场文书
JavaScript的Set数据结构详解
2022/02/18 Javascript
uniapp 微信小程序 自定义tabBar 导航
2022/04/22 Javascript