Redis如何保证与数据库的数据一致性?

前言

在我们日常开发中,随着数据的增长、请求量的增加,不可避免的要用到缓存相关的技术,Redis作为目前主流的作为缓存的非关系型数据库,功能十分强大。多种数据类型、支持持久化、快速读写、高可用、横向扩展是Redis的成为主流缓存技术的主要原因。火箭造完了,接下来搞点干货

当我们使用Redis来缓存数据时,某个需缓存的数据进行修改时(如:Redis缓存了商品信息,商家操作修改了昵称),此时我们接口是先删除Redis中的缓存,再执行sql修改呢;还是先执行sql修改,再删除Redis的缓存?接下来我们一点一点分析

前提

首先,我们讨论这个key的数据一致性,是建立在该key不是热点key的基础上,热点key自然另当别论(有需要,后续有时间说下这个),之所以说不能是热点key的原因是:若是热点key,通过删除key的方式,用户查询来重置缓存,有可能会导致缓存击穿。

先删除缓存数据,再修改数据库数据

当并发量不大的情况下,其实两种方式基本上不会出现数据一致性的问题,不过当并发量上来,用户量增多后,问题就来了:
当我们先执行删除缓存,再进行数据库修改时

 redisTemplate.delete("xx:g_id1")//1
 执行 update goods_info set name = "abc" where goods_id = 1 //2

数据不一致的情况:
一、 线程A修改数据时,需先执行删除缓存操作
二、其他线程只要在线程A删除缓存和执行update期间,查询数据库得到了旧的数据,此时就有极大的概率会出现数据不一致的情况。

先修改数据库数据,再删除缓存数据

当我们先执行修改,在进行删除时

 执行 update goods_info set name = "abc" where goods_id = 1 //1
 redisTemplate.delete("xx:g_id1")//2

数据不一致的情况:
一、 线程A读取商品数据,刚好缓存失效了,故查询数据库数据,刚要执行放入redis缓存时,CPU发生上下文切换,线程A暂时得不到执行
二、此时线程B修改数据,执行update商品操作,然后删除缓存
三、线程A执行时,将之前查询到的旧的数据存到redis缓存中,此时就会出现redis和数据库的数据不一致的情况。
当然这种情况和先删除缓存再修改数据库数据相比,条件较为严苛,出现的可能性要低。

双删延迟策略

作为有追求的技术人员,肯定需要有合适的方法来解决这个问题,其实没有办法做到绝对的一致性,这是由CAP理论决定的,缓存系统适用的场景就是非强一致性的场景,所以它属于CAP中的AP。
所以,我们得委曲求全,可以去做到BASE理论中说的最终一致性。所以通常使用的方法是:双删延迟策略

 redisTemplate.delete("xx:g_id1")//1
 执行 update goods_info set name = "abc" where goods_id = 1 //2
 TimeUnit.SECOND.sleep(1);//3
 redisTemplate.delete("xx:g_id1")//4

通过前后都删除缓存的方式保证数据的一致性,当1~4这个执行时间内可能会出现数据不一致的情况(原因上述已经讲过),3这里之所以要进行延迟是因为,要保证其他查询到旧数据的线程更新缓存了,再执行删除操作后,再查数据库的数据自然就是最新的了。

上述代码显然有个比较大的问题,就是3这个操作,每个key进行删除时,都要等一秒?有朋友就说了:抱歉,我们系统对接口的性能要求很高,超过500毫秒都不行,更别说了一秒了。
解决办法也很简单,启一个线程来异步执行延迟删除key就可以完美解决时间问题。使用@Async注解来进行删除key即可:
@Async介绍
在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

 redisTemplate.delete("xx:g_id1");//1
 执行 update goods_info set name = "abc" where goods_id = 1; //2
 redisAsyncDeleteComponent.asyncDeleteKey("xx:g_id1");//3

RedisAsyncDeleteComponent 代码:

@Component
public class RedisAsyncDeleteComponent {
    
    

    @Async
    public void asyncDeleteKey(String key) throws InterruptedException{
    
    
        TimeUnit.SECONDS.sleep(1);
        redisTemplate.delete(key);
    }
}

关于这个问题,就和大家分享到这,如果有疑问的,欢迎留言讨论

猜你喜欢

转载自blog.csdn.net/kolbjbe/article/details/114438117