黑马点评学习笔记(缓存篇)

1.常见的缓存更新策略

是使用更新缓存模式还是使用删除缓存模式

采用删除缓存模式,因为更新数据时更新数据库并删除缓存,查询时再更新缓存,无效写操作较少。

选择使用删除缓存模式,那么是先操作缓存还是先操作数据库

先操作数据库。原因:

2.缓存穿透

缓存穿透指的是客户端请求的数据在缓存中和数据库中都不存在,使得缓存永远不会生效,请求都会打到数据库。

2种解决方法:

1.缓存空对象

优点:实现简单,维护方便。缺点:额外的内存消耗。可能造成短期的不一致(可以设置TTL)。

2.布隆过滤器。在客户端和Redis之间加个布隆过滤器(存在不一定存在,不存在一定不存在,有5%的错误率)。

优点:内存占用较少,没有多余key。缺点:实现复杂,存在误判可能。

解决商铺查询的缓存穿透问题使用的是第一种方法。

3.缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

缓存雪崩的常见解决方案

  • 给不同的Key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略  比如快速失败机制,让请求尽可能打不到数据库上
  • 给业务添加多级缓存

4.缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

缓存击穿的常见解决方案:

互斥锁(一致性)
优点:内存占用小,一致性高,实现简单
缺点:性能较低,容易出现死锁
逻辑过期(可用性)
优点:性能高
缺点:内存占用较大,容易出现脏读
两者相比较,互斥锁更加易于实现,但是容易发生死锁,且锁导致并行变成串行,导致系统性能下降,逻辑过期实现起来相较复杂,且需要耗费额外的内存,但是通过开启子线程重建缓存,使原来的同步阻塞变成异步,提高系统的响应速度,但是容易出现脏读。

4.1.基于互斥锁解决缓存击穿

平时用的锁没有拿到锁的话会一直等待,所以这里我们要选择自定义锁。

将缓存穿透的逻辑封装成queryWithPassThrough()方法,然后再实现互斥锁逻辑。(在queryById里面)。用JMeter软件进行高并发测试。

    /**
     * 获取锁
     *
     * @param key
     * @return
     */
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        // 拆箱要判空,防止NPE
        return BooleanUtil.isTrue(flag);
    }

    /**
     * 释放锁
     *
     * @param key
     */
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
  1. 使用Redis中的setnx指令实现互斥锁,只有当值不存在时才能进行set操作

4.2基于逻辑过期解决缓存击穿

所谓的逻辑过期,并不是真正意义上的过期,而是新增一个字段,用来标记key的过期时间,这样数据就永不过期了,从根本上解决因为热点key过期导致的缓存击穿。一般搞活动时,比如抢优惠券,秒杀等场景,请求量比较大就可以使用逻辑过期,等活动一过就手动删除逻辑过期的数据。

创建一个逻辑过期数据类:

@Data
public class RedisData {
    /**
     * 过期时间
     */
    private LocalDateTime expireTime;
    /**
     * 缓存数据
     */
    private Object data;
}

修改查询逻辑

 // 1.2 缓存命中,将JSON字符串反序列化未对象,并判断缓存数据是否逻辑过期
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        // 这里需要先转成JSONObject再转成反序列化
        JSONObject data = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(data, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())) {
            // 当前缓存数据未过期,直接返回
            return shop;
        }
 // 2、缓存数据已过期,获取互斥锁,并且重建缓存
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        if (isLock) {
            // 获取锁成功,开启一个子线程去重建缓存
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    this.saveShopToCache(id, CACHE_SHOP_LOGICAL_TTL);
                } finally {
                    unlock(lockKey);
                }
            });
        }
**
     * 将数据保存到缓存中
     *
     * @param id            商铺id
     * @param expireSeconds 逻辑过期时间
     */
    public void saveShopToCache(Long id, Long expireSeconds) {
        // 从数据库中查询店铺数据
        Shop shop = this.getById(id);
        // 封装逻辑过期数据
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        // 将逻辑过期数据存入Redis中
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值