聊聊db和缓存一致性的5种实现方式

数据存储在数据库中,为了加快业务访问的速度,我们将数据库中的一些数据放在缓存中,那么问题来了,如何确保db和缓存中数据的一致性呢?我们列出了5种方法,大家都了解一下,然后根据业务自己选择。

方案1

获取缓存逻辑

使用过定时器,定时刷新redis中的缓存。

db更新数据逻辑

更新数据不用考虑缓存中的数据,直接更新数据就可以了

存在的问题

缓存中数据和db中数据一致性可能没有那么及时,不过最终在某个时间点,数据是一致的。

方案2

获取缓存逻辑

c1:根据key在redis中获取对应的value

c2:如果value存在,直接返回value;若value不存在,继续下面步骤

c3:从数据库获取值,赋值给value,然后将key->value放入redis,返回value

更新db逻辑

u1:开始db事务

u2:更新数据

u3:提交db事务

u4:删除redis中当前数据的缓存

存在的问题

  1. 上面u3成功,u4失败,会导致删除缓存失败,导致缓存中数据和db数据会不一致。
  2. 如果同时有很多线程到达c2发现缓存不存在,同时请求c3访问db,会对db造成很大的压力

方案3

获取缓存逻辑

c1:根据key在redis中获取对应的value

c2:如果value存在,直接返回value;若value不存在,继续下面步骤

c3:从数据库获取值,赋值给value,然后将key->value放入redis,返回value

更新db逻辑

u1:删除redis中当前数据的缓存

u2:开始db事务

u3:更新数据

u4:提交db事务

存在的问题

  1. 更新数据的线程执行u1成功之后,u2还未执行时,此时获取缓存的线程刚好执行了c1到c3的逻辑,此时会将旧的数据放入redis,导致redis和db数据不一致
  2. 同样存在方案2中说到的问题:如果同时有很多线程到达c2发现缓存不存在,同时请求c3访问db,会对db造成很大的压力

方案4

对方案2做改进,确保db更新成功之后,删除缓存操作一定会执行,我们可以通过可靠消息来实现,可靠消息可以确保更新db操作和删除redis中缓存最终要么都成功要么都失败,依靠的是最终一致性来实现的。

改进之后过程如下。

获取缓存逻辑

c1:根据key在redis中获取对应的value

c2:如果value存在,直接返回value;若value不存在,继续下面步骤

c3:从数据库获取值,赋值给value,然后将key->value放入redis,返回value

更新db逻辑

u1:开始db事务

u2:更新数据

u3:投递删除redis缓存的消息

u4:提交db事务

消息消费者-清理redis缓存的消费者

接受到清理redis缓存的消息之后,将redis中对应的缓存清除。

存在的问题

  1. 更新db和清理redis中的缓存之间存在一定的时间延迟,这段时间内,redis缓存的数据是旧的,也就是说这段时间内db和缓存数据是不一致的,但是最终会一致,这个不一致的时间可能比较小(这个需要看消息消费的效率了)
  2. 同样存在方案2中说到的问题:如果同时有很多线程到达c2发现缓存不存在,同时请求c3访问db,会对db造成很大的压力

关于可靠消息的,可以看

方式5

我们先了解一些知识。

redis中几个方法

get(key)

获取key的值,如果存在,则返回;如果不存在,则返回nil

setnx(key,value)

setnx的含义就是SET if Not Exists,该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0

del(key)

将key对应的值从redis中删除

数据库相关知识

select v from t where t.key = #key# for update;

update t set v = #v# where t.key = #key#;

上面两个sql会相互阻塞,直到其中一个提交之后,另外一个才可以继续执行。

下面我们就通过上面的知识来实现db和缓存强一致性。

更新数据逻辑

1.打开db事务
2.update t set v = #v# where t.key = #key#;
3.根据key删除redis中的缓存:RedisUti.del(key);
4.提交db事务

获取缓存逻辑

