redis08_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例(代码片段)

所得皆惊喜 所得皆惊喜     2022-12-26     465

关键词:

①. 缓存雪崩

  • ①. 问题的产生:缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同 数据都过期了,很多数据都查不到从而查数据库
    (redis主机挂了,Redis 全盘崩溃、比如缓存中有大量数据同时过期)

  • ②. 解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
  2. 设置热点数据永远不过期。
  3. 使用缓存预热
  • ③. 什么是缓存预热?
    缓存预热就是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓存

②. 缓存穿透

  • ①. 请求去查询一条记录,先redis后mysql发现都查询不到该条记录,但是请求每次都会打到数据库上面去,导致后台数据库压力暴增,这种现象我们称为缓存穿透,这个redis变成了一个摆设(redis中没有、mysql也没有)

  • ②. 解决方案一:空对象缓存或者缺省值

  1. 黑客会对你的系统进行攻击,拿一个不存在的id 去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕掉
  2. id相同打你系统:第一次打到mysql,空对象缓存后第二次就返回null了,避免mysql被攻击,不用再到数据库中去走一圈了
  3. id不同打你系统:由于存在空对象缓存和缓存回写(看自己业务不限死),redis中的无关紧要的key也会越写越多(记得设置redis过期时间)

  • ③. 解决方案二:Google布隆过滤器Guava解决缓存穿透(核心代码如下)
	//入门案列
    @Test
    public void bloomFilter()
	  // 创建布隆过滤器对象
      BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), 100);
	  // 判断指定元素是否存在
      System.out.println(filter.mightContain(1));
      System.out.println(filter.mightContain(2));
	  // 将元素添加进布隆过滤器
      filter.put(1);
      filter.put(2);
      System.out.println(filter.mightContain(1));
      System.out.println(filter.mightContain(2));
    
//取样本100W数据,查查不在100W范围内的其它10W数据是否存在
public class BloomfilterDemo 
    public static final int _1W = 10000;

    //布隆过滤器里预计要插入多少数据
    public static int size = 100 * _1W;
    //误判率,它越小误判的个数也就越少(思考,是不是可以设置的无限小,没有误判岂不更好)
    public static double fpp = 0.03;

    // 构建布隆过滤器
    //private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size);
    //不写误判率就用默认的0.03
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size);

    public static void main(String[] args) 
        //1 先往布隆过滤器里面插入100万的样本数据
        for (int i = 0; i < size; i++) 
            bloomFilter.put(i);
        

        //故意取10万个不在过滤器里的值,看看有多少个会被认为在过滤器里
        List<Integer> list = new ArrayList<>(10 * _1W);

        for (int i = size+1; i < size + 100000; i++) 
            if (bloomFilter.mightContain(i)) 
                System.out.println(i+"\\t"+"被误判了.");
                list.add(i);
            
        
        System.out.println("误判的数量:" + list.size());
    


  • ④. 解决方案三:Redis布隆过滤器解决缓存穿透
    Guava 提供的布隆过滤器的实现还是很不错的 (想要详细了解的可以看一下它的源码实现),但是它有一个重大的缺陷就是只能单机使用 ,而现在互联网一般都是分布式的场景
    为了解决这个问题,我们就需要用到Redis中的布隆过滤器了

  • ⑤. 针对方案三具体的案列说明:白名单过滤器

  1. 误判问题,但是概率小可以接受,不能从布隆过滤器删除
  2. 全部合法的key都需要放入过滤器+redis里面,不然数据就是返回null
