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
redis 限制内存使用大小的实现
May 08 Redis
redis内存空间效率问题的深入探究
May 17 Redis
详解Redis瘦身指南
May 26 Redis
Django使用redis配置缓存的方法
Jun 01 Redis
详解Redis基本命令与使用场景
Jun 01 Redis
压缩Redis里的字符串大对象操作
Jun 23 Redis
Redis 彻底禁用RDB持久化操作
Jul 09 Redis
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
Feb 12 Redis
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Mar 16 Redis
 Redis 串行生成顺序编码的方法实现
Apr 03 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 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
destoon常用的安全设置概述
2014/06/21 PHP
Thinkphp关闭缓存的方法
2015/06/26 PHP
PHP上传图片时判断上传文件是否为可用图片的方法
2016/10/20 PHP
php实现简单的权限管理的示例代码
2017/08/25 PHP
JQuery获取文本框中字符长度的代码
2011/09/29 Javascript
基于jquery的可多选的下拉列表框
2012/07/20 Javascript
js修改table中Td的值(定义td的单击事件)
2013/01/10 Javascript
Jquery动态添加输入框的方法
2015/05/29 Javascript
jQuery实现两款有动画功能的导航菜单代码
2015/09/16 Javascript
javascript实现无缝上下滚动特效
2015/12/16 Javascript
Vue.js开发环境搭建
2016/11/10 Javascript
Vue + Webpack + Vue-loader学习教程之功能介绍篇
2017/03/14 Javascript
基于JavaScript实现焦点图轮播效果
2017/03/27 Javascript
Vuex简单入门
2017/04/19 Javascript
jQuery实现对网页节点的增删改查功能示例
2017/09/18 jQuery
在Vue项目中使用snapshot测试的具体使用
2019/04/16 Javascript
nodejs搭建本地服务器并访问文件操作示例
2019/05/11 NodeJs
解决vue 使用axios.all()方法发起多个请求控制台报错的问题
2020/11/09 Javascript
nodejs处理tcp连接的核心流程
2021/02/26 NodeJs
python基础教程之类class定义使用方法
2014/02/20 Python
基于并发服务器几种实现方法(总结)
2017/12/29 Python
Python+OpenCV让电脑帮你玩微信跳一跳
2018/01/04 Python
Python针对给定字符串求解所有子序列是否为回文序列的方法
2018/04/21 Python
基于随机梯度下降的矩阵分解推荐算法(python)
2018/08/31 Python
Python爬虫实战之12306抢票开源
2019/01/24 Python
django框架model orM使用字典作为参数,保存数据的方法分析
2019/06/24 Python
最简单的matplotlib安装教程(小白)
2020/07/28 Python
python调用摄像头的示例代码
2020/09/28 Python
英国派对礼服和连衣裙购物网站:TFNC London
2018/07/07 全球购物
GANT葡萄牙官方商店:拥有美国运动服传统的生活方式品牌
2018/10/18 全球购物
全球烹饪课程的领先预订平台:Cookly
2020/01/28 全球购物
ajax是什么及其工作原理
2012/02/08 面试题
护理自我鉴定范文
2013/10/06 职场文书
大学旷课检讨书
2014/01/28 职场文书
2019年销售人员的职业生涯规划书
2019/03/25 职场文书
CSS使用伪类控制边框长度的方法
2022/01/18 HTML / CSS