首页 > 数据库 >redis缓存一致性

redis缓存一致性

时间:2023-02-01 13:47:30浏览次数:53  
标签:缓存 删除 数据库 redis 更新 value key 一致性

redis缓存一致性

 

redis是目前使用最广泛的分布式缓存系统,几乎每家公司都在用。它使用简单,吞吐量高,单机 qps 可以达到 10 万每秒,但在使用redis缓存时存在一个问题,即如何保证缓存数据和数据库中数据的一致性。本文就一致性问题提出常用的解决方案。

一致性问题

读取流程

首先,读缓存;

如果缓存里没有值,那就读取数据库的值;

同时把这个值写进缓存中。

双更模式

先更新缓存,再更新数据库

public void putValue(key, value){
    putToRedis(key, value);
    putToDB(key, value);//异常回滚
}

比如更新一个值,首先刷了缓存,然后把数据库也更新了。但过程中,更新数据库可能会失败,发生了回滚。所以,最后“缓存里的数据”和“数据库的数据”就不一样了,也就是出现了数据一致性问题。

先更新数据库,再更新缓存

public void putValue(key, value){
    // 先更新库
    putToDB(key, value);
    // 再更新缓存
    putToRedis(key, value);
}

问题:操作 A 更新 a 的值为 1,操作 B 更新 a 的值为 2。由于数据库和 Redis 的操作,并不是原子的,它们的执行时长也不是可控制的。当两个请求的时序发生了错乱,就会发生缓存不一致的情况。

双更模式下,数据不一致的概率较大,一般不建议使用双更模式。

删除模式

删除模式即更新数据时,删除缓存,查询时重新从数据库中加载数据。先删除缓存还是后删除缓存?

先删除缓存

public void putValue(key, value){
    deleteFromRedis(key);
    putToDB(key,value);
}

问题:请求A删除了某个 key 的值,这时候有另外一个请求B 到来,那么它就会击穿到数据库,读取到旧的值。无论操作A更新数据库的操作持续多长时间,都会产生不一致的情况。

后删除缓存(Cache-Aside Pattern)

后删除缓存不会出现上述问题。一般情况下这种方式可以解决大部分问题,也是最常用的解决方案。

但是在高并发的情况下,仍有可能出现不一致的情况。场景如下:

public void proccess(key, value){
    N:putToDB(key, 1);
    N:deleteFromRedis(key);
    // A B线程同时操作同一组数据
    A:getFromRedis(key);
    A:getFromDB(key)=1;

    B:putToDB(key, 2);
    B:deleteFromRedis(key);
    // 特殊情况下导致A更新redis慢于B,在B删除redis之后A才完成更新
    A:putToRedis(key, 1);
    
    //DB=2,Redis=1
}

有一系列的高并发操作,一直执行着更新、删除的动作。某个时刻,它更新数据库的值为 1,然后删除了缓存。

正在这时,有两个请求发生了:

  • 一个是读操作,读到的当然是数据库的旧值 1,我们记作操作 A;
  • 同时,另外一个请求发起了更新操作,把数据库记录更新为 2,我们记作操作 B。

一般情况下,读取操作都是比写入操作快的,但我们要考虑两种极端情况:

  • 一种是这个读取操作 A,发生在更新操作 B 的尾部;
  • 一种是操作 A 的这个 Redis 的操作时长,耗费了非常多的时间。比如,这个节点正好发生了 STW。(条件比较苛刻)

那么很容易地,读操作 A 的结束时间就超过了操作 B 删除的动作。

实际上,你也无法控制它们的执行顺序。只要发生这种情况,大概率数据库和Redis的值会不一致。

此种场景下如何解决?

延迟双删

如果有一种机制,能够确保删除动作一定被执行,那就可以解决问题,至少能缩小数据不一致的时间。常用的方法就是延时双删,依然是先更新再删除,唯一不同的是:我们把这个删除动作,在不久之后再执行一次,比如 5 秒之后。