public class RedissonBloomFilterDemo2 
    public static final int _1W = 10000;

    //布隆过滤器里预计要插入多少数据
    public static int size = 100 * _1W;
    //误判率,它越小误判的个数也就越少
    public static double fpp = 0.03;

    static RedissonClient redissonClient = null;
    static RBloomFilter rBloomFilter = null;

    static 
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.68.143:6379").setDatabase(0);
        //构造redisson
        redissonClient = Redisson.create(config);
        //通过redisson构造rBloomFilter
        rBloomFilter = redissonClient.getBloomFilter("phoneListBloomFilter",new StringCodec());
        rBloomFilter.tryInit(size,fpp);
        // 1测试  布隆过滤器有+redis有
        rBloomFilter.add("10086");
        redissonClient.getBucket("10086",new StringCodec()).set("chinamobile10086");

        // 2测试  布隆过滤器有+redis无
        rBloomFilter.add("10087");

        //3 测试 ,都没有

    

    public static void main(String[] args) 
        String phoneListById = getPhoneListById("10086");
        //String phoneListById = getPhoneListById("10086");
        //String phoneListById = getPhoneListById("10086");
        System.out.println("------查询出来的结果: "+phoneListById);

        //暂停几秒钟线程
        try  TimeUnit.SECONDS.sleep(1);  catch (InterruptedException e)  e.printStackTrace(); 
        redissonClient.shutdown();
    

    private static String getPhoneListById(String IDNumber) 
        String result = null;
        if (IDNumber == null) 
            return null;
        
        //1 先去布隆过滤器里面查询
        if (rBloomFilter.contains(IDNumber)) 
            //2 布隆过滤器里有,再去redis里面查询
            RBucket<String> rBucket = redissonClient.getBucket(IDNumber, new StringCodec());
            result = rBucket.get();
            if(result != null) 
                return "i come from redis: "+result;
            else
                result = getPhoneListByMySQL(IDNumber);
                if (result == null) 
                    return null;
                
                // 重新将数据更新回redis
                redissonClient.getBucket(IDNumber,new StringCodec()).set(result);
            
            return "i come from mysql: "+result;
        
        return result;
    

    private static String getPhoneListByMySQL(String IDNumber) 
        return "chinamobile"+IDNumber;
    

  • ⑥. 公司具体案列

③. 在centos7下布隆过滤器2种安装方式

  • ①. 方式一:采用docker安装RedisBloom,推荐

具体步骤:
(1). Redis 在 4.0 之后有了插件功能(Module),可以使用外部的扩展功能,可以使用 RedisBloom 作为 Redis 布隆过滤器插件
(2). docker run -p 6379:6379 --name=redis6379bloom -d redislabs/rebloom
(3). docker exec -it redis6379bloom /bin/bash
(4). redis-cli

[root@TANG2021 ~]# docker exec -it myredistz /bin/bash
root@459c7c8679b1:/data# redis-cli 
127.0.0.1:6379> keys *
1) "10086"
2) "phoneListBloomFilter:config"
3) "phoneListBloomFilter"
127.0.0.1:6379> type phoneListBloomFilter:config
hash
127.0.0.1:6379> hgetall phoneListBloomFilter:config
1) "size"
2) "7298440"
3) "hashIterations"
4) "5"
5) "expectedInsertions"
6) "1000000"
7) "falseProbability"
8) "0.03"
127.0.0.1:6379> 
  • ②. 布隆过滤器常用操作命令
    bf.add key 值
    bf.exists key 值
    bf.madd 一次添加多个元素
    bf.mexists 一次查询多个元素是否存在
  • ③. 方式二:编译安装
 
# 下载 编译 安装Rebloom插件
wget https://github.com/RedisLabsModules/rebloom/archive/v2.2.2.tar.gz
# 解压 
tar -zxvf v2.2.2.tar.gz
cd RedisBloom-2.2.2
# 若是第一次使用 需要安装gcc++环境
make
# redis服启动添加对应参数 这样写还是挺麻烦的
# rebloom_module="/usr/local/rebloom/rebloom.so"
# daemon --user $REDIS_USER-redis "$exec $REDIS_CONFIG --loadmodule # $rebloom_module --daemonize yes --pidfile $pidfile"
# 记录当前位置
pwd
# 进入reids目录 配置在redis.conf中 更加方便
vim redis.conf
# :/loadmodule redisbloom.so是刚才具体的pwd位置 cv一下
loadmodule /xxx/redis/redis-5.0.8/RedisBloom-2.2.2/redisbloom.so
# 保存退出
wq
# 重新启动redis-server 我是在redis中 操作的 若不在请写出具体位置 不然会报错
redis-server redis.conf
# 连接容器中的 redis 服务 若是无密码 redis-cli即可
redis-cli -a 密码
# 进入可以使用BF.ADD命令算成功