/*公众号:路人甲Java
* 工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 坚信用技术改变命运,让家人过上更体面的生活。*/
public class CacheUtil {

    //根据key获取缓存中对应的value
    public static String getCache(String key) throws InterruptedException {
        String value = RedisUtils.get(key);
        if (value != null) {
            return value;
        }
        //过期时间为当前时间+5秒
        String expireTimeKey = key + "ExpireTime";
        long expireTimeValue = System.currentTimeMillis() + 5000;
        //setnx是原子操作,所以只有一个会成功
        int setnx = RedisUtils.setnx(expireTimeKey, expireTimeValue + "");
        if (setnx == 0) {
            expireTimeValue = Long.valueOf(RedisUtils.get(expireTimeKey));
            //如果expireTimeValue小于当前时间,说明expireTimeKey过期了,将其删除
            if (System.currentTimeMillis() > expireTimeValue) {
                //将expireTimeKey对应的删除
                RedisUtils.del(expireTimeKey);
            } else {
                //休眠1秒继续获取
                TimeUnit.SECONDS.sleep(1);
            }
            //重试
            return getCache(key);
        } else {
            //1. 开启db事务
            start transaction;
            //2. 执行update  t set v = #v# where t.key = #key# for update; 将v的值赋值给value
            update  t set v = #v# where t.key = #key# for update;
            RedisUtils.set(key, value);
            //3.提交db事务
            commit transaction;
        }
        return value;
    }

    //redis工具类,内部方法为伪代码
    public static class RedisUtils {
        //根据key获取value
        public static String get(String key) {
            return null;
        }

        //设置key对应的value
        public static void set(String key, String value) {
        }

        //删除redis中一个key对应的值
        public static void del(String key) {
        }

        //setnx的含义就是SET if Not Exists,该方法是原子的,如果key不存在,
        //则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0
        public static int setnx(String key, String value) {
            return 1;
        }
    }
}

这种方式可以确保db和redis中缓存同一时间强一致。

expireTimeKey为了防止某些线线程执行RedisUtils.setnx(expireTimeKey, expireTimeValue + "");返回1,表示setnx成功了,然后执行下一行代码的时候系统后挂了,会导致将db数据加载到redis中失败,代码:if (System.currentTimeMillis() > expireTimeValue)是给其他线程机会,可以获取这个过期时间,发现过期之后直接删掉,这样其他线程才有机会将db数据load到redis中。

工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!喜欢的请关注公众号:路人甲Java

原文地址:https://www.cnblogs.com/itsoku123/p/11718164.html

时间: 2024-08-30 04:21:15

聊聊db和缓存一致性的5种实现方式的相关文章

分布式理论(4):Leases 一种解决分布式缓存一致性的高效容错机制(转)

作者:Cary G.Gray and David R. Cheriton 1989 译者:[email protected] 2011-5-7 出处:http://duanple.blog.163.com/blog/static/70971767201141111440789/ [ 序:所谓租约(leases),其实就是一个合同,即服务端给予客户端在一定期限内可以控制修改操作的权力.如果服务端要修改数据,首先要征求拥有这块数据的租约的客户端的同意,之后才可以修改.客户端从服务端读取数据时往往就同

聊聊高并发(五)理解缓存一致性协议以及对并发编程的影响

Java作为一个跨平台的语言,它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬件差异,给上层的开发者一个一致的使用接口.Java内存模型就是这样一个中间层的模型,它为程序员屏蔽了底层的硬件实现细节,支持大部分的主流硬件平台.要理解Java内存模型以及一些处理高并发的技术手段,理解一些基本的硬件知识是必须的.这篇会说一下跟并发编程相关的一些硬件知识. 一个基本的CPU执行计算的过程如下: 1. 程序以及数据被加载到主内存 2. 指令和数据被加载到CPU的高速缓存 3. CPU执行

缓存一致性和跨服务器查询的数据异构解决方案canal

