redis缓存失效问题

redis数据失效导致的雪崩

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

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

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

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和数据库查询,应该直接返回一个错误的提示,这样可以防止恶意访问,导致服务器瘫痪。

布隆过滤器(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;
    }

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值