引入:
什么是缓存?
答:⽐如我需要去⾼铁站坐⾼铁.我们知道坐⾼铁是需要反复刷⾝份证的(进⼊⾼铁站,检票,上⻋, 乘⻋过程中,出站....). 正常来说,我的⾝份证是放在⽪箱⾥的(⽪箱的存储空间⼤,⾜够能装).但是每次刷⾝份证都需 要开⼀次⽪箱找⾝份证,就⾮常不⽅便. 因此我就可以把⾝份证先放到⾐服⼝袋⾥.⼝袋虽然空间⼩,但是访问速度⽐⽪箱快很多. 这样的话每次刷⾝份证我只需要从⼝袋⾥掏⾝份证就⾏了,就不必开⽪箱了. 此时"⼝袋"就是"⽪箱"的缓存.使⽤缓存能够⼤⼤提⾼访问效率.
访问速度:CPU寄存器>内存>硬盘>网络,速度快的设备可以作为速度慢的设备的缓存
最常见的是使用内存作为硬盘的缓存(redis定位);硬盘也可以作为网络的缓存,浏览器的缓存(浏览器通过http/HTTPS从服务器(网络)上获取到数据(html,css,js,图片(想这样的体积大,有不太改变的数据就可以保存到浏览器本地(浏览器所在主机的硬盘上)后续再打开这个页面,就不必重新从网络上获取上述数据了),视频,音频,字体...)并进行展示)
缓存能够有意义:二八定律(20%的数据,可以应对80%的请求)
通常使用redis作为数据库的缓存(MySQL)
为什么说关系型数据库性能不⾼?
1. 数据库把数据存储在硬盘上,硬盘的IO速度并不快.尤其是随机访问.
2. 如果查询不能命中索引,就需要进⾏表的遍历,这就会⼤⼤增加硬盘IO次数.
3. 关系型数据库对于SQL的执⾏会做⼀系列的解析,校验,优化⼯作.
4. 如果是⼀些复杂查询,⽐如联合查询,需要进⾏笛卡尔积操作,效率更是降低很多
前两种是硬件方面的原因;后两种是软件方面的原因
因为MySQL等数据库,效率比较低,所以承担的并发量就有限,一旦请求数量多了,数据库压力就会很大,甚至很容易宕机
(服务器每次处理一个请求,一定要消耗一些硬件资源(CPU,硬盘,内存,网络...),任意一种资源的消耗超出了机器能提供的上限,机器就很容易出现故障)
如何能提高MySQL能承担的并发量?
1.开源:引入更多的机器,构成数据的集成
2.节流;引入缓存,就是典型的方案,把一些频繁读取的热点数据保存到缓存上,;后续在查询数据的时候,如果缓存已经存在了,就不在访问MySQL,而是直接访问缓存
re
如何知道redis中应该存储那些数据? 如何知道那些数据是热点数据呢?
缓存的更新策略:
1.定期生成:
会把访问的数据,给一日志的形式记录下来(通过日志把都使用到了那些词,给记录下来,就可以针对这些日志来进行统计了统计这一天(一周/一个月)这个吃出现的频率,再根据频率降序排序,再取出前20%的词,就可以把这些词认为是"热点词",接下来就可以把这些热点词,涉及到的搜索结果提前拎出来,就可以放到类似于"redis"这样的缓存中了)
此处的数据,就可以根据当前这里的统计维度,来定期更新(按照天级别统计,就每天更新一次;按照月级别统计,就每个月更新一次)
写一套离线的流程(往往使用shell,Python写脚本代码...)可以通过定时任务来触发
a)完成统计热词的过程
b)根据热词,找到搜索结果的数据(广告搜索)
c)把得到缓存数据同步到缓存服务器上
d)控制这些缓存服务器自动重启
优点:上述过程,实际上实现起来比较简单,过程更可控(缓存中有啥是比较固定的),方便排查问题
缺点:实时性不够,如果出现一些突发性事件,有些本来不是热词的内容,成了热词,新的热词可能给后面的数据库啥的带来较大的压力
2.实时生成
1)如果在redis中查到了,就直接返回了
2)如果redis中不存在,就从数据库查,把查到的结果同时也写入redis(经过一段时间的"动态平衡",redis中的key就逐渐都成了热点数据了;这样不停的写,就会是redis的内存占用越来越多,逐渐达到内存上限(不一定是机器内存上限,redis中也可以配置,最多使用多少内存),后面如果继续往里面插入数据就会出现问题)
针对上述问题就引入了"内存淘汰机制":
FIFO (First In First Out) 先进先出 :把缓存中存在时间最久的(也就是先来的数据)淘汰掉.
LRU(LeastRecentlyUsed)淘汰最久未使⽤的 :记录每个key的最近访问时间.把最近访问时间最⽼的key淘汰掉.
LFU(LeastFrequently Used)淘汰访问次数最少的 :记录每个key最近⼀段时间的访问次数.把访问次数最少的淘汰掉.
Random随机淘汰:从所有的key中抽取幸运⼉被随机淘汰掉
Redis 内置的淘汰策略如下:
• volatile-lru 当内存不⾜以容纳新写⼊数据时,从设置了过期时间(设置了过期时间就算,包括过期时间还没到的)的key中使⽤LRU(最近最 少使⽤)算法进⾏淘汰
• a llkeys-lru 当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LRU(最近最少使⽤)算法进 ⾏淘汰.
• v olatile-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,在过期的key中,使⽤LFU算法 进⾏删除key.
• a llkeys-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LFU算法进⾏ 淘汰.
• v olatile-random 当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中,随机淘汰数 据.
• a llkeys-random 当内存不⾜以容纳新写⼊数据时,从所有key中随机淘汰数据.
• v olatile-ttl 在设置了过期时间的key中,根据过期时间进⾏淘汰,越早过期的优先被淘汰.
缓存预热(Cache preheating):
缓存中的数据:
1.定期生成(这种情况,不涉及预热)
2.实时生成
redis服务器首次接入之后,服务器里是没有数据的;客户端先查询redis,如果没有查到,就再查一次MySQL,查到了之后,会把数据写入到redis中(此时所有的请求都会打给MySQL,随着时间的推移,redis上的数据积累越多,MySQL承担的压力就越小)
缓存预热就是为了解决上述问题的,把定期生成和实时生成结合一下;先通过离线的方式,通过一些统计的途径,先把热点数据找到一批,导入到Redis中,此时导入的这批热点数据,就能帮MySQL承担很大的压力;随着时间的推移,逐渐就使用新的热点数据淘汰掉旧的数据
缓存穿透(Cache penetration):
查询某个key,在Redis中没有,MySQL中也没有,这个key肯定也不会被更新到Redis中(经过多次查询仍然没有,如果这样的数据,存在很多,并且还反复查询,一样会给MySQL带来很大的压力)
造成这样的原因:
1.业务设计不合理,比如缺少必要的参数检验环节,导致非法的key也被进行查询了(典型)
2.开发/运维操作,不小心把部分数据从数据库上误删了(没那么典型,表现也是缓存穿透,误删操作,不一定能及时发现)
3.黑客恶意攻击
如何解决?
通过改进业务/加强监控报警....(亡羊补牢)
更靠谱的方案:
1)如果发现这个key在Redis和MySQL上都不存在,仍然写入Redis中,value设成一个非法值(比如"")
2)还可以引入布隆过滤器,每次查询Redis/MySQL之前都先判定一下key是否在布隆过滤器上存在(把所有的key都插入到布隆过滤器中)布隆过滤器本质上是结合了hash+bitmap,以比较小的空间开销,比较快的时间速度,实现了针对key是否存在的判定
关于缓存雪崩:
由于在短时间内,Redis上大规模的key失效,导致缓存的命中率陡然下降,并且MySQL的压力迅速上升,甚至宕机
1)Redis直接挂了(Redis宕机/Redis集群规模下大量节点宕机)
2)Redis好着呢,但是可能之前短时间内设置了很多key给Redis,并且设置的过期时间是相同的
给Redis里设置key作为缓存的时候,有时候为了考虑缓存的时效性,就会设置过期时间(和Redis的内存淘汰缓存汰机制,是配合使用的)
解决方法:
1)加强监控,加强Redis集群可用性的保证
2)不给key设置过期时间/设置过期时间的时候添加随机因子(避免同一时刻过期)
关于缓存击穿(Cache breakdown):
相当于缓存雪崩的特殊情况,针对热点key,突然过期了,导致大量的请求直接访问到MySQL,甚至引起数据库宕机(热点key访问频率高,影响更大)
解决方法:
1)基于统计的方式发现热点key,并设置永不过期(往往需要对结果上做出较大的调整)
2)必要的服务降级,例如访问数据库的时候使用分布式锁,限制请求的并发数