④. 缓存击穿

  • ①. 缓存击穿:大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去(简单说就是热点key突然失效了,暴打mysql)

  • ②. 危害:会造成某一时刻数据库请求量过大,压力剧增

  • ③. 方案一:互斥更新、随机退避、差异失效时间

  • ④. 方案2:对于访问频繁的热点key,干脆就不设置过期时间

  • ⑤. 方案3:互斥独占锁防止击穿

public User findUserById2(Integer id) 
    User user = null;
    String key = CACHE_KEY_USER+id;

    //1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql
    user = (User) redisTemplate.opsForValue().get(key);
    if(user == null) 
        //2 大厂用,对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
        synchronized (UserService.class)
            user = (User) redisTemplate.opsForValue().get(key);
            //3 二次查redis还是null,可以去查mysql了(mysql默认有数据)
            if (user == null) 
                //4 查询mysql拿数据
                user = userMapper.selectByPrimaryKey(id);//mysql有数据默认
                if (user == null) 
                    return null;
                else
                    //5 mysql里面有数据的,需要回写redis,完成数据一致性的同步工作
                    //setnx 不存在才创建
                    redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);
                
            
        
    
    return user;

⑤. 高并发的淘宝聚划算案例落地

  • ①. 什么是淘宝聚划算?

  • ②. 分析过程

步骤说明
1100%高并发,绝对不可以用mysql实现
2.先把mysql里面参加活动的数据抽取进redis,一般采用定时器扫描来决定上线活动还是下线取消
3支持分页功能,一页20条记录
  • ③. redis数据类型选型
  • ④. springboot+redis实现高并发的淘宝聚划算业务
@Data
@ApiModel(value = "聚划算活动producet信息")
public class Product 

    private Long id;
    /**
     * 产品名称
     */
    private String name;
    /**
     * 产品价格
     */
    private Integer price;
    /**
     * 产品详情
     */
    private String detail;

    public Product() 
    

    public Product(Long id, String name, Integer price, String detail) 
        this.id = id;
        this.name = name;
        this.price = price;
        this.detail = detail;
    

public class Constants 
    public  static final String JHS_KEY="jhs";
    public  static final String JHS_KEY_A="jhs:a";
    public  static final String JHS_KEY_B="jhs:b";

@Service
@Slf4j
public class JHSTaskService
    @Autowired
    private RedisTemplate redisTemplate;

    @PostConstruct
    public void initJHS()
        log.info("启动定时器淘宝聚划算功能模拟.........."+DateUtil.now());
        new Thread(() -> 
            //模拟定时器,定时把数据库的特价商品,刷新到redis中
            while (true)
                //模拟从数据库读取100件特价商品,用于加载到聚划算的页面中
                List<Product> list=this.products();
                //采用redis list数据结构的lpush来实现存储
                this.redisTemplate.delete(Constants.JHS_KEY);
                //lpush命令
                this.redisTemplate.opsForList().leftPushAll(Constants.JHS_KEY,list);
                //间隔一分钟 执行一遍
                try  TimeUnit.MINUTES.sleep(1);  catch (InterruptedException e)  e.printStackTrace(); 

                log.info("runJhs定时刷新..............");
            
        ,"t1").start();
    

    /**
     * 模拟从数据库读取100件特价商品,用于加载到聚划算的页面中
     */
    public List<Product> products() 
        List<Product> list=new ArrayList<>();
        for (int i = 1; i <=20; i++) 
            Random rand = new Random();
            int id= rand.nextInt(10000);
            Product obj=new Product((long) id,"product"+i,i,"detail");
            list.add(obj);
        
        return list;
    

