在使用redis作为缓存时出现的问题

在使用redis作为缓存时出现的问题

使用redis作为关系型数据库的缓存时不可避免的问题

缓存穿透:

缓存穿透是指客户端请求的数据在缓存和数据库中都不存在---->会造成缓存失效--->给数据库带来很大的压力;

解决方案:

方案一:

  • 缓存空对象
    • 优点:
      • 实现简单,维护方便
    • 缺点:
      • 额外的内存消耗
      • 可能造成短期的不一致

方案二:

  • 使用布隆过滤器:

    • 优点:内存占用少,没有多余的key
  • 缺点:

    • 实现复杂
    • 可能存在误判
  • 增加 id 的复杂度,避免被猜到 id 规律。

  • 做好数据的基础格式校验。

  • 加强用户的权限校验。

  • 做好热点参数的限流(sentinel)。

缓存雪崩:

缓存雪崩是指在同一时间段大量的缓存 key 同时失效或者 Redis 服务宕机,导致大量请求到达数据库,对数据库造成极大的压力。
常见的解决方案有:

  • 给不同的 key 的 TTL添加随机值。
  • 利用 Redis 集群提高服务的可用性。(Redis 主从,哨兵)
  • 给缓存业务添加降级限流策略(sentinel)。
  • 给业务添加多级缓存。

缓存击穿

也叫热点 key 问题,是指一个被高并发访问并且缓存重建业务比较复杂的key 突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案有:

  • 互斥锁
    • 优点:
      • 没有额外的内存消耗
      • 保证一致性
      • 失效简单
    • 缺点:
      • 线程需要等待,性能收到影响
      • 可能有死锁的风险
  • 逻辑过期
    • 优点:
    • 线程无需等待,性能较好
    • 缺点:
      • 不保证一致性
      • 具有额外内存消耗
      • 实现复杂

最重要的问题是

一旦使用缓存,就有可能导致缓存数据和数据库的数据不一致;

首先,我们得清楚“数据的一致性”具体是啥意思。其实,这里的“一致性”包含了两种情况:

  • 缓存中有数据,那么,缓存的数据值需要和数据库中的值相同;
  • 缓存中本身没有数据,那么,数据库中的值必须是最新值。

解决缓存不一致的办法有以下几种

方案一:双写模式:写缓存时,也同步写数据库,缓存和数据库中的数据一致;

方案二:失效模式::每次修改数据库的数据后,删除redis中缓存的数据,当有redis查询请求时,会先去数据库查询,然后更新到redis中,后续继续请求redis。更新时删除缓存数据,称为失效模式

方案三: 延迟双删是在失效模式的基础上,在删除reids缓存时,让程序睡眠几十毫秒,再次执行删除缓存操作,可有效预防失效模式中缓存不一致问题,但是并不推荐。因为数据不一致问题本来就是极少情况发生的,如果使用延时双删,那么大部分正常的请求都会被阻塞几十毫秒,系统性能下降,显然得不偿失!

如上所示,无论是双写模式还是失效模式,都无法完美解决缓存一致性问题!但在不同的业务场景对数据一致性的要求也不同,并非所有场景都需要数据强一致性,我们要根据实际业务场景来分析

redisTemplate 根据下标选择指定的redis数据库
实现方法一:

@Autowired
    RedisTemplate<String, ?> redisTemplate;
    @SuppressWarnings("resource")
    public Object slist() { 
        JedisConnectionFactory connectionFactory = 
                (JedisConnectionFactory) redisTemplate.getConnectionFactory();
        connectionFactory.setDatabase(1);//选择1号数据库
        List<San> slist = new ArrayList<San>();
        //通过redis模板对象 获取操作列表的接口
        ListOperations forList = redisTemplate.opsForList();
        List<String> range = forList.range("slist", 0, -1);
        for (String object : range) { 
            San s = JsonUtils.jsonToPojo(object, San.class);
            slist.add(s);
        }
        return slist;
    }

实现方法二:

@Repository
public class SanDao { 
    //注入redis模板对象
    @Autowired
    RedisTemplate<String, ?> redisTemplate;
    //注入jedis连接工厂对象
    @Autowired
    JedisConnectionFactory jedisConnectionFactory;
    @SuppressWarnings("resource")
    public Object slist() { 
        jedisConnectionFactory.setDatabase(1);//选择1号数据库
        List<San> slist = new ArrayList<San>();
        //通过redis模板对象 获取操作列表的接口
        ListOperations forList = redisTemplate.opsForList();
        List<String> range = forList.range("slist", 0, -1);
        for (String object : range) { 
            San s = JsonUtils.jsonToPojo(object, San.class);
            slist.add(s);
        }
        return slist;
    }
}
posted @ 2022-10-25 08:18  鸽宗  阅读(54)  评论(0编辑  收藏  举报