Redis缓存穿透、缓存击穿、缓存雪崩

redis 专栏收录该内容
6 篇文章 0 订阅

1、基本概念

图片

2、缓存穿透解决方案

  • 缓存空对象(代码接单,效果不好),导致大量的空对象数据
  • 布隆过滤器(代码复杂,效果很好,一般使用这个) 原理 手写分布式的布隆过滤器

①、比如根据用户ID查询用户的信息

public RestResponse<User> getUserInfo(String userId) {
        //缓存空对象(代码接单,效果不好),导致大量的空对象数据
        //先查询redis缓存
        Object redisUser = redisUtil.getValue(userId);
        //命中缓存
        if(redisUser != null){
            if(redisUser instanceof NullValueResultVO){
                ResultGenerator.genSuccessResult(new NullValueResultVO(),"查询无果");
            }
            //正常返回数据
            return ResultGenerator.genSuccessResult(redisUser);
        }
        try{
            User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getId, userId));
            if(user != null){
                //放入缓存中 并返回
                redisUtil.setValue(userId, user,10L, TimeUnit.MINUTES);
                return ResultGenerator.genSuccessResult(user);
            }else{
                redisUtil.setValue(userId, new NullValueResultVO(),10L, TimeUnit.MINUTES);
            }
        }finally {

        }
        return ResultGenerator.genFailResult("查询无果");
    }
NullValueResultVO类-空对象
package com.example.demo.vo;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

import java.io.Serializable;

/**
 * @description 空对象
 * @author lst
 * @date 2021/2/20
 */
@Data
@JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler" })
public class NullValueResultVO implements Serializable {
}

②、引用布隆过滤器

一、谷歌的布隆过滤器组件

引入pom

        <!-- 谷歌的布隆过滤器组件 -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>27.0.1-jre</version>
        </dependency>

测试类

package com.example.demo.controller;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

import java.util.ArrayList;
import java.util.List;

/**
 * @author lst
 * @description 谷歌的布隆过滤器组件
 * @date 2021/2/20
 */
public class TestBloomFilter {

    private static int size = 1000000;

    /**
     * size 预计要插入多少数据
     * fpp容错率
     * BloomFilter 位数据
     *
     */

    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(),size,0.001);

    public static void main(String[] args) {
        for (int i = 1; i <= size; i++){
            bloomFilter.put(i);
        }
        List<Integer> list = new ArrayList<>(10000);
        //故意取10000个不在过滤器里的值,看看有多少个会被认为在过滤器里
        for (int i = size + 10000; i < size + 20000; i++){
            //误判
            if(bloomFilter.mightContain(i)){
                list.add(i);
            }
        }

        System.out.println("误判的数量: " + list.size());
        System.out.println("误判的数量数据: " + list);
    }

}

二、手写过滤器

RedisBloomFilter类
package com.example.demo.utils;

import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
import java.util.List;

/**
 * @author lst
 * @description 布隆过滤器
 * @date 2021/2/22
 *
 */
@ConfigurationProperties("bloom.filter")
@Component
public class RedisBloomFilter {

    //预计插入量
    private long expectedInsertions;

    //可接受的错误率
    private double fpp;

    @Autowired
    private RedisTemplate redisTemplate;


    //bit数组长度
    private long numBits;
    //hash函数数量
    private int numHashFunctions ;

    public long getExpectedInsertions() {
        return expectedInsertions;
    }

    public void setExpectedInsertions(long expectedInsertions) {
        this.expectedInsertions = expectedInsertions;
    }

    public void setFpp(double fpp) {
        this.fpp = fpp;
    }

    public double getFpp() {
        return fpp;
    }

    @PostConstruct
    public void init(){
        this.numBits = optimalNumOfBits(expectedInsertions, fpp);
        this.numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    }

    //计算hash函数个数
    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }

    //计算bit数组长度
    private long optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }

    /**
     * 判断keys是否存在于集合
     */
    public boolean isExist(String key) {
        long[] indexs = getIndexs(key);
        List list = redisTemplate.executePipelined(new RedisCallback<Object>() {

            @Nullable
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                redisConnection.openPipeline();
                for (long index : indexs) {
                    redisConnection.getBit("bf:hilite".getBytes(), index);
                }
                redisConnection.close();
                return null;
            }
        });
        return !list.contains(false);
    }

    /**
     * 将key存入redis bitmap
     */
    public void put(String key) {
        long[] indexs = getIndexs(key);
        redisTemplate.executePipelined(new RedisCallback<Object>() {

            @Nullable
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                redisConnection.openPipeline();
                for (long index : indexs) {
                    redisConnection.setBit("bf:hilite".getBytes(),index,true);
                }
                redisConnection.close();
                return null;
            }
        });
    }

    /**
     * 根据key获取bitmap下标
     */
    private long[] getIndexs(String key) {
        long hash1 = hash(key);
        long hash2 = hash1 >>> 16;
        long[] result = new long[numHashFunctions];
        for (int i = 0; i < numHashFunctions; i++) {
            long combinedHash = hash1 + i * hash2;
            if (combinedHash < 0) {
                combinedHash = ~combinedHash;
            }
            result[i] = combinedHash % numBits;
        }
        return result;
    }

    /**
     * 获取一个hash值
     */
    private long hash(String key) {
        Charset charset = Charset.forName("UTF-8");
        return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
    }
}

先初始化布隆过滤器,将数据放入过滤器中,正常项目可以在添加的时候添加,删除的时候可以做一个定时删除再重建布隆过滤器。

/**
     * @Description 初始化 讲数据放入布隆过滤器中
     * @author lst
     * @date 2021/2/22 15:45
     */
    public void init(){
        QueryWrapper queryWrapper = new QueryWrapper();
        List<User> userList =  userMapper.selectList(queryWrapper);
        userList.stream().forEach(user -> {
            redisBloomFilter.put(user.getId());
        });
    }
@Override
    public RestResponse<User> getUserInfo(String userId) {
        //解决缓存穿透
        if(!redisBloomFilter.isExist(userId)){
            ResultGenerator.genFailResult("非法访问");
        }

        //先查询redis缓存
        Object redisUser = redisUtil.getValue(userId);
        //命中缓存
        if(redisUser != null){
            //正常返回数据
            return ResultGenerator.genSuccessResult(redisUser);
        }
        try{
            User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getId, userId));
            if(user != null){
                //放入缓存中 并返回
                redisUtil.setValue(userId, user,10L, TimeUnit.MINUTES);
                return ResultGenerator.genSuccessResult(user);
            }
        }finally {

        }
        return ResultGenerator.genFailResult("查询无果");
    }

3、缓存击穿

参考分布式锁:Redis分布式锁zookeeper实现分布式锁

 

 

 

 

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值