@RestController
@Slf4j
@Api(description = "聚划算商品列表接口")
public class JHSProductController
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 分页查询:在高并发的情况下,只能走redis查询,走db的话必定会把db打垮
     * http://localhost:5555/swagger-ui.html#/jhs-product-controller/findUsingGET
     */
    @RequestMapping(value = "/pruduct/find",method = RequestMethod.GET)
    @ApiOperation("按照分页和每页显示容量,点击查看")
    public List<Product> find(int page, int size) 
        List<Product> list=null;
        long start = (page - 1) * size;
        long end = start + size - 1;
        try 
            //采用redis list数据结构的lrange命令实现分页查询
            list = this.redisTemplate.opsForList().range(Constants.JHS_KEY, start, end);
            if (CollectionUtils.isEmpty(list)) 
                //TODO 走DB查询
            
            log.info("查询结果:", list);
         catch (Exception ex) 
            //这里的异常,一般是redis瘫痪 ,或 redis网络timeout
            log.error("exception:", ex);
            //TODO 走DB查询
        
        return list;
    

  • ⑤. 上述存在哪些生产的问题?QPS上1000后导致可怕的缓存击穿
    (互斥更新、随机退避、差异失效时间)


  • ⑥. 互斥更新、随机退避

    redis12_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例(代码片段)

    文章目录①.缓存雪崩②.缓存穿透③.在centos7下布隆过滤器2种安装方式④.缓存击穿⑤.高并发的淘宝聚划算案例落地①.缓存雪崩①.问题的产生:缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至... 查看详情

    redis缓存雪崩缓存穿透缓存击穿

    ...分布式锁缓存穿透解决方案过滤非法查询缓存空对象布隆过滤器布隆过滤器的新增布隆过滤器的查询布隆过滤器的删除布隆过滤器解决缓存穿透布隆过滤器的特点缓存击穿解决方案设置热点Key永不过期使用Redis的分布式锁Redis缓... 查看详情

    什么是缓存穿透缓存雪崩及解决缓存穿透雪崩解决方案实现(代码片段)

    (目录)1、缓存穿透问题的解决思路缓存穿透:常见的解决方案有两种:缓存空对象布隆过滤缓存空对象思路分析:布隆过滤:①编码解决商品查询的缓存穿透问题:核心思路如下:在原来的逻辑中:现在的逻辑中:代码:/***查... 查看详情

    redis的缓存问题之缓存穿透缓存雪崩缓存击穿(代码片段)

    目录一、什么是缓存穿透?二、常见的解决方案有两种:1、缓存空对象2、布隆过滤综上所述三、编码解决商品查询的缓存穿透问题四、缓存雪崩问题及解决思路1、什么是缓存雪崩?五、缓存击穿问题及解决思路 1、... 查看详情

    redis——缓存穿透缓存击穿缓存雪崩

    ...也会导致这一段时间内的数据不一致问题。2)使用布隆过滤器简单地说就是在缓存前面加了一个过滤器,查询一个数据时布隆过滤器中存在才继续查询缓存,否则直接返回空值。注意,布隆过滤器可能误判(不存在的肯定不存... 查看详情

    redis的缓存穿透缓存雪崩缓存击穿问题的概念与解决办法(代码片段)

    ...穿透1.1什么是缓存穿透?1.2怎么解决1.3BloomFilter布隆过滤器1.3.1BloomFilter的原理1.3.2BloomFilter的优缺点1.3.3GuavaBloomFilter1.3.4RedisBloomFilter2缓存雪崩3缓存击穿4缓存预热5防止Redis宕机1缓存穿 查看详情

    redisredis缓存穿透和雪崩

    ...力,这时候就相当于出现了缓存穿透。解决方案布隆过滤器布隆过滤器是一种数据结构,对 查看详情

    redis缓存穿透击穿雪崩预热更新降级(代码片段)

    ...:提前过滤掉不合法的请求,可以使用Redis中布隆过滤器; 布隆过滤器设计思想:布隆过滤器由一个长度为m比特的位数组(bitarray)与k个哈希函数 查看详情

    缓存雪崩击穿穿透

    ...据同时过期Redis故障宕机二、缓存击穿三、缓存穿透布隆过滤器总结前言用户的数据一般都是存储于数据库,数据库的数据是落在磁盘上的,磁盘的读写速度可以说是计算机里最慢的硬件了。当用户的请求,都访问数... 查看详情

    缓存雪崩击穿穿透

    ...据同时过期Redis故障宕机二、缓存击穿三、缓存穿透布隆过滤器总结前言用户的数据一般都是存储于数据库,数据库的数据是落在磁盘上的,磁盘的读写速度可以说是计算机里最慢的硬件了。当用户的请求,都访问数... 查看详情

    redis中的缓存穿透雪崩击穿的原因以及解决方案(详解)(代码片段)

    ...;穿透解决方案:对空值进行缓存设置白名单使用布隆过滤器网警雪崩解决方案:进行预先的热门词汇的设置,进行key时长的调整实时调整,监控哪些数据是热门数据,实时的调整key的过期时长使用锁机制击穿... 查看详情

    硬核|redis布隆(bloomfilter)过滤器原理与实战(代码片段)

    ...缓存穿透、缓存雪崩怎么解决?中我们说到可以使用布隆过滤器避免「缓存穿透」。码哥,布隆过滤器还能在哪些场景使用呀?比如我们使用「码哥跳动」开发的「明日头条」APP看新闻,如何做到每次推荐给该用户的内容不会重... 查看详情

    redis学习笔记10:redis缓存穿透和雪崩

    ...,这时候就相当于出现了缓存穿透。解决方案:1.布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;2.缓存空对象但是这种方法会存在... 查看详情

    redis十五.缓存穿透与布隆过滤器原理(代码片段)

    目录一.布隆过滤简基础二.布隆过滤器原理三.redis缓存穿透Google布隆过滤器Guava解决缓存穿透Redis布隆过滤器解决缓存穿透(推荐)redis安装布隆过滤器插件四.模拟Guava编码通过redis实现布隆过滤器一.布隆过滤简基础首先了解一个需... 查看详情

    缓存击穿、穿透、雪崩及redis分布式锁

    ...术A分布式锁:setnx,redisson并发问题幂等问题:落表状态,Redis缓存击穿:指缓存中无,db中有原因:一个key高并发恰好失效导致大量请求到db方案:加锁,自旋锁,或一个线程查db,一个线程监控(直接用Redisson分布式锁)缓存穿透:指缓存和db中均... 查看详情

    redis缓存击穿(失效)缓存穿透缓存雪崩怎么解决?

    ...ff0c;QPS=10000左右,读写平均耗时10~100ms。用Redis作为缓存系统正好可以弥补DB的不足,「码哥」在自己的MacBookPro2019上执 查看详情

    redis学习笔记26——缓存异常:如何解决缓存雪崩击穿穿透问题?

    缓存雪崩缓存雪崩是指大量的应用请求无法在Redis缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。缓存雪崩一般有两个原因导致:1、缓存中有大量数据同时过期解决方法... 查看详情

    redis学习笔记26——缓存异常:如何解决缓存雪崩击穿穿透问题?

    缓存雪崩缓存雪崩是指大量的应用请求无法在Redis缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。缓存雪崩一般有两个原因导致:1、缓存中有大量数据同时过期解决方法... 查看详情