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 相关文章推荐
Windows中Redis安装配置流程并实现远程访问功能
Jun 07 Redis
你真的了解redis为什么要提供pipeline功能
Jun 22 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
Jedis操作Redis实现模拟验证码发送功能
Sep 25 Redis
Redis模仿手机验证码发送的实现示例
Nov 02 Redis
使用RedisTemplat实现简单的分布式锁
Nov 20 Redis
redis的list数据类型相关命令介绍及使用
Jan 18 Redis
Redis+Lua脚本实现计数器接口防刷功能(升级版)
Feb 12 Redis
 Redis 串行生成顺序编码的方法实现
Apr 03 Redis
Redis批量生成数据的实现
Jun 05 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之数组学习
2011/05/29 PHP
探寻PHP脚本不报错的原因
2014/06/12 PHP
PHP的魔术常量__METHOD__简介
2014/07/08 PHP
使用PHPMailer实现邮件发送代码分享
2014/10/23 PHP
PHP Cookie学习笔记
2016/08/23 PHP
利用PHP如何统计Nginx日志的User Agent数据
2019/03/06 PHP
php web环境和命令行环境下查找php.ini的位置
2019/07/17 PHP
Laravel实现通过blade模板引擎渲染视图
2019/10/25 PHP
用js判断用户浏览器是否是XP SP2的IE6
2007/03/08 Javascript
XRegExp 0.2: Now With Named Capture
2007/11/30 Javascript
Jquery中Ajax 缓存带来的影响的解决方法
2011/05/19 Javascript
JavaScript对象的property属性详解
2014/04/01 Javascript
javascript实现避免页面按钮重复提交
2015/01/08 Javascript
JQuery zClip插件实现复制页面内容到剪贴板
2015/11/02 Javascript
jQuery查看选中对象HTML代码的方法
2016/06/17 Javascript
Angularjs 实现分页功能及示例代码
2016/09/14 Javascript
JS闭包与延迟求值用法示例
2016/12/22 Javascript
微信小程序左滑删除效果的实现代码
2017/02/20 Javascript
关于在mongoose中填充外键的方法详解
2017/08/14 Javascript
jquery操作ul的一些操作笔记整理(干货)
2017/08/31 jQuery
用React-Native+Mobx做一个迷你水果商城APP(附源码)
2017/12/25 Javascript
javaScript动态添加Li元素的实例
2018/02/24 Javascript
Javascript查看大图功能代码实现
2020/05/07 Javascript
ssm+vue前后端分离框架整合实现(附源码)
2020/07/08 Javascript
[02:53]DOTA2亚洲邀请赛 NewBee战队巡礼
2015/02/03 DOTA
Python爬虫实现简单的爬取有道翻译功能示例
2018/07/13 Python
Opencv+Python 色彩通道拆分及合并的示例
2018/12/08 Python
Python数据可视化:饼状图的实例讲解
2019/12/07 Python
记录模型训练时loss值的变化情况
2020/06/16 Python
CSS3的transition和animation的用法实例介绍
2014/08/20 HTML / CSS
Smallable英国家庭概念店:设计师童装及家居装饰
2017/07/05 全球购物
英国时尚优质的女装:Hope Fashion
2018/08/14 全球购物
干部竞争上岗演讲稿
2014/09/11 职场文书
自我推荐信格式模板
2015/03/24 职场文书
2015年老干部工作总结
2015/04/23 职场文书
办公室主任岗位竞聘书
2015/09/15 职场文书