什么是缓存
缓存(Cache)的核心思路就是把一些常用的数据放到访问速度更快的地方,方便获取。
关于硬件的访问速度来说
CPU寄存器>内存>硬盘>网络
因此常见使用内存作为硬盘的缓存,例如redis。使用硬盘作为网络的缓存,例如浏览器通过http/https从服务器上获取到数据(html,css,js,图片,视频,音频,文字)像这种体积大,又不太会改变的数据,就可以保存到浏览器本地,后续在打开该网页,就不必重新从网络获取上述数据了。
根据“二八原则”,20%的热点数据,能够应对80%的访问场景。因此只需要把这些少量的热点数据缓存起来,就可以应对大多数的场景,从而在整体上有明显的性能提升。
使用redis作为缓存
在网站开发中,我们经常使用mysql来存储数据。mysql虽然功能强大,但是有一个致命的缺点,就是性能不高(进行一次查询操作要消耗很多系统硬件资源)。
:::info
为什么关系型数据库性能不高?
- 硬件原因
- 数据库把数据存储在硬盘上,硬盘的IO速度不快,尤其是随机访问
- 如果查询不能命中索引,就需要进行表的遍历,这就会增加磁盘IO次数
- 软件原因
- 关系型数据库对于sql的执行会做一系列解析,校验,优化工作
- 对于复杂查询,比如联合查询,需要进行笛卡尔积,效率很低
:::
因此,如果访问数据库的并发量很高,对于数据库的压力就很大,容易使数据库宕机。
:::success
为什么并发量高了就会宕机?
服务器每次处理一个请求,就需要消耗一定的硬件资源,例如cpu,内存,硬盘,网络带宽。
而一个服务器的资源是有限的,一个请求消耗一份资源,当把资源耗尽,后续请求就没有资源可用,就无法正确处理,更严重的还会导致服务器程序的代码崩溃。
:::
如何让数据库能够承担更大的并发量?
- 开源:引入更多机器,部署更多数据库,构成数据库集群(主从复制,分库分表)
- 节流:引入缓存,使用其他方式保存经常访问的热点数据,从而降低直接访问数据库的请求数量
实际开发中两种方案搭配使用
缓存更新策略
缓存更新策略
定期生成
访问的数据会以日志的形式记录下来,我们可以写个程序(如python,shell脚本代码),针对这些日志进行统计,统计这一天/周/月,每个词出现的频率,在根据频率降序排序,取出前20%的词,就是“热点词”。将这些“热点词”放大redis中
实时生成
如果在redis中查到,就直接返回;如果redis中不存在,就从数据库查,把查到的结果同时写到redis中。经过一段时间“动态平衡”,redis中的key就逐渐变成热点数据了。
内存淘汰机制
不停的写入redis,就会使redis的内存占用越来越多,当达到内存上限,就会触发redis的“内存淘汰机制”。
- FIFO(first in first out)先进先出:把缓存中存在时间最久的(先来的数据)淘汰掉
- LRU(least recently used)淘汰最久未使用:记录每个key的最近访问时间,把最近访问时间最老的key淘汰掉
- LFU(least frequently used)淘汰访问次数最少的:记录每个key最近一段时间的访问次数,把访问次数最少的淘汰掉
- Random随机淘汰:随机抽取key删除掉
redis配置文件中内置的淘汰策略
volatile-lru
当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中使⽤LRU算法(最近最少使⽤)进⾏淘汰allkeys-lru
当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LRU算法(最近最少使⽤)进 ⾏淘汰.volatile-lfu
4.0版本新增,当内存不⾜以容纳新写⼊数据时,在过设置了过期时间的key中,使⽤LFU算法 进⾏删除key.allkeys-lfu
4.0版本新增,当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LFU算法进⾏淘汰volatile-random
当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中,随机淘汰数 据.allkeys-random
当内存不⾜以容纳新写⼊数据时,从所有key中随机淘汰数据volatile-ttl
在设置了过期时间的key中,根据过期时间进⾏淘汰,越早过期的优先被淘汰. (相当于 FIFO, 只不过是局限于过期的 key)noeviction
默认策略,当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错
缓存常见问题
缓存预热
缓存更新中,有定期更新,不需要预热;定期更新,需要预热。
在redis服务器首次接入的时候,服务器中没有数据。此时所有请求都会打给mysql,随着时间推移,redis上的数据越积累越多,mysql承担的压力逐渐变小。但就怕刚开始mysql就垮了,因此需要进行缓存预热(将定期生成和实时生成结合),先通过离线的方式,通过一些统计的途径,先把热点数据找到一批,导入到redis中,此时导入的这批热点数据,就能帮mysql承担很大的压力,随着时间推移,逐渐使用新的热点数据淘汰掉旧的数据。
缓存穿透(cache penetration)
查询某个key时,redis中没有,mysql中也没有。这次查询没有,下次查询还是没有,如果像这样的数据非常多,并且还反复查询,就会给mysql带来很大压力。
出现的可能原因
- 业务设计不合理,例如缺少必要的参数校验环节,导致非法的key也被进行查询
- 开发/运维误操作,不小心将部分数据从数据库上误删除
- 黑客攻击
解决方案
- 改进业务/加强监控报警(亡羊补牢)
- 如果发现这个key在redis和mysql中都不存在,仍然写入redis,value设成一个非法值如“”
- 引入布隆过滤器,每次查询redis/mysql之前都先判定key是否在布隆过滤器上存在。
布隆过滤器:结合了hash+bitmap,以比较小的空间,比较快的时间速度,实现对key是否存在的判定
缓存雪崩(cache avalanche)
短时间内,redis上大规模的key失效,导致缓存命中率陡降,并且mysql压力迅速上升,甚至导致宕机。
出现的可能原因
- redis挂了,redis宕机/redis集群模式下大量节点宕机
- redis正常,但是之前短时间内设置了很多key给redis,并且设置的过期时间正好相同
解决方案
- 加强监控报警,加强redis集群可用性的保证
- 不给key设置过期时间/设置过期时间的时候添加随机因子,避免同一时刻过期
缓存击穿(cache breakdown)
缓存雪崩的特殊情况,针对热点key突然过期,导致大量请求直接打到mysql上,甚至引起数据库宕机。
解决方案
- 基于统计方式发现热点key,并设置永不过期
- 进行必要的服务降级,例如访问数据库的时候使用分布式锁,限制同时请求的数据库的并发数
服务降级:本身服务器有十个功能,特定情况下,适当关闭一些不重要的功能,只保留核心功能(省点模式)