20210609-Redis(缓存穿透及雪崩问题)

一.首先,先从这两个问题出发,在一般的中小型软件企业中,很难遇到这种问题,然而如果遇到大型项目有着几百万的流量,那么这个问题就应该深刻考虑了

  1.在分析问题之前,让我们先弄明白什么是缓存穿透和缓存雪崩问题?

    *缓存穿透:就是针对系统请求缓存中不存在的数据,导致所有的请求都指向数据库,从而导致数据库连接异常

    *缓存雪崩:就是在缓存同一时间大面积失效,此时又来了一波请求,结果请求全部指向数据库,从而导致数据库连接异常

二.针对上述问题(解决方案)

  *缓存穿透:

    1.利用互斥锁:缓存失效的时候,先去获得锁,当得到锁后,再去请求数据库,没得到锁,则先休眠一段时间后再重试

    2.采用异步更新策略:无论key是否取到值都直接返回,value值中维护一个缓存失效时间,如果缓存过期,异步开启一个线程去读取数据库,更新缓存,这里需要做缓存预热(项目启动前,先加载缓存)操作

    3.提供一个能迅速判断请求是否有效的拦截器,比如:利用布隆过滤器维护一系列合法有效的key,然后迅速判断出请求中所携带的key是否合法有效,如果不合法,则直接返回

  *缓存雪崩:

    1.给定缓存的失效时间,加上一个随机值,避免集体失效

    2.使用互斥锁,但是互斥锁的吞吐量明显下降了

    3.双缓存,假设我们有两个缓存,缓存A,缓存B,缓存A的失效时间为20分钟,缓存B不设失效时间,自己做缓存预热操作,流程细分为以下几点:

      *I 从缓存A读数据库,有则直接返回

      *II A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程

      *III 更新线程同时更新缓存A和缓存B

三.针对上述解决方案进行实现

1.缓存击穿(互斥锁方案) 

/**
 * 分布式锁获取数据,解决缓存击穿
 * @param key
 * @return
 * @throws InterruptedException
 */
public Object getData(String key) throws InterruptedException {
	//从redis查询数据
	Object result = get(key);
	ReentrantLock reentrantLock = new ReentrantLock();
	if(null == result){
		//获取锁
		if(reentrantLock.tryLock()){
			result = get(key);
			if(null == result) {
				//存缓存
				set(key,result);
			}
			//释放锁
			reentrantLock.unlock();
		}else {
			//睡一会再拿
			Thread.sleep(100L);
			result = getData(key);
		}
	}
	return result;
}

2.采用异步更新策略基于订阅binlog的同步机制

  *技术整体思路:

    1.MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

      *读redis:热数据都在redis上

      *写mysql:增删改都是操作mysql

      *更新redis数据:mysql的数据操作binlog,来更新redis

    2.Redis更新

      *数据操作主要分为两大块:一个是全量(将全部数据一次写入到redis)一个是增量(实时更新)这里的增量是指mysql的 更新、插入、删除,变更数据

      *读取binlog后分析:利用消息队列,推送更新各台redis的缓存数据,这样一旦mysql中产生了新数据的写入、更新、删除等操作时,既可以把binlog相关的消息推送至redis中,redis在根据binlog中的记录,对redis进行更新。

        这一机制类似于mysql的主从备份机制,因为mysql的主备份也是通过binlog来实现的数据的一致性

 

2.提供一个能迅速判断请求是否有效的拦截器布隆过滤器

  *首先要提一下布隆过滤器的特点:

    1.布隆过滤器是用来判断一个元素是否出现在给定集合中的重要工具,具有快速,比哈希表更节省空间等优点,而缺点在于有一定的误识别率(false-positive,假阳性),亦即,它可能会把不是集合内的元素判定为存在于集合内,不过这样的概率相当小,在大部             分的生产环境中是可以接受的

  *redis中实现布隆过滤器:(通过Redission来构造布隆过滤器)

    1.设置值:setbit key offset value

    2.获取值:gitbit key offset

    3.获取位图指定范围为 1的个数:bitcount key [start end]

package com.ys.rediscluster.bloomfilter.redisson;

import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedissonBloomFilter {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.14.104:6379");
        config.useSingleServer().setPassword("123");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%
        bloomFilter.tryInit(100000000L,0.03);
        //将号码10086插入到布隆过滤器中
        bloomFilter.add("10086");

        //判断下面号码是否在布隆过滤器中
        System.out.println(bloomFilter.contains("123456"));//false
        System.out.println(bloomFilter.contains("10086"));//true
    }
}

  

    

posted @ 2021-06-09 00:47  firefox7557  阅读(7)  评论(0)    收藏  举报