public void putValue(key, value){
    putToDB(key, value);
    deleteFromRedis(key);
    // 5秒之后再次进行删除
    deleteFromRedisDelay(key, 5second);
}

延迟删除动作也有多种实现方式:

  • 如果放在DelayQueue中,会有随着 JVM 进程的死亡,丢失更新的风险;
  • 如果放在 MQ 中,会增加编码的复杂性。

实现方案要根据实际情况进行选择,没有完美的方案,只要能满足业务需求即可。

设置较小的缓存时间

俗称闪电缓存,即把缓存的失效时间设置非常短,比如 5秒。一旦失效,就会再次去数据库读取最新数据到缓存,即数据不一致只会在短时间内不一致。但这种方式,在非常高的并发下,同一时间对某个 key 的请求击穿到 DB,产生缓存击穿问题。

缓存击穿

缓存击穿,指的是缓存中没有数据但数据库中有,由于同一时刻请求量特别大,但是没有读到缓存数据,就会一股脑涌入到数据库中读取,导致数据库因压力过大不可用。

解决方案:

  • 读操作互斥,使用锁或者分布式锁来控制;
  • 更新集中,采用定时或者 binlog 的方式同步更新。
getValue(key){
    res = getFromRedis(key);
    // 未命中
    if(null == res){
        lock.lock(...);
        // 再次读取缓存为null
        res = getFromRedis(key);
        if(res == null){
            res = getFromDB(key);
            if(null != res){
                putToRedis(key,res);
            }
        }
        lock.unlock();
    }
    return res;
}


  分类: redis 标签: redis

标签:缓存,删除,数据库,redis,更新,value,key,一致性
From: https://www.cnblogs.com/manmanblogs/p/17082251.html

相关文章

  • 面试题-Redis的主从同步是如何实现的
    Redis的主从同步是如何实现的Redis的主从复制可以根据是否是全量,分为全量同步和增量同步(也叫全量复制和部分复制)。全量复制一般用于初次复制的场景部分复制则用于处理......
  • redisson分布式锁源码和原理浅析
    在redisson之前,很多人可能已经自己实现过基于redis的分布式锁,本身原理也比较简单,redis自身就是一个单线程处理器,具备互斥的特性,通过setNx,exist等命令就可以完成简单的分布式......
  • Redis主从同步分析
    一、Redis主从同步原理1.1Redis主从同步的过程配置好slave服务器连接的master后,slave会建立和master的连接,然后发送sync命令。无论是第一次同步建立的连接还是连接断开后的......
  • mysql缓存-Buffer Pool总结
    1.磁盘太慢,用内存作为缓存很有必要。2.BufferPool本质上是InnoDB向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size来调整它的大小。3.Buffer......
  • redis基本数据类型 set类型
      127.0.0.1:6379>SADDs1abc(integer)3127.0.0.1:6379>SMEMBERSs11)"b"2)"c"3)"a"127.0.0.1:6379>SREMs1a(integer)1127.0.0.1:6379>SCARD......
  • 面试题-如何实现Redis的高可用
    如何实现Redis的高可用实现Redis的高可用,主要有哨兵和集群两种方式。哨兵简单的一句话:反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为......
  • redis的五大数据类型
    redis的五大数据类型Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作​​数据库​​​、​​缓存​​​和​​消息中间件MQ​​。它支持多种类型的数据结构,......
  • redis之三种特殊数据类型
    redis之三种特殊数据类型一、Geospatial(地理位置)朋友的定位,附近的人,打车距离计算?Redis的Geo在Redis3.2版本就推出了!这个功能可以推算地理位置的信息,两地之间的距离,方......
  • Redis主从复制
    Redis主从复一、概念主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower),​​数据的复制是单向的,只......
  • StringRedisTemplate和RedisTemplate的使用区别
    最近在使用redis的过程中,整合java的时候,用redisTemplate的过程产生一个bug,通过stringRedisTemplate解决了,这里分享下解决过程,仅供参考。RedisTemplate使用的序列类在在操作......