zoukankan      html  css  js  c++  java
  • Redis缓存雪崩,缓存穿透,缓存击穿,缓存预热概念及解决方案

    一、缓存处理流程

          前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。

    二:缓存雪崩

           概念:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候由于查询数据量巨大,引起数据库压力过大甚至down机。

           解决方案:

    1. 缓存数据的过期时间在一个基础的时间上加一个随机值,防止同一时间大量数据过期现象发生。
    2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同缓存数据库中。
    3. 设置热点数据永远不过期。

    三:缓存穿透

           概念: 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。

           例如用户不断发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。这时可以在接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

    解决方案一:缓存空对象

    /**
     * 缓存空对象:
     * 此种方式存在漏洞,不经过判断就直接将Null对象存入到缓存中,
     * 如果恶意制造很多不存在的id,那么缓存中的键值就会很多,恶意攻击时,很可能会被打爆,所以需设置较短的过期时间。
     */
    public Object getObjectInclNullById(Integer id) {
        // 从缓存中获取数据
        Object cacheValue = cache.get(id);
        // 缓存为空
        if (cacheValue != null) {
            // 从数据库中获取
            Object storageValue = storage.get(key);
            // 缓存空对象
            cache.set(key, storageValue);
            // 如果存储数据为空,需要设置一个过期时间(300秒)
            if (storageValue == null) {
                // 必须设置过期时间,否则有被攻击的风险
                cache.expire(key, 60 * 5);
            }
            return storageValue;
        }
        return cacheValue;
    }

    缓存空对象会有一个必须考虑的问题:

    缓存空对象的时候缓存层中会存更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。

    解决方案二:布隆过滤器拦截

          布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 O(n),O(log n),O(n/k)。

          布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

    示例:google guava包下有对布隆过滤器的封装,BloomFilter。

    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;
    public class BloomFilterTest { // 初始化一个能够容纳10000个元素且容错率为0.01布隆过滤器 private static final BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 10000, 0.01); //初始化布隆过滤器 private static void initLegalIdsBloomFilter() { // 初始化10000个合法Id并加入到过滤器中 for (int legalId = 0; legalId < 10000; legalId++) { bloomFilter.put(legalId); } } //id是否合法有效,即是否在过滤器中 public static boolean validateIdInBloomFilter(Integer id) { return bloomFilter.mightContain(id); } public static void main(String[] args) { // 初始化过滤器 initLegalIdsBloomFilter(); // 误判个数 int errorNum=0; // 验证从10000个非法id是否有效 for (int id = 10000; id < 20000; id++) { if (validateIdInBloomFilter(id)){ // 误判数 errorNum++; } } System.out.println("judge error num is : " + errorNum); } }

    实现布隆过滤器拦截

    设置过期时间,让其自动过期失效,这种在很多时候不是最佳的实践方案。

     我们可以提前将真实正确的商品Id,在添加完成之后便加入到过滤器当中,每次再进行查询时,先确认要查询的Id是否在过滤器当中,如果不在,则说明Id为非法Id,则不需要进行后续的查询步骤了。

    /**
     * 防缓存穿透的:布隆过滤器
     */
    public Object getObjectByBloom(Integer id) {
        // 判断是否为合法id
        if (!bloomFilter.mightContain(id)) {
            // 非法id,则不允许继续查库
            return null;
        } else {
            // 从缓存中获取数据
            Object cacheValue = cache.get(id);
            // 缓存为空
            if (cacheValue == null) {
                // 从数据库中获取
                Object storageValue = storage.get(id);
                // 缓存空对象
                cache.set(id, storageValue);
            }
            return cacheValue;
        }
    } 

    四:缓存击穿

           概念:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

           补充:通常使用缓存 + 过期时间的策略来帮助我们加速接口的访问速度,减少了后端负载,同时保证功能的更新,一般情况下这种模式已经基本满足要求了。但如下两个问题如果同时出现,可能就会对系统造成致命的危害:其一:这个key是一个热点key,其二是key的访问量非常大缓存的构建是需要一定时间的。(可能是一个复杂计算,例如复杂的sql、多次IO、多个依赖(各种接口)等等),于是就会出现一个致命问题:在缓存失效的瞬间,有大量线程来构建缓存,造成后端负载加大,甚至可能会让系统崩溃 。

           解决方案:

    1. 设置热点数据永远不过期。
    2. 加互斥锁,互斥锁参考代码如下:

     五:缓存预热

            概念:缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

            解决思路:

              1.直接写个缓存刷新页面,上线时手工操作下;

              2.数据量不大,可以在项目启动的时候自动进行加载;

              3.定时刷新缓存;

              

  • 相关阅读:
    sequence.c
     Link 
    转:MFC中屏蔽ESC和回车关闭对话框
    转:CWebBrowser2去除边框、滚动条、右键菜单
    VC:res协议——从模块中获取资源
    20131213
    20131212
    20131211
    20131205
    20131128
  • 原文地址:https://www.cnblogs.com/wffzk/p/11842195.html
Copyright © 2011-2022 走看看