缓存穿透、缓存并发、热点缓存之最佳招式

简介: 一、前言在之前的一篇缓存穿透、缓存并发、缓存失效之思路变迁文章中介绍了关于缓存穿透、并发的一些常用思路,但是个人感觉文章中没有明确一些思路的使用场景,本文继续将继续深化与大家共同探讨,同时也非常感谢这段时间给我提宝贵建议的朋友们。

一、前言

在之前的一篇缓存穿透、缓存并发、缓存失效之思路变迁文章中介绍了关于缓存穿透、并发的一些常用思路,但是个人感觉文章中没有明确一些思路的使用场景,本文继续将继续深化与大家共同探讨,同时也非常感谢这段时间给我提宝贵建议的朋友们。

说明:本文中提到的缓存可以理解为Redis。

二、缓存穿透与并发方案

相信不少朋友之前看过很多类似的文章,但是归根结底就是二个问题:

  • 如何解决穿透
  • 如何解决并发

当并发较高的时候,其实我是不建议使用缓存过期这个策略的,我更希望缓存一直存在,通过后台系统来更新缓存系统中的数据达到数据的一致性目的,有的朋友可能会质疑,如果缓存系统挂了怎么办,这样数据库更新了但是缓存没有更新,没有达到一致性的状态。

解决问题的思路是
如果缓存是因为网络问题没有更新成功数据,那么建议重试几次,如果依然没有更新成功则认为缓存系统出错不可用,这时候客户端会将数据的KEY插入到消息系统中,消息系统可以过滤相同的KEY,只需保证消息系统不存在相同的KEY,当缓存系统恢复可用的时候,依次从mq中取出KEY值然后从数据库中读取最新的数据更新缓存。
注意:更新缓存之前,缓存中依然有旧数据,所以不会造成缓存穿透。

下图展示了整个思路的过程:


Paste_Image.png

看完上面的方案以后,又会有不少朋友提出疑问,如果我是第一次使用缓存或者缓存中暂时没有我需要的数据,那又该如何处理呢?

解决问题的思路
在这种场景下,客户端从缓存中根据KEY读取数据,如果读到了数据则流程结束,如果没有读到数据(可能会有多个并发都没有读到数据),这时候使用缓存系统中的setNX方法设置一个值(这种方法类似加个锁),没有设置成功的请求则sleep一段时间,设置成功的请求读取数据库获取值,如果获取到则更新缓存,流程结束,之前sleep的请求这时候唤醒后直接再从缓存中读取数据,此时流程结束。

在看完这个流程后,我想这里面会有一个漏洞,如果数据库中没有我们需要的数据该怎么处理,如果不处理则请求会造成死循环,不断的在缓存和数据库中查询,这时候我们会沿用我之前文章中的如果没有读到数据则往缓存中插入一个NULL字符串的思路,这样其他请求直接就可以根据“NULL”进行处理,直到后台系统在数据库成功插入数据后同步更新清理NULL数据和更新缓存。

流程图如下所示:

Paste_Image.png

总结:
在实际工作中,我们往往将上面二个方案组合使用才能达到最佳效果,虽然第二种方案也会造成请求阻塞,但是只是在第一次使用或者缓存暂时没有数据的情况下才会产生,在生产中经过检验在TPS没有上万的情况下是不会造成问题的。

三、热点缓存解决方案

1、缓存使用背景:

我们拿用户中心的一个案例来说明:
每个用户都会首先获取自己的用户信息,然后再进行其他相关的操作,有可能会有如下一些场景情况:

  • 会有大量相同用户重复访问该项目。
  • 会有同一用户频繁访问同一模块。

2、思路解析

  • 因为用户本身是不固定的而且用户数量也有几百万尤其上千万,我们不可能把所有的用户信息全部缓存起来,通过第一个场景情况可以看到一些规律,那就是有大量的相同用户重复访问,但是究竟是哪些用户重复访问我们也并不知道。

  • 如果有一个用户频繁刷新读取项目,那么对数据库本身也会造成较大压力,当然我们也会有相关的保护机制来确实恶意攻击,可以从前端控制,也可以有采黑名单等机制,这里不在赘述。如果用缓存的话,我们又该如何控制同一用户繁重读取用户信息呢。

请看下图:

Paste_Image.png

我们会通过缓存系统做一个排序队列,比如1000个用户,系统会根据用户的访问时间更新用户信息的时间,越是最近访问的用户排名越排前,系统会定期过滤掉排名最后的200个用户,然后再从数据库中随机取出200个用户加入队列,这样请求每次到达的时候,会先从队列中获取用户信息,如果命中则根据userId,再从另一个缓存数据结构中读取用户信息,如果没有命中则说明该用户请求频率不高。

