目录
前言:
为什么说关系型数据库性能不高
如何提高MySQL并发量
缓存更新策略
定期更新
实时更新
内存淘汰策略
Redis内置的淘汰策略
缓存常见问题
缓存预热
缓存穿透
缓存雪崩
缓存击穿
对于缓存的理解,缓存目的就是为了提供更快速的访问效率。一般会使用访问迅速的为访问较为缓慢的作为缓存。例如使用内存作为硬盘的缓存,硬盘作为网络的缓存。使用缓存可以减轻被缓存服务请求数量,一定程度上提供了系统高可用性能。
1)数据库把数据存储在硬盘上,硬盘IO速度并不快,尤其是随机访问。
2)如果查询不能命中索引,就需要进行表遍历,这会大量增加硬盘IO次数。
3)关系型数据库会对SQL进行一系列的解析,优化,校验等工作。
4)一些复杂查询,联表查询,会进行笛卡尔积操作,效率会降低很多。
注意:
因为mysql等数据库效率比较低,所承担的并发量就有限,一旦请求数量多了,数据库压力就会很大,就很容易宕机。
开源:
引入更多机器,构成MySQL集群。
节流:
引入缓存,把一些频繁读取的热点数据,保存在缓存上。后续查询数据的时候,如果缓存中存在,就不去MySQL中进行查询了。
注意:
数据是存在二八原则的,20%的数据可以满足80%的请求,虽然redis是内存数据库,只能存储少量数据,但基于这个原则,可以大大减少MySQL数据库压力。
如何知道redis中应该存储哪些数据,如何知道哪些数据是热点数据呢?
会把访问的数据以日志形式记录下来,然后就可以分析这些日志数据,得到一些频繁出现的词,那么这些词所对应的数据就算热点数据了,就可以存储在redis中。
这里redis中的数据可以按照一天一更新,或者一周一更新,定期的进行热点数据统计,更新redis中的数据。
优点:
上述过程实现起来比较简单,过程更加可控(缓存中有什么数据比较固定),方便排查问题。
缺点:
实时性不够,如果出发一些特殊事件,有一些本来不是热词的数据成了热词,新热词查询就可能给后面数据库带来比较大的压力。
1)先从redis中查询,如果查到数据直接返回。
2)如果redis中不存在,则去数据库查,然后把数据插入redis中。
3)这样不停的往redis中插入数据,就会使redis的内存占用越来越多,逐渐达到上限。此时如果继续往redis中插入数据,就会触发问题。为了解决此问题,redis引入了内存淘汰策略。
1)FIFO(First In First Out)先进先出
把缓存中存在时间最久的(也就是先来的数据)淘汰掉。
2)LRU (Least Recently Used)淘汰最久未使用的
记录每个key的最近访问时间.把最近访问时间最老的key淘汰掉。
3)LFU(Least Frequently Used)淘汰访问次数最少的
记录每个key最近⼀段时间的访问次数。把访问次数最少的淘汰掉.。
4)Random随机淘汰
从所有的key中抽取幸运儿被随机淘汰掉。
注意:
1)经过一段时间的动态平衡,redis中的数据逐渐就成为了热点数据。
2)具体采用哪种内存淘汰策略,需要根据业务具体问题具体分析。
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提供的策略和上述介绍的通用策略是基本⼀致的。只不过redis这里会针对 "过期key" 和 "全部key" 做分别处理。
1)定期更新缓存数据,不存在缓存预热情况,首次启动缓存,也会存在一些热点数据,不会给mysql服务造成太大压力。
2)实时更新缓存数据,缓存服务首次启动缓存中是没有数据的,这个时候mysql服务承载的压力就比较大。
解决方案:
通过离线的方式,向缓存服务导入一些热点数据,首次启动mysql服务就不会有太大压力。随着时间推移,缓存中数据逐渐就都成为热点数据了。
查询某个key,redis中不存在,MySQL中也不存在,那么就不会在redis中进行存储。如果存在大量的这些查询,同样会给MySQL造成太大压力。
问题出现场景:
1)业务设计不合理,不如缺少必要的参数校验,导致非法key也被查询了。
2)开发/运维误操作,不小心把部分数据从MySQL中删除了。
3)黑客恶意攻击。
解决方案:
1)如果发现这个key,在redis和MySQL中都不存在,仍然写入redis中,value可设置一个非法值(比如“”),那么后续这些key的查询,在缓存中就可以查询到,就可以执行非法校验逻辑。
2)引入布隆过滤器,每次查询redis/MySQL时,都先判断一下key是否在布隆过滤器中存在。(把所有key都插入到布隆过滤器中),如果布隆过滤器不存在就不需要查询redis或MySQL,MySQL就不会有太大压力。
3)布隆过滤器:本质是结合了 hash + bitmap 不会存储真实数据,但可以判断是否存在。以比较小的空间开销,比较快的时间速度,实现争对key是否存在的判定。
由于在短时间内,redis上大规模的key失效,导致缓存命中率陡然下降,导致MySQL服务压力迅速上升,甚至直接宕机。
问题出现场景:
1)redis直接挂了,redis宕机 / redis集群模式下大量节点宕机。
2)redis没问题,但可能之前短时间内设置很多key到redis中,并且设置的过期时间都是相同的。
解决方案:
1)加强监控报警,保证redis集群的可用性。
2)不给key设置过期时间。
3)key设置过期时间的时候,添加随机因子,避免同一时刻同时失效。
相当于缓存雪崩的特殊情况,针对热点key,突然过期了,导致大量请求直接访问数据库,甚至引起数据库直接宕机(热点key的访问频率高,请求数就多,影响更大)
解决方案:
1)基于统计的方式发现热点key,并且设置永不过期。
2)进行必要的服务降级,例如访问数据库的时候使用分布式锁,限制同时请求数据库的并发数。客户端响应时间会比较长,但不至于数据库服务直接挂了。
小结:
redis作为缓存场景是非常常见的,需要因地制宜选择适合自己业务的功能。