欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

redis缓存失效问题

程序员文章站 2022-05-22 20:34:34
...

redis数据失效导致的雪崩

因为缓存失效,从而导致大量的请求没有命中缓存,导致请求全部打到数据库。

redis缓存失效问题

1.大量请求,导致数据库处理不过来,整个系统依赖数据库的功能全部崩溃。

2.单系统挂掉,其它依赖于该系统的应用也会出现不稳定甚至崩溃。

redis数据失效的场景

redis缓存失效问题

1.因为打到内存阀值,采用数据淘汰策略(LRU/LFU)导致数据失效。

2.数据设置了过期时间,达到过期时间后,数据失效。

3.因为故障或者宕机/升级,导致服务重启,数据失效。

防止redis数据失效方法

1.保证内存充足,淘汰的数据都是非热点的数据,不会导致系统崩溃;可以采用redis集群分片部署。

2.对于大量的过期数据,设置过期错开,不要设置同一个过期时间,导致某一时刻,大量数据不命中。

3.做好数据备份,做好主从复制。

缓存雪崩解决方案

1.对数据库访问进行限流,保证不会因为缓存雪崩导致,数据库故障——使用信号量控制并发。

2.容错降级——返回指定的异常码。

代码实例:

 // 数据库限流,根据数据库连接大小定义
    Semaphore semaphore = new Semaphore(30);
 
    public Object queryStock(String goodsId) {
        String cacheKey = "goodsStock-" + goodsId;
        // 1.先从Redis里面获取数据
        String value = mainRedisTemplate.opsForValue().get(cacheKey);
        // 2.缓存里面没有数据,从数据库取
        if (value != null) {
            logger.warn(Thread.currentThread().getName() + "缓存中获得数据+++++++++++++++++++++++++++");
            return value;
        }
        // 3.去请求数据库,需要进行控制,根据连接数进行控制
        try {
            // 同一时间,只能有30个请求去数据库获取数据,并且重构缓存
            boolean acquire = semaphore.tryAcquire(5, TimeUnit.SECONDS);
            if (acquire) {
                // 再次从Reids中获取数据
                value = mainRedisTemplate.opsForValue().get(cacheKey);
                if (value != null) {
                    logger.warn(" 缓存中获得数据+++++++++++++++++++++++++++");
                    return value;
                }
                value = databaseService.queryFromDatabase(goodsId);
                System.err.println(" 数据库中获得数据==============================");
                // 3.塞到缓存,过期时间
                String v = value;
                mainRedisTemplate.execute((RedisCallback<Boolean>) conn ->{
                    return conn.setEx(cacheKey.getBytes(), 120, v.getBytes());
                });
            } else {
                // 等待时间
                // 定义错误信息返回指定错误码
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放信号量
            semaphore.release();
        }
        return value;
    }

布隆过滤器——过滤不必要的查询

redis缓存失效问题

对于有些场景,当访问一个本就不存的数据时,这时我们不需要去走redis和数据库查询,应该直接返回一个错误的提示,这样可以防止恶意访问,导致服务器瘫痪。

布隆过滤器(Bloom Filter):是1970年布隆提出的,它实际上是一个很长的二进制数组和一系列hash函数,布隆过滤器可以用于检索一个元素是否在一个集合中;它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

布隆过滤器的构建过程:

1.加载符合条件的记录

2.计算每条元素的hash值

3.计算hash值对应二进制数据的位置

4.将对应位置的值改为1

查找元素是否存在的过程:

1.计算元素的hash值

2.计算hash值对应二进制数组的位置

3.找到数组中对应的位置的值,0代表不存在,1代表存在。

代码实例:

@Service
// 构建一个布隆过滤器
public class RedisBloomFilter {
  // redis连接信息
  @Value("${spring.redis.main.hostName}")
  private String redisHost;
  
  @Value("${spring.redis.main.port}")
  private int redisPort;
  
  // bloomfilter 客户端
  private Client client;
  
  @PostConstruct
  public void init() {
    // bloomfilter 客户端
    client = new Client(redisHost, redisPort);
  }
  
  /**
   * 创建一个自定义的过滤器
   * @param filterName
   */
  public void createFilter(String filterName) {
    // 创建一个容量10万,误判率0.01%的布隆过滤器
    client.createFilter(filterName, 1000, 0.1);
  }
  
  /**
   * 添加元素
   * @param filterName
   * @param value
   * @return
   */
  public boolean addElement(String filterName, String value) {
    return client.add(filterName, value);
  }
 
  /**
   * 判断元素是否存在
   * @param filterName
   * @param value
   * @return
   */
  public boolean exists(String filterName, String value) {
    return client.exists(filterName, value);
  }
}
public Object queryStock(final String goodsId) {
      String cacheKey = "goodsStock-"+ goodsId;
      // 增加一个布隆过滤器的筛选
      boolean exists = filter.exists("goodsBloomFilter", cacheKey);
      if(!exists) {
        logger.warn(Thread.currentThread().getName()+" 您需要的商品不存在+++++++++++++++++++++++++++");
        return "您需要的商品不存在";
      }
      
      public Object queryStock(String goodsId) {
        String cacheKey = "goodsStock-" + goodsId;
        // 1.先从Redis里面获取数据
        String value = mainRedisTemplate.opsForValue().get(cacheKey);
        // 2.缓存里面没有数据,从数据库取
        if (value != null) {
            logger.warn(Thread.currentThread().getName() + "缓存中获得数据+++++++++++++++++++++++++++");
            return value;
        }
        // 3.去请求数据库,需要进行控制,根据连接数进行控制
        try {
            // 同一时间,只能有30个请求去数据库获取数据,并且重构缓存
            boolean acquire = semaphore.tryAcquire(5, TimeUnit.SECONDS);
            if (acquire) {
                // 再次从Reids中获取数据
                value = mainRedisTemplate.opsForValue().get(cacheKey);
                if (value != null) {
                    logger.warn(" 缓存中获得数据+++++++++++++++++++++++++++");
                    return value;
                }
                value = databaseService.queryFromDatabase(goodsId);
                System.err.println(" 数据库中获得数据==============================");
                // 3.塞到缓存,过期时间
                String v = value;
                mainRedisTemplate.execute((RedisCallback<Boolean>) conn ->{
                    return conn.setEx(cacheKey.getBytes(), 120, v.getBytes());
                });
            } else {
                // 等待时间
                // 定义错误信息返回指定错误码
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放信号量
            semaphore.release();
        }
        return value;
    }