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 Cluster 字段模糊匹配及删除
May 27 Redis
了解Redis常见应用场景
Jun 23 Redis
Redis集群的关闭与重启操作
Jul 07 Redis
在redisCluster中模糊获取key方式
Jul 09 Redis
解析redis hash应用场景和常用命令
Aug 04 Redis
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 Redis
为什么RedisCluster设计成16384个槽
Sep 25 Redis
redis数据一致性的实现示例
Mar 18 Redis
使用Redis实现点赞取消点赞的详细代码
Mar 20 Redis
redis复制有可能碰到的问题汇总
Apr 03 Redis
Redis实战高并发之扣减库存项目
Apr 14 Redis
一文教你快速生成MySQL数据库关系图
Jun 28 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
新版mysql+apache+php Linux安装指南
2006/10/09 PHP
利用phpexcel把excel导入数据库和数据库导出excel实现
2014/01/09 PHP
ThinkPHP登录功能的实现方法
2014/08/20 PHP
php用户名的密码加密更安全的方法
2019/06/21 PHP
img的onload的另类用法
2008/01/10 Javascript
javascript Array.sort() 跨浏览器下需要考虑的问题
2009/12/07 Javascript
jquery 弹出登录窗口实现代码
2009/12/24 Javascript
jQuery常用操作方法及常用函数总结
2014/06/19 Javascript
JS实现为表格动态添加标题的方法
2015/03/31 Javascript
jquery实现Li滚动时滚动条自动添加样式的方法
2015/08/10 Javascript
详解JavaScript对象和数组
2015/12/03 Javascript
JavaScript获取客户端IP的方法(新方法)
2016/03/11 Javascript
JavaScript编程中实现对象封装特性的实例讲解
2016/06/24 Javascript
jQuery中map函数的两种方式
2017/04/07 jQuery
Node.js中看JavaScript的引用
2017/04/22 Javascript
小程序ios音频播放没声音问题的解决
2018/07/11 Javascript
Vue自定义多选组件使用详解
2020/09/08 Javascript
Python HTTP客户端自定义Cookie实现实例
2017/04/28 Python
Python基于scapy实现修改IP发送请求的方法示例
2017/07/08 Python
python中模块的__all__属性详解
2017/10/26 Python
利用Opencv中Houghline方法实现直线检测
2018/02/11 Python
Python单元测试实例详解
2018/05/25 Python
python实现梯度下降和逻辑回归
2020/03/24 Python
Django自带用户认证系统使用方法解析
2020/11/12 Python
详解pycharm连接远程linux服务器的虚拟环境的方法
2020/11/13 Python
CSS3实现滚动条动画效果代码分享
2016/08/03 HTML / CSS
京东全球售:直邮香港,澳门,台湾,美国,澳大利亚等地区
2017/09/24 全球购物
英国定做窗帘和纺织品面料一站式商店:Dekoria
2018/08/29 全球购物
PyQt QMainWindow的使用示例
2021/03/24 Python
医院护士求职自荐信格式
2013/09/21 职场文书
大学老师推荐信
2014/02/25 职场文书
2015年仓库管理员工作总结
2015/04/21 职场文书
爱护环境建议书
2015/09/14 职场文书
Nginx的基本概念和原理
2022/03/21 Servers
netty 实现tomcat的示例代码
2022/06/05 Servers
Nginx安装配置详解
2022/06/25 Servers