深入Redis:核心的缓存
Redis最主要的用途,主要有三个方面:存储数据、缓存、消息队列。
其中,缓存是Redis最常用的场景。Redis使用内存作为硬盘的缓存。把用户集中访问的20%数据放到缓存中去,可以应对80%的请求。
数据库是非常重要的组件,但是mysql的速度又比较慢,所以我们用Redis来作为mysql的缓存。
为什么说关系型数据库性能不高?
- 数据库把数据存储在硬盘上,硬盘的IO速度并不快。尤其是随机访问。
- 如果查询不能命中索引,就需要进行表的遍历,这就会大大增加硬盘IO次数。
- 关系型数据库对于SQL的执行会做一系列的解析,校验,优化工作。
- 如果是一些复杂查询,比如联合查询,需要进行笛卡尔积操作,效率更是降低很多。
对于mysql来说,服务器每处理一个请求,都要消耗一些硬件资源,任意一种资源的消耗超过了机器提供的性能上线,机器就很容易出故障。引入缓存就能够把大量查询的操作放到内存中去,又快又不用读写硬盘,资源消耗就少了。
更新策略
定期生成
Redis会把访问的数据,以日志的形式记录下来,比如搜索引擎中的“查询词”就是访问的数据。通过统计这些词出现的频率,取出前20%的词,这些词就可以认为是“热点词”。这些热点词就可以拿出来放到Redis中作为缓存。
实时生成
如果在Redis中查到了,就直接返回;如果Redis中不存在,就从数据库查,把查到的结果同时也写入Redis。
这样经过一段时间的“动态平衡”,redis中的key就逐渐都成为了热点数据了。
但是这种方式会让redis中的内存越占越多,直到达到上限。为了解决这种问题,Redis就引入了“内存淘汰策略”。
Redis 提供了多种内存淘汰策略,可以在配置文件中设置
maxmemory-policy
配置项来指定使用哪种策略。以下是 Redis 支持的内存淘汰策略:
noeviction: 不淘汰任何数据,当内存使用达到限制时,所有写入操作(包括写入命令和有写入副作用的命令,如 DEL 和 EXPIRE)都会返回错误。
allkeys-lru: 从所有键中挑选最近最少使用的键淘汰。
volatile-lru: 从设置了过期时间的键中挑选最近最少使用的键淘汰。
allkeys-random: 从所有键中随机挑选键淘汰。
volatile-random: 从设置了过期时间的键中随机挑选键淘汰。
volatile-ttl: 从设置了过期时间的键中挑选将要过期的键淘汰。
stable: 从所有键中挑选“近似”最少使用的键淘汰。
volatile-lfu: 从设置了过期时间的键中挑选使用频率最低的键淘汰。
allkeys-lfu: 从所有键中挑选使用频率最低的键淘汰
缓存预热
缓存预热(Cache preheating),是针对于缓存实时生成的情况。
Redis服务器首次接入之后,服务器里面是没有数据的。此时所有的请求都会发给mysql,短时间内mysql的压力很大,一段时间之后redis上的数据才会渐渐多起来,mysql的压力才会下去。
缓存预热就是用来解决上述问题的,通过把定期生成和实时生成结合一下,先通过离线的方式,通过一些统计的途径,先把热点数据找到一批,导入到redis中,此时导入这些的这批热点数据就能够帮mysql承担很大的压力了。随着时间的推移就能够使用新的热点数据淘汰掉旧的数据。
缓存穿透
查询的某个key,在redis中没有,mysql中也没有,这个key肯定也不会被更新到redis中。反复查询都没有,但是会给mysql带来很大的压力。
- 业务设计不合理,比如缺少必要的参数校验环节,导致非法的key被反复查询
- 开发、运维误操作,把某个key从数据库上删除了
- 黑客恶意攻击
解决办法:
- 如果发现某个key在redis和mysql都不存在,仍然把这个值写入到redis中,value设成一个非法的值,如 " "
- 引入布隆过滤器,每次查询redis/mysql之前都先判定一下key是否在布隆过滤器上
缓存血崩
在短时间内,redis上大规模的key失效,导致缓存命中率陡然下降,并且mysql的压力迅速上升,甚至直接宕机。
- redis大量节点宕机
- 之前同时设置的key,过期时间是相同的
可以考虑给redis设置过期时间的时候,添加一些随机的因子,避免同一时刻过期。
缓存击穿
相当于缓存血崩的特殊情况,针对热点key,突然过期了,导致大量的请求直接访问到数据库上,甚至引起数据库宕机。
解决办法:
- 基于统计的方式发现热点key,并且设置永不过期
- 进行必要的服务降级,例如访问数据库的时候使用分布式锁,显示同时请求数据库的并发数