5.1 缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于 并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力 瞬间增大,造成过大压力。
有些数据是错误数据没有必要查库的数据,例如,有些瞎搞的人输入错误的订单号,这种情况下我们可以采用布隆过滤来做验证,防止击穿,去查库。
guava工具包中提供了布隆过滤的方法。
布隆过滤技术主要是利用hash去做映射,具体的细节,就不在这细说了。
接下来看一下简单的代码应用:
@PostConstruct //对象创建后,自动调用本方法
public void init(){//在bean初始化完成后,实例化bloomFilter,并加载数据
List<Provinces> provinces = this.list();
//当成一个SET----- 占内存,比hashset占得小很多
bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), provinces.size());// 32个
for (Provinces p : provinces) {
bf.put(p.getProvinceid());
}
}
@Cacheable(value = "province")
public Provinces detail(String provinceid) {
//先判断布隆过滤器中是否存在该值,值存在才允许访问缓存和数据库
if(!bf.mightContain(provinceid)){
System.out.println("非法访问--------"+System.currentTimeMillis());
return null;
}
System.out.println("数据库中得到数据--------"+System.currentTimeMillis());
Provinces provinces = super.detail(provinceid);
return provinces;
}
5.2 缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压 力过大甚至 down 机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩 是不同数据都过期了,很多数据都查不到从而查数据库。
可以简单的理解为:大量的击穿造成雪崩。
解决方案:
对操作数据库枷锁,只让一个线程去查库,查到数据后返回更新到缓存redis中,然后其他人就可以从缓存中获取,无需再去查库。
此时需要对数据库的查询操作,加锁 ---- lock (因考虑到是对同一个参数数值上 一把锁,此处 synchronized 机制无法使用) 加锁的标准流程代码如下(一样解决击穿的问题):
/**
* 缓存雪崩
*/
//@Service("provincesService")
public class ProvincesServiceImpl3 extends ProvincesServiceImpl implements ProvincesService{
private static final Logger logger = LoggerFactory.getLogger(ProvincesServiceImpl3.class);
@Resource
private CacheManager cm;
private ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();//线程安全的
private static final String CACHE_NAME = "province";
public Provinces detail(String provinceid) {
// 1.从缓存中取数据
Cache.ValueWrapper valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);
if (valueWrapper != null) {
logger.info("缓存中得到数据");
return (Provinces) (valueWrapper.get());
}
//2.加锁排队,阻塞式锁---100个线程走到这里---同一个sql的取同一把锁
doLock(provinceid);//32个省,最多只有32把锁,1000个线程
try{//第二个线程进来了
// 一次只有一个线程
//双重校验,不加也没关系,无非是多刷几次库
valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);//第二个线程,能从缓存里拿到值?
if (valueWrapper != null) {
logger.info("缓存中得到数据");
return (Provinces) (valueWrapper.get());//第二个线程,这里返回
}
Provinces provinces = super.detail(provinceid);
// 3.从数据库查询的结果不为空,则把数据放入缓存中,方便下次查询
if (null != provinces){
cm.getCache(CACHE_NAME).put(provinceid, provinces);
}
return provinces;
}catch(Exception e){
return null;
}finally{
//4.解锁
releaseLock(provinceid);
}
}
private void releaseLock(String userCode) {
ReentrantLock oldLock = (ReentrantLock) locks.get(userCode);
if(oldLock !=null && oldLock.isHeldByCurrentThread()){
oldLock.unlock();
}
}
private void doLock(String lockcode) {//给一个搜索条件,对应一个锁
//provinceid有不同的值,参数多样化
//provinceid相同的,加一个锁,---- 不是同一个key,不能用同一个锁
ReentrantLock newLock = new ReentrantLock();//创建一个锁
Lock oldLock = locks.putIfAbsent(lockcode, newLock);//若已存在,则newLock直接丢弃
if(oldLock == null){
newLock.lock();
}else{
oldLock.lock();
}
}
}