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
详解缓存穿透击穿雪崩解决方案
May 28 Redis
Redis读写分离搭建的完整步骤
Sep 14 Redis
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 Redis
浅谈Redis跟MySQL的双写问题解决方案
Feb 24 Redis
Redis中有序集合的内部实现方式的详细介绍
Mar 16 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
Redis数据同步之redis shake的实现方法
Apr 21 Redis
Redis过期数据是否会被立马删除
Jul 23 Redis
基于redis+lua进行限流的方法
Jul 23 Redis
python中使用redis用法详解
Dec 24 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
什么是调频(FM)、调幅(AM)、短波(SW)、长波(LW)
2021/03/01 无线电
PHP中使用CURL伪造来路抓取页面或文件
2011/05/04 PHP
PHP中session变量的销毁
2014/02/27 PHP
php 使用GD库为页面增加水印示例代码
2014/03/24 PHP
thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法分析
2019/08/05 PHP
php7性能提升的原因详解
2019/10/13 PHP
JavaScript 基础问答三
2008/12/03 Javascript
精选的10款用于构建良好易用性网站的jQuery插件
2011/01/23 Javascript
JavaScript 对任意元素,自定义右键菜单的实现方法
2013/05/08 Javascript
jquery批量控制form禁用的代码
2013/08/06 Javascript
用jquery的方法制作一个简单的导航栏
2014/06/23 Javascript
jQuery中animate()方法用法实例
2014/12/24 Javascript
JavaScript里实用的原生API汇总
2015/05/14 Javascript
javascript弹性运动效果简单实现方法
2016/01/08 Javascript
基于vuejs+webpack的日期选择插件
2020/05/21 Javascript
原生js实现键盘控制div移动且解决停顿问题
2016/12/05 Javascript
jQuery插件HighCharts绘制的基本折线图效果示例【附demo源码下载】
2017/03/07 Javascript
Node实战之不同环境下配置文件使用教程
2018/01/02 Javascript
nodejs提示:cross-device link not permitted, rename错误的解决方法
2019/06/10 NodeJs
详解package.json版本号规则
2019/08/01 Javascript
vue实现带过渡效果的下拉菜单功能
2020/02/19 Javascript
基于JS实现计算24点算法代码实例解析
2020/07/23 Javascript
python制作企业邮箱的爆破脚本
2016/10/05 Python
python3中int(整型)的使用教程
2017/03/23 Python
python获取服务器响应cookie的实例
2018/12/28 Python
Python实现的栈、队列、文件目录遍历操作示例
2019/05/06 Python
Django项目之Elasticsearch搜索引擎的实例
2019/08/21 Python
python中property属性的介绍及其应用详解
2019/08/29 Python
tensorflow实现对张量数据的切片操作方式
2020/01/19 Python
python3 xpath和requests应用详解
2020/03/06 Python
使用SVG实现提示框功能的示例代码
2020/06/05 HTML / CSS
二年级语文教学反思
2014/02/02 职场文书
2015欢度元旦标语口号
2014/12/09 职场文书
会计岗位职责
2015/02/03 职场文书
入党自传范文2015
2015/06/26 职场文书
大学生志愿者心得体会
2016/01/15 职场文书