缓存的主要性能:高并发、高性能
缓存优点:
大幅度提升性能,优化用户体验
减少对数据库的读操作,数据库压力降低,加快响应速度
缓存缺点:
1)缓存与数据库双写不一致
解决方案:如果系统不是严格要求“缓存+数据库”必须保持一致的情况下最好不要做这个方案:即读请求和写请求串行化,放在一个内存队列里,但是它会导致系统的吞吐量大幅度降低,用比正常情况多几倍的机器去支撑线上请求。
最经典的是缓存+数据库读写的模式(Cache Aside Pattern)
1.读的时候,先读缓存,缓存没有的话,再读数据库,然后取出数据放入缓存,同时返回响应。
2.更新的时候,先更新数据,再删除缓存。
2)缓存雪崩
概念:系统A,每天每秒5000个请求,本来在高峰期可以抗住每秒4000个请求,但是缓存机器意外发生了宕机,缓存挂了,此时5000个请求全部落数据库,那数据库必然扛不住,然后就挂了,如果没t采用解决方案,DBA很着急,重启数据库,但是数据库又被新流量给打死了。
解决方案:
1. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如某个key只允许一个线程查询数据和写缓存,其他线程等待。
2. 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
3. 做二级缓存,A为原始缓存,B为拷贝缓存,A失效后,可访问B,A缓存失效时间设置为短期,B设置为长期。
3)缓存穿透
概念:访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时BD会挂掉。
举例:系统A ,每秒5000个请求,其中4000个请求是黑客发起的恶意攻击,数据库ID是从1开始的,而黑客发起的请求ID全都是负数,这有的话,缓存就不会有,而每次请求会视”缓存无于物”,直接查询数据库,这种恶意攻击场景的缓存穿透会直接把数据库给打死。
解决方案:1.对于查询结果为空的情况也进行缓存,将缓存时间设置短一点,或将该key对应的数据insert之后再清理缓存。
2.对一定不存在的key直接过滤,可以将所有可能存在的key放在一个大Bitmap里,查询时通过Bitmap过滤。
4)缓存击穿
概念:当某个key非常热点,访问非常频繁,处于集中式高并发访问的情况下,当这个key失效的瞬间,大量的请求就击穿了缓存,直接访问数据库,就像在一道屏障上凿了一个洞。
解决方案:
1.使用互斥锁(mutex key):感知到缓存失效,去查询DB时,使用分布式锁,使其控制一个线程去问数据库加载数据,加锁失败的线程等待即可。
2.手动过期:redis从不会设置过期时间,功能上将过期时间存在key对应的value里,如果发现要过期,通过一个后台的异步线程进行缓存的构建,也就是“手动”过期。
5)缓存并发竞争
概念:某个时刻,多个系统实例都去更新某个key,可以基于zookeeper去实现分布式锁,每个系统通过zookeeper获取分布式锁,确保同一个时刻只有一个系统实例在操作某个key,别人都不允许读和写。
解决方案:
1.要写入缓存的数据都是从mysql查出来,都得写入mysql,写入mysql中的时候必须保存一个时间戳,从mysql查出来的时候,时间戳也要查询出来。
2.每次写之前,先判断一下当前这个value的时间戳是否比缓存value里的时间戳要新,如果是的话,可以写,否则,就用旧数据覆盖新的数据。
高性能使用场景:
一个请求过来,耗时600ms,到最后结果不变,或者结果变了,但是也没反映给用户,这时直接放缓存里,一个key对应一个value, 2ms搞定,
总结:一堆复杂操作耗时出来得结果,后面还有一堆请求操作,那就直接放缓存里。
高并发使用场景:
高峰期一秒过来的请求有一万条,那么一个mysql单机会死掉,这时候上缓存,单机支撑的高并发轻松一秒几万几十万,支撑高并发so easy,单机承载并发量是mysql单机的几十倍.