缓存
在一个网站中,经常使用关系型数据库(比如MySQL来存储数据),关系型数据库虽然功能强大,但是效率不高。因此如果数据库的并发量比较高,对于数据库的压力是很大的,极容易造成数据库宕机
解决方法:
- 开源:引入更多的机器,构成数据库集群(主从复制、分库分表等)
- 节流:引入缓存,或其他方式保存经常访问的热点数据,从而降低数据库的请求数量
redis的访问速度要比MySQL快很多,处理同一个访问请求,Redis消耗的系统资源也要比MySQL少很多,因此Redis能够支持更大的并发量

- 当需要查询数据时,业务服务器先访问Redis
- 如果Redis中存在,则直接返回
- 如果redis不存在则查询数据库
缓存更新策略
-
定期生成
每隔一定的周期,对访问的数据频次进行统计,挑选出访问频次最高的前N%的数据。这种方式得到的数据实时性较差,但是更加可控,适用于搜索引擎
-
实时生成
先给缓存设置容量上限(maxmemory参数),用户每次查询,先查redis如果redis中存在则直接返回,如果不存在就查询数据库,然后将查到的结果也保存到redis。这种方法得到的数据实时性更高,但不太可控,适合内部使用
缓存淘汰策略
如果缓存容量达到上限,就需要按照一定规则将"不太热门的数据删除",以保存新的数据
-
FIFO (First In First Out) 先进先出:
把缓存中存在时间最久的数据丢弃
-
LRU (Least Recently Used) 淘汰最久未使用的
记录每一个key的最近访问时间,淘汰最近访问时间最远的
-
LFU (Least Frequently Used) 淘汰访问次数最少的
记录每一个key最近一段时间的访问次数,淘汰访问次数最少的
-
Random 随机淘汰
随机抽取key进行淘汰
Redis内置的淘汰策略:
volatile-lru:从设置了过期时间的key中淘汰最久未使用的
allkeys-lru:,从所有key中淘汰最久未使用的
volatile-lfu: 4.0版本新增,在设置了过期时间的key中,淘汰最近一段时间访问次数最少的
allkeys-lfu:4.0版本新增,从所有key 淘汰访问次数最少的
volatile-random:从设置了过期时间的key中,随机淘汰数据.
allkeys-random :从所有key中随机淘汰数据
volatile-ttl 在设置了过期时间的key中,优先淘汰剩余过期时间最短的
noeviction 默认策略,当内存达到上限时,新写入操作会报错
缓存使用注意事项
缓存预热
redis刚刚启动的时候内部没有数据,此时先通过离线的方式,通过一些途径先把热点数据导入一批。刚导入的热点数据不需要特别准确,只需要帮助MySQL抵挡一部分数据就可以了,随之时间推移,会有新的热点数据替换掉旧的热点数据,缓存热点会逐渐自动调整,来适应当前的情况
缓存穿透
访问的key在redis和数据库中都不存在,此时key不会被放到缓存上,每次访问都会打到数据库。
产生原因:
- 业务设计不合理,比如缺少必要的参数校验环节
- 部分数据在数据库中误删除
- 黑客恶意攻击
如何解决:
-
针对要查询的参数进行严格的合法性校验,比如要查询一个手机号,就要判定这个手机号的格式是否正确
-
针对数据库中不存在的key,也存储到redis中,不过value为一个"",避免后续频繁访问数据库
-
使用布隆过滤器,来提前判断某个key是否存在
布隆过滤器是结合hash+bitmap的思想,将key通过n个不同的哈希函数映射到位图上n个不同的位置。当输入一个字符串时,使用哈希函数去判断位图上对应的n个位置是否都为1,如果有一个为0则表示不存在
缓存雪崩
短时间内大量的key在缓存上失效,导致数据库压力骤增
产生原因:
- redis宕机
- redis上大量的key同时过期(设置了相同的过期时间)
如何解决:
- 部署高可用的redis集群,并且完善监控报警机制,比如哨兵模式
- 设置过期时间时添加随机的时间因子
缓存击穿
缓存雪崩的特殊情况,针对热点key,突然过期,导致大量请求直接访问到数据库上
- 将热点key设置为永不过期
- 进行服务降级,比如访问数据库时使用分布式锁,限制同时请求数据库的并发量
分布式锁
如果数据库内部商品库存只有一件,但是有两台客户端同时访问数据库来判断是否有库存,得到的结果都是有库存,此时就会出现超卖的情况
概念
为解决上述在一个分布式系统中,多个节点同时访问一个公共资源时引发的类似线程安全的问题,就引入了分布式锁
本质上是一个公共服务器,来记录加锁状态
实现
引入一个redis数据库,并设置统一一个key
-
每个客户端在进行业务逻辑处理之前,使用
set key v nx ex 10写入键值对。如果写入成功,表示目前服务器没有加锁,可以执行后续的业务逻辑,执行完毕后,将锁删除。如果写入失败,则表示服务器正在被其他客户端访问 -
设置过期时间,以解决持有锁的客户端宕机导致锁无法正常释放的情况
-
将key设置为针对哪个资源进行加锁,将value设置为每个客户端服务器的唯一编号。客户端服务器删除锁之前,先比较一下自己的编号与value是否相同,相同则删除。以解决一个客户端加锁,另一个客户端由于某种bug将不属于自己的锁删除
-
引入lua脚本,解决删除key时,get和del命令不是原子的问题
luaif redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end; -
引入一个线程(看门狗),这个线程的作用时在锁快要过期时,自动续约,以实现动态过期时间
-
引入 Redlock 算法,引入一组Redis节点,,加锁时按照一定的顺序,写入多个master节点,如果某个节点加锁失败就立刻尝试下一个节点,当加锁成功的节点数超过总节点数的一半,则表示加锁成功
解决redis集群主节点宕机并且主从复制的延迟,导致的锁丢失问题