当你的项目数据量上去了之后,通常会遇到两种情况,第一种情况应是最大可能的使用cache来对抗上层的高并发,第二种情况同样也是需要使用分库 分表对抗上层的高并发...逼逼逼起来容易,做起来并不那么乐观,由此引入的问题,不见得你有好的解决方案,下面就具体分享下. 一:尽可能的使用Cache 比如在我们的千人千面系统中,会针对商品,订单等维度为某一个商家店铺自动化建立大约400个数据模型,然后买家在淘宝下订单之后,淘宝会将订单推 送过来,订单会在400个模型中兜一圈,从而推送更贴切符合该买家行为习惯的

数据异构解决方案缓存一致性和跨服务器查询

缓存一致性和跨服务器查询的数据异构解决方案canal 当你的项目数据量上去了之后,通常会遇到两种情况,第一种情况应是最大可能的使用cache来对抗上层的高并发,第二种情况同样也是需要使用分库 分表对抗上层的高并发...逼逼逼起来容易,做起来并不那么乐观,由此引入的问题,不见得你有好的解决方案,下面就具体分享下. 一:尽可能的使用Cache 比如在我们的千人千面系统中,会针对商品,订单等维度为某一个商家店铺自动化建立大约400个数据模型,然后买家在淘宝下订单之后,淘宝会将订单推 送过来,订单会在4

分布式锁三种实现方式(数据库实现,缓存Redis等,Zookeeper)

分布式锁三种实现方式: 1. 基于数据库实现分布式锁: 2. 基于缓存(Redis等)实现分布式锁: 3. 基于Zookeeper实现分布式锁: 一, 基于数据库实现分布式锁 1. 悲观锁 利用select … where … for update 排他锁 注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表.有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题. 2. 乐观锁 所谓乐观锁与前边最大区别在

zt:缓存一致性(Cache Coherency)入门 cach coherency

http://www.infoq.com/cn/articles/cache-coherency-primer http://www.cnblogs.com/xybaby/p/6641928.html 本文是RAD Game Tools程序员Fabian “ryg” Giesen在其博客上发表的<Cache coherency primer>一文的翻译,经作者许可分享至InfoQ中文站.该系列共有两篇,本文系第一篇. 我计划写一些关于多核场景下数据组织的文章.写了第一篇,但我很快意识到有大量的

由一个bug引发的SQLite缓存一致性探索

问题 我们在生产环境中使用SQLite时中发现建表报“table xxx already exists”错误,但DB文件中并没有该表.后面才发现这个是SQLite在实现过程中的一个bug,而这个bug与数据字典的一致性相关,下面这篇文章主要讨论SQLite的缓存机制,以及缓存一致性实现的策略,希望对大家了解SQLite缓存机制有一定的帮助. 缓存 SQLite中缓存主要包括两方面,数据字典缓存和数据页缓存.SQLite本身是一个文件数据库,所有的数据都在一个DB文件中,文件以块(page)的形式

Java的多线程机制:缓存一致性和CAS

Java的多线程机制:缓存一致性和CAS 一.总线锁定和缓存一致性 这是两个操作系统层面的概念.随着多核时代的到来,并发操作已经成了很正常的现象,操作系统必须要有一些机制和原语,以保证某些基本操作的原子性,比如处理器需要保证读一个字节或写一个字节是原子的,那么它是如何实现的呢?有两种机制:总线锁定和缓存一致性. 我们知道,CPU和物理内存之间的通信速度远慢于CPU的处理速度,所以CPU有自己的内部缓存,根据一些规则将内存中的数据读取到内部缓存中来,以加快频繁读取的速度.我们假设在一台PC上只有一

多线程之:MESI-CPU缓存一致性协议

MESI(Modified Exclusive Shared Or Invalid)(也称为伊利诺斯协议,是因为该协议由伊利诺斯州立大学提出)是一种广泛使用的支持写回策略的缓存一致性协议,该协议被应用在Intel奔腾系列的CPU中,详见“support the more efficient write-back cache in addition to the write-through cache previously used by the Intel 486 processor” MESI