核⼼思路就是把⼀些常⽤的数据放到触⼿可及(访问速度更快)的地⽅,⽅便随时读取.
速度快的设备,可以作为速度慢的设备的缓存
(CPU寄存器>内存>硬盘>网络)
最常见的是,使用内存作为硬盘的缓存
硬盘也可以作为网络的缓存,浏览器的缓存
浏览器通过http/https从服务器上获取到数据(html,css,js,图片,视频...),像图片这样体积大,又不太改变的数据,就可以保存到浏览器本地(浏览器所在主机的硬盘上),后续再打开这个页面,就不必重新从网络上获取上述数据了
缓存的意义:二八定律
缓存速度虽然快,但是空间小,20%的数据,可以应对80%的请求
通常使用redis作为数据库的缓存
为什么说关系型数据库性能不⾼?
1. 数据库把数据存储在硬盘上,硬盘的IO速度并不快.尤其是随机访问.
2. 如果查询不能命中索引,就需要进⾏表的遍历,这就会⼤⼤增加硬盘IO次数.
3. 关系型数据库对于SQL的执⾏会做⼀系列的解析,校验,优化⼯作.
4. 如果是⼀些复杂查询,⽐如联合查询,需要进⾏笛卡尔积操作,效率更是降低很多.
因为mysql等数据库,效率比较低,所以承担的并发量就有限.一旦请求数量多了,数据库的压力就会很大,甚至很容易宕机了
服务器每次处理一个请求,一定要消耗一些硬件资源(cpu,内存,硬盘...),任意一种资源的消耗超出了机器能提供的上限,机器就很容器出现故障了
如何提高mysql能承担的并发量?
虽然redis只能存少数数据,但是大部分请求都是使用的这少数的热点数据
Redis能⽀持的并发量更⼤:
- Redis数据在内存中,访问内存⽐硬盘快很多.
- Redis只是⽀持简单的key-value存储,不涉及复杂查询的那么多限制规则.
注意:
缓存是⽤来加快"读操作"的速度的.如果是"写操作",还是要⽼⽼实实写数据库,缓存并不能 提⾼性能.
如何知道redis中应该存储哪些数据?
如何知道哪些数据是热点数据呢?
每隔⼀定的周期(⽐如⼀天/⼀周/⼀个⽉),对于访问的数据频次进⾏统计.挑选出访问频次最⾼的前N%的数据.
会把访问的数据,给以日志的形式记录下来
然后对日志进行统计,统计某段时间,每个词出现的频率,再根据频率降序排序,取出前N%的词,就可以把这些词认为是"热点词"
数据量可以很大,一台机器存不下,就需要使用分布式系统来存储这些日志(HDFS),再使用hadoop的map-reduce来写代码,进行统计,也可以使用基于HDFS的HBASE这样的数据库来写sql统计
把这些热点词,涉及到的结果,,提前拎出来,放到类似于redis这样的缓存中
此处的数据,就可以根据当前这里统计的维度,来定期更新
写一套离线的流程(往往使用shell,python写脚本代码),可以通过定时任务来触发
优点:
实现比较简单,过程可控(缓存中的有什么内容比较固定),方便排查问题
缺点:
实时性不够,如果出现一些突发性事件,有一些本来不是热词的内容,成了热词,新的热词就可能给后面的数据库带来较大的压力
先给缓存设定容量上限(可以通过Redis配置⽂件的 maxmemory 参数设定):
这样不停的写redis,就会使redis的内存占用越来越多,逐渐达到内存上限
此时继续往里面插入数据,就会触发问题,为了解决上述情况,redis就引入了"内存淘汰策略"
FIFO (First In First Out) 先进先出
把缓存中存在时间最久的(也就是先来的数据)淘汰掉.
LRU(LeastRecentlyUsed)淘汰最久未使⽤的
记录每个key的最近访问时间.把最近访问时间最⽼的key淘汰掉.
LFU(LeastFrequently Used)淘汰访问次数最少的
记录每个key最近⼀段时间的访问次数.把访问次数最少的淘汰掉.
Random随机淘汰
从所有的key中抽取幸运⼉被随机淘汰掉.
以甄嬛传为例
后宫佳丽三千,相当于数据库中的全量数据.经常宠幸的妃⼦相当于热点数据,是放在缓存中的.
今年选秀的⼀批新的⼩主,其中有⼀个被你看上了.宠信新⼈,⾃然就需要有旧⼈被冷落.到底谁是要被冷落的⼈呢?
• FIFO:皇后是最先受宠的.现在已经年⽼⾊衰了.皇后失宠.
• LRU:统计最近宠幸时间.皇后(⼀周前),熹妃(昨天),安答应(两周前),华妃(⼀个⽉前).华妃失宠. • LFU:统计最近⼀个⽉的宠幸次数,皇后(3次),熹妃(15次),安答应(1次),华妃(10次).安答应失宠.
• Random:随机挑⼀个妃⼦失宠.
具体采用哪种策略,结合实际场景
Redis内置的淘汰策略如下:
策略 | 含义 |
volatile-lru | 当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中使⽤LRU(最近最少使⽤)算法进⾏淘汰(设置了过期时间的就算,包括过期时间还没到的) |
allkeys-lru | 当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LRU(最近最少使⽤)算法进 ⾏淘汰. |
volatile-lfu | 当内存不⾜以容纳新写⼊数据时,在过期的key中,使⽤LFU算法 进⾏删除key |
allkeys-lfu | 内存不⾜以容纳新写⼊数据时,从所有key中使⽤LFU算法进⾏ 淘汰 |
volatile-random | 当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中,随机淘汰数 据. |
allkeys-random | 当内存不⾜以容纳新写⼊数据时,从所有key中随机淘汰数据. |
volatile-ttl | 在设置了过期时间的key中,根据过期时间进⾏淘汰,越早过期的优先被淘汰. (相当于FIFO,只不过是局限于过期的key) |
noeviction | 默认策略,当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错,不适合实时更新缓存 |
经过一段时间的"动态平衡",redis中的key就逐渐都成了热点数据了
缓存中的数据
1.定期生成(不涉及预热)
2.实时生成
redis服务器首次接入之后,服务器里是没有数据的,此时所有的请求都会给mysql,随着时间的推移,redis上的数据越来越多,mysql承担的压力就小了
缓存预热,就是用来解决上述问题的
处理:
把定期生成和实时生成结合一下
先通过离线的方式,通过一些统计的途径,把热点数据找到一批,导入到redis后,此时导入的这批热点数据,就能帮mysql承担很大的压力了
随着时间的推移,逐渐就使用新的热点数据淘汰旧的数据
查询某个key,在redis中没有,mysql也没有,这个key肯定也不会被更新到redis中
如果像这样的数据,存在很多,并且还反复查询,一样也会给mysql带来压力
为什么会出现这样的情况:
处理:
通过改进业务/加强监控报警=>亡羊补牢
更靠谱的方案:
1)针对要查询的参数进⾏严格的合法性校验.⽐如要查询的key是⽤⼾的⼿机号,那么就需要校验当前 key 是否满⾜⼀个合法的⼿机号的格式
2)如果发现这个key,在redis和mysql上都不存在,仍然写入redis中,value设置成一个非法值(比如" "),避免后续频繁访问数据库.
3)还可以引入布隆过滤器,每次查询redis/mysql之前,都先判定一下key是否在布隆过滤器上存在(把所有的key都插入到布隆过滤器),再真正查询
布隆过滤器,本质上是结合了hash+bitmap,以比较小的空间开销,较快的时间速度,实现针对key是否存在的判定
在短时间内,redis上大规模的key失效,导致缓存命中率下降,并且MySQL的压力迅速上升,甚至之间宕机
如何产生:
1)redis之间挂了
redis宕机/redis集群模式下大量节点宕机
2)redis好着,但是Redis上的⼤量的key同时过期
可能之前短时间内设置了很多的key给redis,并且设置的过期时间是相同的
给redis里设置key作为缓存的时候,有的时候为了考虑缓存的时效性,就会设置过期时间(和redis内存淘汰机制,配合使用)
处理:
1)部署⾼可⽤的Redis集群,并且完善监控报警体系
2)不给key设置过期时间/设置过期时间的时候添加随机的因子(避免同一时刻)
此处把breakdown翻译成"击穿",个⼈以为并⾮是⼀个好的选择.容易和缓存穿透混淆. 翻译成"瘫痪"或者"崩溃"也许更合适⼀些.
相当于缓存雪崩的特殊情况,针对热点key,突然过期了,导致大量的请求直接访问到数据库上,甚至引起数据库宕机
处理:
假如本身服务器的功能,有10个,但是在特定情况下,适当关闭一些不重要的功能,只保留核心功能(服务降级)