为什么使用缓存?有哪些常见的使用方式和异常场景?

导读:本篇文章讲解 为什么使用缓存?有哪些常见的使用方式和异常场景?,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

为什么使用缓存

  • 提升读速度,数据缓存,大量联表查询。例如,根据用户名称查询用户,获取相关的角色,操作权限等信息。
  • 降低数据库层的负载,当并发比较高时,redis缓存的每秒钟可以支持几万次的查询请求,可以有效的降低数据库查询的负载。

常见的使用方式

  • 设置最大的缓存量,以及达到最大存储量时的剔除规则。以redis为例,maxmemory 最大缓存数,maxmemory-policy 剔除策略,包括LRU策略,random随机剔除,TTL删除即将失效的key。
  • 超时剔除,设置失效时间,到达时间后自定剔除。

  • 主动更新,对于实时性要求较高的数据,在修改后主动修改缓存。

常见异常场景

缓存穿透

一般缓存设计,首先从缓存查询数据,如果没有值,再从DB数据库获取数据,获取数据后写入缓存。缓存穿透是指查询一个不存在的数据,在缓存层DB数据库都不能查询到数据,最后返回空数据。如果该类情况的访问并发很高,或者受到恶意的攻击,可能会造成DB数据库宕机。解决方式一般两种方案,如下:

  • 缓存空对象

在DB数据库查询不到数据时,设置一个空对象,下次再次查询该对象时,则直接返回缓存对象。示例代码如下:

public Goods getById(Long id) {
 
        if (Objects.isNull(id)) {
            return null;
        }
        String key = GOODS + id;
        String goodsJson = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(goodsJson)) {
            return JSON.parseObject(goodsJson, Goods.class);
        }
        Goods goods = goodsMapper.selectByPrimaryKey(id);
        if (Objects.nonNull(goods)) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(goods), RedisConst.DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            return goods;
        } else {
            
            // 穿透优化
            redisTemplate.opsForValue().set(key, JSON.toJSONString(new Goods()), RedisConst.DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            return null;
        }
    }

但是,缓存空对象存在一些问题。存储空对象后,需要更多的存储空间(如果是收到攻击,问题更严重)。可以设置一个失效时间,让其自动删除。缓存了空对象后,与DB数据库的数据会有一段数据不一致。例如,设置缓存的失效时间是2分钟,在这段时间内,数据库又添加了该id的数据,就出现了缓存和数据库数据不一致。此时,可以利用消息系统或者其他方式清理缓存。 

  • 使用布隆过滤器

对于数据实时性比较小的数据,可以使用bloomfilter过滤器存储数据库存在的数据,在缓存查询数据前,根据bloomfilter过滤器做一次校验。如果存在,在进行查询操作,反之,则直接返回空值。这在一定程度上减少了缓存和DB数据库的负载,起到了保护作用。由于bloomfilter没有删除操作,所以,该解决方案只适用于数据实时性不大,数据量大的情况。

缓存雪崩

在高并发请求的情况下,由于缓存层服务器崩溃,导致所有的请求直接在db层执行,导致db崩溃,进而导致整个服务的崩溃不可用。解决方案如下:

  1. 要保证缓存层的高可用,以redis为例,其高可用方案包括Redis sentinel(哨兵模式),Redis Cluster(集群模式)。
  2. 对高并发的服务做限流和降级处理,当请求超过了服务的负载,则进行对应的限流和降级处理。常见的解决方案包括spring cloud 的hystrix,以及alibaba sentinel。

缓存击穿

一般缓存设计都是使用缓存+失效时间的模式,这种模式存在一种问题。对于一些热点key的数据,当该key的缓存失效,需要重新从DB数据库获取。这时,涌入大量请求,所有请求并发地访问DB数据库,可能会压垮数据库,这种情况叫做缓存击穿。解决方案如下:

  • 使用互斥分布式锁

在请求并发访问数据库时,对查询数据库操作进行加锁,同一时间只有一个请求操作数据库。示例代码如下:

public Goods getById(Long id) {
 
        if (Objects.isNull(id)) {
            return null;
        }
        String key = GOODS + id;
        String goodsJson = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(goodsJson)) {
            return JSON.parseObject(goodsJson, Goods.class);
        }
        Goods goods = goodsMapper.selectByPrimaryKey(id);
        if (Objects.nonNull(goods)) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(goods), RedisConst.DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            return goods;
        } else {
            
            // 穿透优化
            redisTemplate.opsForValue().set(key, JSON.toJSONString(new Goods()), RedisConst.DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            return null;
        }
    }
  • 设置热点key数据永不过期

就是保证数据一直在缓存不过期,在缓存即将过期时,进行缓存的更新,已经过期时间的延长。

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13643.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!