JAVA伪代码如下所示:

       for (int i = 0; i < times; i++) {
            user = new ExternalUser();
            user.setId(i+"");
            user.setUpdateTime(new Date(System.currentTimeMillis()));
            CacheUtil.zadd(sortKey, user.getUpdateTime().getTime(), user.getId());
            CacheUtil.putAndThrowError(userKey+user.getId(), JSON.toJSONString(user));
        }
        
        Set<String> userSet = CacheUtil.zrange(sortKey, 0, -1);
        System.out.println("[sortedSet] - " + JSON.toJSONString(userSet) );
        if(userSet == null || userSet.size() == 0)
            return;
        
        Set<Tuple> userSetS = CacheUtil.zrangeWithScores(sortKey, 0, -1);
        StringBuffer sb = new StringBuffer();
        for(Tuple t:userSetS){
            sb.append("{member: ").append(t.getElement()).append(", score: ").append(t.getScore()).append("}, ");
        }
        
        System.out.println("[sortedcollect] - " + sb.toString().substring(0, sb.length() - 2));
        
        Set<String> members = new HashSet<String>();
        for(String uid:userSet){
            String key = userKey + uid;
            members.add(uid);
            ExternalUser user2 = CacheUtil.getObject(key, ExternalUser.class);
            System.out.println("[user] - " + JSON.toJSONString(user2) );
        }
        System.out.println("[user] - "  + System.currentTimeMillis());
        
        String[] keys = new String[members.size()];
        members.toArray(keys);
        
        Long rem = CacheUtil.zrem(sortKey, keys);
        System.out.println("[rem] - " + rem);
        userSet = CacheUtil.zrange(sortKey, 0, -1);
        System.out.println("[remove - sortedSet] - " + JSON.toJSONString(userSet));
目录
相关文章
|
3月前
|
存储 缓存 监控
缓存击穿、缓存穿透、缓存雪崩 3大问题,如何彻底解决?
【10月更文挑战第8天】在分布式系统中,缓存的使用极大地提高了系统的性能和响应速度。然而,缓存击穿、缓存穿透和缓存雪崩是三个常见的缓存相关问题,它们可能导致系统性能下降,甚至引发系统崩溃。本文将深入探讨这三个问题的成因、影响以及彻底的解决方案。
153 1
|
28天前
|
缓存 监控 NoSQL
Redis经典问题:缓存穿透
本文详细探讨了分布式系统和缓存应用中的经典问题——缓存穿透。缓存穿透是指用户请求的数据在缓存和数据库中都不存在,导致大量请求直接落到数据库上,可能引发数据库崩溃或性能下降。文章介绍了几种有效的解决方案,包括接口层增加校验、缓存空值、使用布隆过滤器、优化数据库查询以及加强监控报警机制。通过这些方法,可以有效缓解缓存穿透对系统的影响,提升系统的稳定性和性能。
|
2月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
2月前
|
缓存 NoSQL 数据库
缓存穿透、缓存击穿和缓存雪崩及其解决方案
在现代应用中,缓存是提升性能的关键技术之一。然而,缓存系统也可能遇到一系列问题,如缓存穿透、缓存击穿和缓存雪崩。这些问题可能导致数据库压力过大,甚至系统崩溃。本文将探讨这些问题及其解决方案。
|
2月前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
81 10
|
2月前
|
缓存 监控 NoSQL
Redis 缓存穿透的检测方法与分析
【10月更文挑战第23天】通过以上对 Redis 缓存穿透检测方法的深入探讨,我们对如何及时发现和处理这一问题有了更全面的认识。在实际应用中,我们需要综合运用多种检测手段,并结合业务场景和实际情况进行分析,以确保能够准确、及时地检测到缓存穿透现象,并采取有效的措施加以解决。同时,要不断优化和改进检测方法,提高检测的准确性和效率,为系统的稳定运行提供有力保障。
66 5
|
2月前
|
缓存 监控 NoSQL
Redis 缓存穿透及其应对策略
【10月更文挑战第23天】通过以上对 Redis 缓存穿透的详细阐述,我们对这一问题有了更深入的理解。在实际应用中,我们需要根据具体情况综合运用多种方法来解决缓存穿透问题,以保障系统的稳定运行和高效性能。同时,要不断关注技术的发展和变化,及时调整策略,以应对不断出现的新挑战。
63 4
|
3月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
98 4
|
3月前
|
消息中间件 缓存 NoSQL
大数据-49 Redis 缓存问题中 穿透、雪崩、击穿、数据不一致、HotKey、BigKey
大数据-49 Redis 缓存问题中 穿透、雪崩、击穿、数据不一致、HotKey、BigKey
89 2
|
3月前
|
缓存 NoSQL 关系型数据库
缓存穿透以及解决方案
缓存穿透以及解决方案
50 0