缓存穿透:
发生原因:无意/恶意 大量访问数据库和缓存中都没有的数据key,缓存没有特殊处理时,数据库中没有的也不会去加入到缓存,此时缓存相当于失效,DB压力增大,严重时可能导致DB宕机;
解决方式:
-
1.后端不能信任前端传递的参数,保持不信任的心,去验证参数,给不存在的key,定义默认值/空值(例如 稍后重试,或者403,404等等...通用或者自定义状态码或者看具体的场景)缓存有效时间可以设置短点,太长可能导致正常情况出问题;
- 1-1.正常用户一般不会在几秒内产生特别大量的请求,因此还可以在反向代理服务器(例如Nginx,Apache TS,HAProxy,Varnish 等等...)加以控制,使用nginx限制某些异常ip,或者基于nginx的Openresty可伸缩的Web平台中可以使用Lua脚本筛查ip;
- 2.布隆过滤器(Bloom Filter):可以检查到某key一定不存在或可能存在,我们只需要知道一定不存在就可以满足需求;
缓存击穿:
发生原因:指一个高热点Key,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞;
解决方式:
- 1.设置热点数据永远不过期;
-
2.锁更新
synchronized关键字:
public synchronized String getCacheData() { // Read redis String cacheData = getDataFromRedis(); if (StringUtils.isEmpty(cacheData )) { // Read database cacheData = getDataFromDB(); // Write redis setDataToCache(cacheData); } return cacheData; }
synchronized 这个锁太宽泛,会造成大量的请求阻塞,性能极低,进一步优化缩小锁的范围
private static final byte[] lock = new byte[0]; public String getCacheData() { String cacheData = getDataFromRedis(); if (cacheData.isEmpty()) { synchronized (lock) { cacheData = getDataFromDB(); setDataToCache(cacheData); } } return cacheData; }
还可以使用互斥锁优化
public String getCacheData() { String result = getDataFromRedis(); if (result.isEmpty()) { if (reenLock.tryLock()) { try { //Read database result = getDataFromDB(); //Write redis setDataToCache(result); }catch(Exception e){ e.printStackTrace(); }finally { reenLock.unlock (); // release lock } return result; } } result = getDataFromRedis(); if (result.isEmpty()) { try { Thread.sleep(100); return getCacheData(); } catch (InterruptedException e) { e.printStackTrace(); } } return result; }
-
3.异步更新:
把缓存设置为永久不过期,异步定时更新缓存。比如后台有个值守线程专门定时更新缓存,但一般还要定时频繁地去检测缓存,一旦发现被踢掉(比如被缓存的失效策略 FIFO、LFU、LRU 等)需要立刻更新缓存,但这个“定时”的度是比较难掌握的,实现简单但用户体验一般;
其实异步更新机制比较适合缓存预热,缓存预热是指系统上线时,将相关的缓存数据直接加载到缓存系统,避免在用户请求时才缓存数据,提高了性能;
缓存雪崩:
发生原因:因为大量的缓存同时失效,高并发都访问了DB,可能因此打崩了DB;如果没用什么特别的方案来处理这个故障,即使重启数据库,也可能会被持续的高并发给打死,几乎是灾难性的
解决方式:
-
1.集群:
高可用方案的本质就是冗余,集群是其实现方式之一,使用集群可以避免服务单点故障,但集群也带来了复杂度,好在很多成熟的中间件都有稳妥的集群方案,比如 Redis 集群; -
2.差异性过期时间:
根据业务特点随机生成过期时间,让过期时间分散;
也有是通过定时刷新过期时间,类似于 refresh token 机制; -
3.服务降级或熔断
服务熔断:当缓存服务器宕机或超时响应时,为了防止整个系统出现雪崩,暂时停止业务服务访问缓存系统;
服务降级:当出现大量缓存失效,而且处在高并发高负荷的情况下,在业务系统内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息;
(Hystrix限流+降级)限流组件:
牺牲部分用户的体验换来服务器的安全;
确保了每秒只有多少个请求能通过。 只要数据库不死,就是说,对用户来说,3/5 的请求都是可以被处理的。 只要有 3/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次;
未完待续 ......