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
}
}

浙公网安备 33010602011771号