Redis学习篇-缓存穿透,缓存击穿,缓存雪崩

前言:

随着业务的发展,可能出现了大量数据的请求,在这个时候,如果所有的请求都涌入数据库,就会造成数据库的压力增大,一些简单的sql查询因为数据库承受大量压力的而几何式变慢,甚至造成瘫痪。

因为,为了解决这个问题,引入了nosql,而redis则是nosql技术中的一种。

但是引入nosql,则会引入缓存穿透,缓存击穿,缓存雪崩等问题,因此,本章则会关于这几个问题,说说我自己的理解和解决。

本文所引用redis结构是一主二从三哨兵。

一.是什么

既然引入redis会引入缓存穿透,缓存击穿,缓存雪崩等,我们先来解释一下这个几个东西是什么。

 缓存穿透:通过请求一个无论redis还是数据库都不存在的key,因此请求访问redis都是null,转而请求数据库,还是造成了大量请求同时间到达了数据库,进而起到了压垮了数据库的作用。

缓存击穿:对于某一个热点key,不停地被高并发访问,但是一旦该热点key过期了,这时候成千上万的请求立马访问到数据库一层,起到压垮数据库的作用。

缓存雪崩:在某一个时间段,大量缓存集体过期,这个时候,由于缓存过期,所有的请求压力转移后端去,数据库去。

二.怎么解决

2.1缓存穿透解决办法

面对缓存穿透比较常用的一种办法就是使用布隆过滤器(Bloom Fliter),通过数据哈希存储到一个巨大的bitmap中,从而避免进行到数据库中。另外也有一种简单的解决办法就是把不存在key的空缓存也缓存到redis里面去,设置好过期时间,最好不超过5分钟。

根据网上找到布隆过滤器,根据guava里面的布隆过滤器进行改造,方便分布式使用。

public class BloomFilterHelper<T> {

    private int numHashFunctions;

    private int bitSize;

    private Funnel<T> funnel;

    /**
     * <p>
     * 1.构造函数判断Funnel是否为空,赋值。
     * 2.计算bit的大小,
     * 3.需要哈希次数
     * </p>
     *
     * @param funnel
     * @param expectedInsertions
     * @param fpp -》false positive
     */
    public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
        Preconditions.checkArgument(funnel != null, "funnel不能为空");
        this.funnel = funnel;
        bitSize = optimalNumOfBits(expectedInsertions, fpp);
        //计算hash次数
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
    }


    /**
     * 计算hashmap -使用murmur3-128
     * @param value
     * @return
     */
    public int[] murmurHashOffset(T value) {
        int[] offset = new int[numHashFunctions];

        long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
        int hash1 = (int) hash64;
        int hash2 = (int) (hash64 >>> 32);
        for (int i = 1; i <= numHashFunctions; i++) {
            int nextHash = hash1 + i * hash2;
            if (nextHash < 0) {
                nextHash = ~nextHash;
            }
            offset[i - 1] = nextHash % bitSize;
        }

        return offset;
    }

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

    /**
     * 计算hash方法执行次数
     */
    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }
}

redis环境注册

@Configuration
public class RedisConfig {
    @Autowired
    private RedisTemplate redisTemplate;

    @Bean
    public RedisTemplate redisTemplateInit() {
        //设置序列化Key的实例化对象
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    /**
     * <注册BloomFilterHelper>
     *
     * @param
     * @return com.zy.crawler.config.redis.BloomFilterHelper<java.lang.String>
     * @author Lifeifei
     * @date 2019/4/8 13:18
     */
    @Bean
    public BloomFilterHelper<String> initBloomFilterHelper() {
        return new BloomFilterHelper<>((Funnel<String>) (from, into) -> into.putString(from, Charsets.UTF_8)
                .putString(from, Charsets.UTF_8), 1000000, 0.01);
    }
}

RedisService

@Service
public class RedisService {
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据给定的布隆过滤器添加值
     */
    public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            redisTemplate.opsForValue().setBit(key, i, true);
        }
    }

    /**
     * 根据给定的布隆过滤器判断值是否存在
     */
    public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            if (!redisTemplate.opsForValue().getBit(key, i)) {
                return false;
            }
        }

        return true;
    }

}

redis里面通过setBit进行存储,因此即便某个key不存在,可以直接通过布隆过滤器查询不同hash出的bit是否都存在。从而解决了问题。

2.2缓存击穿解决方法

缓存击穿一般是因为某个热点key过期后,成千上万的请求突然转向数据库增大数据库的压力。因此一般的做法的加个互斥锁(mutex).一旦某个热点过期了,设置互斥锁,第一个获取锁的重新设置热点值,后面的请求则休眠一秒,等待第一个请求设置了热点值后,重新获取热点值。

这是在serviceimpl的方法

@Override
    public String getTalkingPoint(String key) {
        String talkingPointValue = (String)redisTemplate.opsForValue().get(key);
        if(talkingPointValue ==null){
            //互斥锁
            if(redisTemplate.opsForValue().setIfAbsent(key+"_mutex","key_mutex",3,TimeUnit.MINUTES)){
                //从数据库查询
                HighTecoEntity byId = this.getById(1);
                redisTemplate.opsForValue().set(key,byId.getUser(),3,TimeUnit.MINUTES);
                return byId.getUser();
            }else{
                //拿不到互斥锁,休眠一秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    log.info("获取失败");
                    return null;
                }
                String getTalkingValueByRedis = (String) redisTemplate.opsForValue().get(key);
                return null;
            }
        }
        return talkingPointValue;
    }

2.3缓存雪崩

缓存雪崩相对缓存击穿而言,是单个key值过期和N个key值同时过期,这时短时间的大量数据读写操作极大可能导致数据库垮掉。我们可以选择通过随机因子把不同类型的key分散开来,除此之外,还可以增加其过期时间,即原本是30min,后面可以设置成60min,防止 大规模key的过期。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值