Redis如何使用乐观锁(CAS)保证数据一致性

目录
  • 场景
  • 问题模拟
  • CAS 来保证数据一致性

场景

在 Redis 中经常会存在这么一种情况,读取某一个 key 的值,做一些业务逻辑处理,然后根据读取到的值来计算出一个新的值,重新 set 进去。

如果客户端 A 刚读取到 key 值,紧接着客户端 B 就修改这个 key 的值,那么就会存在并发安全的问题。

问题模拟

假设 Redis Server 有个键名为 test 的key,里面存放的是一个 json 数组 [1, 2, 3]。

下面让我们模拟一下,客户端 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” 里的内容最终如下:

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” 的内容如下:

可见我们通过使用 WATCH 和 TRANACTION 命令,采用 CAS 乐观锁的方式实现了数据的一致性。

到此这篇关于Redis如何使用乐观锁(CAS)保证数据一致性的文章就介绍到这了,更多相关Redis 乐观锁保证数据一致性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 面试常问:如何保证Redis缓存和数据库的数据一致性

    目录 一.一致性 1.强一致性 2.弱一致性 3.最终一致性 二.redis缓存和mysql数据库数据一致性解决 1.方案一:采用延时双删策略 2.方案二:一步更新缓存(基于订阅Binlog的同步机制) 首先,我们先来看看有哪几种一致性的情况呢? 一.一致性 1.强一致性 如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存.这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大. 2.弱一致性 这种一致性级别约束了系统在写入成

  • 高并发下Redis如何保持数据一致性(避免读后写)

    “读后写” 通常意义上我们说读后写是指针对同一个数据的先读后写,且写入的值依赖于读取的值. 关于这个定义要拆成两部分来看,一:同一个数据:二:写依赖于读.(记住这个拆分,后续会用到,记为定义一.定义二)只有当这两部分都成立时,读后写的问题才会出现. 在项目中,当面对较多的并发时,使用redis进行读后写操作,是非常容易出问题的,常常使得程序不具备鲁棒性,bug很难稳定复现(得到的值往往跟并发数有关). 举个栗子: 存在A.B两个进程,同时操作下面这段代码: $objRedis = new Red

  • redis数据一致性的实现示例

    前言 所谓的redis数据一致性即当进行修改或者保存.删除之后,redis中的数据也应该进行相应变化,不然用户再次查询的时候很可能查询出已经删除过的脏数据. 一.缓存一致的必要性 还是接上篇来说,我们已经解决了redis缓存穿透的问题(简单解决方案,可以再次优化),但是使用redis的时候缓存一致性的问题我们也需要着重考虑,例如:保存了一个新用户之后,就应该同时在redis缓存中也插入该条数据,更新了某条数据在缓存中也应该同步更新,而redis默认的做法是:当你不去设置的时候redis中存放的一

  • MySQL与Redis如何保证数据一致性详解

    前言 由于缓存的高并发和高性能已经在各种项目中被广泛使用,在读取缓存这方面基本都是一致的,大概都是按照下图的流程进行操作: 但是在更新缓存方面,是更新完数据库再更新缓存还是直接删除缓存呢?又或者是先删除缓存再更新数据库?在这一点上就值得探讨了. 一致性方案 在实际项目开发中需要保证数据库和缓存中的数据一致,否则人家充值了100块,不断刷新却还是显示0.01元,岂不是尴尬?从理论上来说,为缓存设置过期时间是最终保证数据一致性的解决方案,采用这种方案的话,所有的写操作都是以数据库为准,如果数据库写入

  • Redis如何使用乐观锁(CAS)保证数据一致性

    目录 场景 问题模拟 CAS 来保证数据一致性 场景 在 Redis 中经常会存在这么一种情况,读取某一个 key 的值,做一些业务逻辑处理,然后根据读取到的值来计算出一个新的值,重新 set 进去. 如果客户端 A 刚读取到 key 值,紧接着客户端 B 就修改这个 key 的值,那么就会存在并发安全的问题. 问题模拟 假设 Redis Server 有个键名为 test 的key,里面存放的是一个 json 数组 [1, 2, 3]. 下面让我们模拟一下,客户端 A 与 客户端 B 同时访问

  • redis中事务机制及乐观锁的实现

    Redis事务机制 在MySQL等其他数据库中,事务表示的是一组动作,这组动作要么全部执行,要么全部不执行. Redis目前对事物的支持相对简单.Redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他的client命令.当一个client在一个链接中发出multi命令时,这个链接会进入一个事务上下文,该连接后续的命令不会立即执行,而是先放到一个队列中,当执行exec命令时,redis会顺序的执行队列中的所有命令. Multi 开启事务: 127.0.0.1:637

  • Redis 缓存淘汰策略和事务实现乐观锁详情

    目录 缓存淘汰策略 标题LRU原理 标题Redis缓存淘汰策略 设置最大缓存 淘汰策略 Redis事务 Redis事务介绍 MULTI EXEC DISCARD WATCH Redis 不支持事务回滚(为什么呢) Redis乐观锁 Redis乐观锁实现秒杀 缓存淘汰策略 标题LRU原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”. 最常见的实现是使用一个链表保存缓存数据,

  • Java多线程 乐观锁和CAS机制详细

    目录 一.悲观锁和乐观锁 1.悲观锁 2.乐观锁 二.CAS机制 一.悲观锁和乐观锁 1.悲观锁 悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作.synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁. 特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对

  • MySQL中的乐观锁,悲观锁和MVCC全面解析

    前言 在数据库的实际使用过程中,我们常常会遇到不希望数据被同时写或者读的情景,例如秒杀场景下,两个请求同时读到系统还有库存1个,然后又先后把库存更新为0,这时候就会出现超卖的情况,这时候货物的实际库存和我们的记录就会对应不上了. 为了解决这种资源竞争导致的数据不一致等问题,我们需要有一种机制来进行保证数据的正确访问和修改,而在数据库中,这种机制就是数据库的并发控制.其中乐观并发控制,悲观并发控制和多版本并发控制是数据库并发控制主要采用的技术手段. 悲观并发控制 本质 维基百科:在关系数据库管理系

  • Java并发问题之乐观锁与悲观锁

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.再比如Java里面的同步原语synchronized关键字的实现也是悲观锁. 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版

  • PHP+redis实现的悲观锁机制示例

    本文实例讲述了PHP+redis实现的悲观锁.分享给大家供大家参考,具体如下: 锁机制 通常使用的锁分为乐观锁,悲观锁这两种,简单介绍下这两种锁,作为本文的背景知识,对这类知识已经有足够了解的同学可以跳过这部分. 乐观锁 先来看下百度百科上的解释:大多是基于数据版本( Version )记录机制实现.何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 "version" 字段来实现.读取出数据时,将此版本号一同读出,之后更新时,对此版本

  • Redis Template实现分布式锁的实例代码

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 1.互斥性.在任意时刻,只有一个客户端能持有锁. 2.不会发生死锁.即使有一个

  • 一文秒懂Java中的乐观锁 VS 悲观锁

    乐观锁 VS 悲观锁 悲观锁:总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁.写锁.行锁等),当其他线程想要访问数据时,都需要阻塞挂起. 乐观锁:总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改. 乐观锁在Java中通过使用无锁来实现,常用的是CAS,Java中原子类的递增就是通过CAS自旋实现. CAS CAS全称 Compare And Swap(比较与交换),是一种

随机推荐