Redis之缓存穿透&缓存击穿&缓存雪崩

本文介绍了缓存系统中常见的三种问题——缓存穿透、缓存击穿和缓存雪崩,并提供了相应的解决方案。对于缓存穿透,提出了接口校验、布隆过滤器和缓存空值的方法;缓存击穿则可以通过设置永不过期的key、使用互斥锁或队列排队来解决;缓存雪崩的对策包括设置热点数据不过期、随机过期时间和加锁排队。文章通过代码示例详细解释了这些策略的实现。
摘要由CSDN通过智能技术生成

在这里插入图片描述

1.缓存穿透

一个在缓存和数据库都不存在的数据,而用户不断发起请求,借此攻击数据库,造成数据库压力过大。比如请求 id < 0 的数据。
解决方案:
1.接口校验、限流;
2.布隆过滤器;
3.缓存空值,设置过期时间短些

package com.yzm.redis12.controller;

import com.yzm.redis12.entity.Product;
import com.yzm.redis12.service.ProductService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class RedisController {

    private final StringRedisTemplate stringRedisTemplate;
    private final ProductService productService;

    public RedisController(StringRedisTemplate stringRedisTemplate, ProductService productService) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.productService = productService;
    }

    @GetMapping("/penetrate")
    public String cachePenetrate(Integer id) {
        String cacheKey = "penetrate:" + id;
        long cacheTime = 30L;

        //缓存查询
        String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
        if (cacheValue == null) {
            //缓存没有,查询数据库
            Product product = productService.getById(id);
            if (product == null) {
                //数据库没有,设置空值或默认值
                cacheValue = "";
            } else {
                cacheValue = product.getName();
            }
            stringRedisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime, TimeUnit.SECONDS);
        }

        return cacheValue;
    }
}

2.缓存击穿

一个key数据库存在,原来缓存有但现在过期了,发送请求发现过期了直接查询数据库并回设缓存,此时若发生高并发可能会瞬间把数据库压垮。
解决方案:
1.设置key永不过期;
2.队列排队;
3.互斥锁;

    @GetMapping("/puncture")
    public String cachePuncture(Integer id) {
        String cacheKey = "puncture:" + id;
        long cacheTime = 30L;

        //缓存查询
        String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
        if (cacheValue == null) {
            //缓存没有,使用互斥锁查询数据库更新缓存,其余阻塞排队
            synchronized (cacheKey) {
                //此时可能有缓存数据了
                cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
                if (cacheValue == null) {
                    //缓存还是没有,查询数据库
                    Product product = productService.getById(id);
                    cacheValue = product.getName();
                    //回设缓存
                    stringRedisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 10, TimeUnit.SECONDS);
                }
            }
        }

        return cacheValue;
    }

    public String cachePuncture2(Integer id) throws InterruptedException {
        String cacheKey = "puncture:" + id;
        String blockKey = "block:" + id;
        long cacheTime = 30L;

        //缓存查询
        String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
        if (cacheValue == null) {
            //setIfAbsent == SETNX :只有key不存在的时候才能设置成功,利用它可以实现锁的效果
            if (stringRedisTemplate.opsForValue().setIfAbsent(blockKey, "1", cacheTime, TimeUnit.SECONDS)) {
                //查询数据库
                Product product = productService.getById(id);
                cacheValue = product.getName();
                //回设缓存
                stringRedisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 10, TimeUnit.SECONDS);
                stringRedisTemplate.delete(blockKey);
            } else {
                //阻塞一会,再重试获取数据
                Thread.sleep(50);
                return cachePuncture2(id);
            }
        }

        return cacheValue;
    }

3.缓存雪崩

当缓存服务器重启或者大量缓存集中在某一时间段失效,这样在失效的时候,也会给数据库带来很大压力。跟缓存击穿不一样,雪崩是大量key集体过期。
解决方案:
1.热点数据不过期;
2.在原有的失效时间基础上增加一个随机值,减少集体失效
3.加锁排队;

    @GetMapping("/avalanche")
    public String cacheAvalanche(Integer id) {
        String cacheKey = "avalanche:" + id;
        long cacheTime = 30L;

        //缓存查询
        String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
        if (cacheValue == null) {
            //缓存没有,使用互斥锁查询数据库更新缓存,其余阻塞排队
            synchronized (cacheKey) {
                //此时可能有缓存数据了
                cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
                if (cacheValue == null) {
                    //缓存还是没有,查询数据库
                    Product product = productService.getById(id);
                    cacheValue = product.getName();
                    //回设缓存
                    stringRedisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 10, TimeUnit.SECONDS);
                }
            }
        }

        return cacheValue;
    }

加锁排队在分布式环境中,有可能还要解决分布式锁的问题;
线程还会被阻塞,用户体验很差

4.设置过期标志更新缓存

    @GetMapping("/avalanche2")
    public String cacheAvalanche2(Integer id) {
        String cacheKey = "avalanche:" + id;
        String signKey = "avalanche:sign" + id;
        long cacheTime = 60L;

        //缓存查询
        String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
        //缓存标记
        String signValue = stringRedisTemplate.opsForValue().get(signKey);
        if (signValue == null) {
            //缓存标记过期
            //设置成功的去查询数据库并更新缓存,其余的返回旧的缓存值(缓存值的时间是缓存标记的2倍)
            if (stringRedisTemplate.opsForValue().setIfAbsent(signKey, "1", cacheTime, TimeUnit.SECONDS)) {
                //查询数据库
                Product product = productService.getById(id);
                cacheValue = product.getName();
                stringRedisTemplate.opsForValue().set(cacheKey, cacheValue, cacheTime * 2, TimeUnit.SECONDS);
            }
        }

        return cacheValue;
    }

缓存标记:记录缓存数据是否过期,如果过期就去更新实际key的缓存;
缓存数据:它的过期时间比缓存标记的时间延长1倍。这样,当缓存标记过期后,实际缓存还能把旧数据返回给调用端,直到新的key值更新完成后,才会返回新缓存。

相关链接

首页
上一篇:布隆过滤器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值