为什么关系型数据库性能不高
-
数据库把数据存储在硬盘上,硬盘的IO速度并不快,尤其是随机访问。
-
如果查询不能命中索引,就需要进行表的遍历,这就会大大增加硬盘IO次数。
-
关系型数据库对于SQL的执行会做一系列的解析、校验、优化工作。
-
如果是一些复杂查询,比如联合查询,需要进行笛卡尔积操作,效率更是降低很多。
mysql等数据库,效率比较低,所以承担的并发量有限,一旦请求数量多了,数据库的压力就会很大,甚至容易宕机----服务器每次处理一个请求,一定都要消耗一些硬件资源(CPU,内存,硬盘,网络...),任意一种资源的消耗超出了机器能提供的上限,机器就很容易出现故障。
如何提高mysql能承担的并发量?(客观需求)
-
开源:引入更多的机器,构成数据库集群。
-
节流:引入缓存,把一些频繁读取的热点数据保存到缓存上。后续查询数据时,如果缓存中已经存在了,就不再访问mysql了。
怎么知道哪些数据应该存储在Redis中(怎么知道哪些数据是热点数据)?
定期生成
把访问的数据,以日志的形式记录下来
通常是写一套离线的流程(往往使用 shell,python 写脚本代码...),然后通过定时任务来触发。
-
完成统计热词的过程。
-
根据热词,找到搜索结果的数据。
-
把得到的缓存同步到缓存服务器上。
-
控制这些缓存服务器自动重启。
优点:上述过程实现起来比较简单,过程更可控(缓存中的数据比较固定),方便排查问题
缺点:实时性不够,如果出现一些突发性事件,有一些本来不是热词的内容成了热词,新的热词就可能会给后面的数据库带来较大的压力
实时生成
-
如果在 Redis 中查到了,就直接返回
-
如果 Redis 中不存在,就从数据库查,把查询的结果同时写入 Redis
但是不停地写入 Redis,就会使 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 中随机淘汰数据。
-
volatile-ttl:在设置了过期时间的 key 中,根据过期时间进行淘汰,越早过期的优先被淘汰。(相当于 FIFO,只不过是局限于过期的 key)
-
noeviction 默认策略:当内存不足以容纳新写入的数据时,新写入的操作会报错。
缓存预热(Cache preheating)
上述"定期生成"的情况中不涉及"预热",在"实时生成"中,由于客户端先查询 Redis,如果没有才会查询 mysql,再把查到的数据写入 Redis 中。但是第一次查询时,Redis 中是空的,就注定要在 mysql 中查找,随着后续 Redis 中数据越来越多,mysql 的压力才会慢慢减小。但是也无法改变"万事开头难"的事实。
缓存预热就是"定期生成"和"实时生成"的结合,先通过离线的方式,通过一些统计的途径,把热点数据找到一部分,导入到 Redis 中,就能帮 mysql 承担很大压力。随着时间推移,就会逐渐使用新的热点数据淘汰掉旧的数据。
缓存穿透(Cache penetration)
是什么
对于"实时生成",如果查询的 key 不在 Redis 中,下一次查询时就一定在了。但前提是这个 key 一定要在 mysql 中能够找到,才会被写入 Redis;但如果出现了很多 Redis 和 mysql 中都没有的"错误数据",就会导致每次都会查一次 mysql,一样会给 mysql 带来很大压力。这种情况称为 缓存穿透。
为什么
原因可能有以下几种:
-
业务设计不合理,比如缺少必要的参数校验环节,导致非法的 key 也被查询。
-
开发/运维误操作,不小心把部分数据从数据库上误删了。
-
黑客恶意攻击。
怎么办
-
针对要查询的参数进行严格的合法性校验。比如要查询的 key 是用户的手机号,就需要校验当前 key 是否满足一个合法的手机号的格式。
-
针对数据库上也不存在的 key,也将其存储到 Redis 中,但是 value 设置为一个"",避免后续频繁访问数据库。
-
使用布隆过滤器先判定 key 是否存在,再真正查询。
缓存雪崩(Cache avalanche)
是什么
短时间内大量的 key 在缓存上失效,导致数据库压力骤增,甚至直接宕机
本来 Redis 是 MySQL 的一个护盾,帮 MySQL 抵挡了很多外部的压力。一旦护盾突然失效,MySQL 自身承担的压力骤增,就可能直接崩溃。
为什么
大规模的 key 失效,可能性主要有两种:
-
Redis 挂了。
-
Redis 上的大量的 key 同时过期
大量的 key 同时过期,可能是短时间内在 Redis 上缓存了大量的 key,并且设定了相同的过期时间。
因为有些数据可能早些时候是热点数据,但后续很久不用,也就自然不再是热点了,所以设定一个过期时间,过期时间结束前没有进行访问,就可以进行热点数据的更新了。
怎么办
-
部署高可用的 Redis 集群,并且完善监控报警体系。
-
不给 key 设置过期时间 或者 设置过期时间的时候添加随机事件因子,保证不让太多 key 在同一时间过期。
缓存击穿(Cache breakdown)
相当于缓存雪崩的特殊情况。针对热点 key,突然过期了,导致大量的请求直接访问到数据库上,甚至引起数据库宕机。
解决方法:
-
基于统计的方式发现热点 key,并设置永不过期。
-
进行必要的服务降级。例如访问数据库的时候使用分布式锁,限制同时请求数据库的并发数。