保护私人版权,尊重他人版权。转载请注明出处并附带页面链接
缓存穿透
定义
缓存穿透,是指查询一个数据库一定不存在的数据。 正常的使用缓存流程大致是,数据查询先进行缓存查询,当key不存在或者key已经过期,再对数据库进行查询,并把查询到的数据放进缓存。如果数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决
对查询结果为空的情况也进行缓存,缓存时间设置短一点,
注意:该key对应的数据insert了之后清理缓存
布隆过滤器——对一定不存在的key进行过滤。把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤,从而避免了对底层数据库的查询压力。
缓存雪崩
定义
指在设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,导致所有的查询都落在数据库上,造成了缓存雪崩。
解决
缓存失效后,通过加锁或者队列控制读数据库写缓存的线程数。比如对某个key只允许一个线程查询和写缓存,其他线程等待。
缓存reload机制,预先去更新缓存,在即将发生大并发访问前手动触发加载缓存。
不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
二级缓存。A1为原始缓存,A2为拷贝缓存,A1缓存失效时间设置为短期,A2设置为长期,当A1失效时,A1加锁只有 1 个线程获取到锁,这个线程查询数据库并更新到到 A1 缓存和 A2 缓存中, 其他线程从A2获取数据返回
注意:主要是通过避免缓存同时失效并结合锁机制实现。 A2缓存中可能会存在脏数据,需要业务能够容忍这种短时间的不一致。而且,这种方案可能会造成额外的缓存空间浪费。
缓存击穿
定义
由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透
是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库可能瞬间压垮DB。
和缓存雪崩的区别:缓存击穿针对某一key缓存,缓存雪崩则是很多key
解决
- 起定时任务专门主动更新缓存数据——永不过期
注:这种方案比较容易理解,但会增加系统复杂度。比较适合那些 key 相对固定,cache 粒度较大的业务,key 比较分散的则不太适合,实现起来也比较复杂。
检查更新。将缓存key的过期时间(绝对时间)一起保存到缓存中,获取缓存时把缓存过期时间和当前系统时间相减,当小于某个阈值时主动更新(阶段性时间点高并发不适合)——永不过期
二级缓存。A1为原始缓存,A2为拷贝缓存,A1缓存失效时间设置为短期,A2设置为长期,当A1失效时,A1加锁只有 1 个线程获取到锁,这个线程查询数据库并更新到到 A1 缓存和 A2 缓存中, 其他线程从A2获取数据返回
互斥锁: 在缓存失效的时候,创建分布式锁成功后,再进行查询数据库并回设缓存;加锁失败则重试整个get缓存的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function getRedis($key) {
// 从缓存读取数据
$result = Redis::get($key);
if (empty($result)) { //setex set(key, value, exTime); 60s, 第二次成功
if (Redis::setnx($key. ':lock', 1, 10)) { //第二次失败
//加锁成功,从DB获取数据库后写入缓存;
$result = getDataFromDB();
Redis::set($key, $result);
Redis::del($key. ':lock');// 释放锁
} else {
usleep(5000);
return Redis::get($key);
}
}
return $result;
}提前互斥锁: 在value内部设置小于真实过期的时间的超时值timeout1。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31function getRedis($key, $timeout = 60) {
// 从缓存读取数据
$result = Redis::get($key);
if (empty($result)) {
if (Redis::setnx($key. ':lock', 1, 3 * 60)) {
//加锁成功,从DB获取数据库后写入缓存;
$result = getDataFromDB();
Redis::set($key, $result);
Redis::del($key. ':lock');// 释放锁
} else {
usleep(5000);
return Redis::get($key);
}
} else if ($result['timeout'] <= time()) {
if (Redis::setnx($key. ':lock', 1, 3 * 60)) {
//延长timeout并重新保存
$result['timeout'] += 3 * 60 * 1000;
Redis::set(key, $result, $timeout * 2);
//加锁成功,从DB获取数据库后写入缓存;
$result = getDataFromDB();
$result['timeout'] = $timeout;
Redis::set($key, $result);
Redis::del($key. ':lock');// 释放锁
} else {
usleep(5000);
return Redis::get($key);
}
}
return $result;
}