文章目录
缓存穿透
现象
- 应用服务器压力变大了,很多请求过来
- redis命中率降低
- 一直查询数据库(缓存中很多数据查询到,就查数据库,导致数据库压力增加)
原因
- Redis查询缓存中不存在的数据
- 出现很多非正常的url访问,一般出现在收到恶意攻击的时候,根据正常的url来改变值来恶意攻击服务器
解决方案
- 对空值缓存:对查不到的数据做空值缓存,并设置空结果的过期时间,最长不超过五分钟
- 设置可访问的名单(白名单):使用Redis的bitmaps类型定义一个可访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,不在的话,做拦截处理
- 采用布隆过滤器
- 进行实时监控:人为的排查分析
缓存击穿
现象特点
- 数据库访问压力瞬时增大
- redis里面没有出现大量key过期
- redis正常运行
原因
Redis某个key过期了,大量访问使用这个key
解决方案
- 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大key的时长
- 实时调整:现场监控热门数据,实时调整key的过期时长
- 使用锁(能避免这种情况,但是去点在于效率低下)
- 在缓存失效的时候(判断拿出来的值为空),不是立即去load db
- 先使用缓存工具的某些带成功的操作返回值的操作(比如setnx)去set一个mutex key
- 当操作返回成功时,在进行load db的操作,并设缓存,最后删除mutex key
- 当操作返回失败时,证明有现成在load db操作,当前线程睡眠一段时间再重试整个get缓存的方法
缓存雪崩
现象
- 数据库压力变大,服务器崩溃
原因
在极少时间段,查询大量key的集中过期情况
解决方案
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存
- 使用锁或者队列:用加锁或者队列的方式保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适合高并发的情形
- 设置过期标志更新缓存,通过缓存reload机制,预先更新缓存,在即将发生大并发前手动触发加载缓存
- 将缓存失效时间分散开
Redis实现分布式锁
分布式锁实现方案
- 基于数据库实现分布式锁
- 基于缓存实现(Redis等)
- 基于Zookeeper
上述实现方式Redis性能最高,Zookeeper最可靠
Redis实现
- 最简单的使用setnx上锁,del解锁,但是此时有问题,若设置锁了,长时间不释放锁又会导致后续资源拿不到锁的情形
- 上述1中锁一直不释放的话,可以设置过期时间来解决这个问题,时间到了自动释放
setnx users name1
expire users 10
- 上述2案例有个问题,就是这个不是原子的,设置锁后,突然故障,无法设置过期时间,也会导致问题,因为Redis的操作是原子的,可以使用指令,加锁设置过期时间同时操作
set users name1 nx ex 5
分布式下锁误删除
出现情形
假设a和b两个线程,a线程先获取分布式锁,然后由于业务操作或者服务器卡顿等一系列操作造成a线程的锁过期自动失效释放了(而a线程还没有数据处理完)
a释放锁后,b线程获取了锁执行具体操作,此时a突然操作完了,然后释放了锁,而不是b主动释放的
这种情形就造成了锁误删除的情形
解决方案
- 将锁的值设置成uuid,唯一标识,
set lock uuid nx ex 10
, - 释放锁的时候首先判断当前uuid是否和要释放锁的uuid一样,一样释放,不一样不释放
Redis分布式锁——加锁释放锁的原子性优化
造成问题
在上述案例中的当a上锁,执行具体操作的时候,然后释放锁步骤,也遇到了比较锁的uuid值一样,恰巧在del
操作要执行之前(也就是比较完成之后,删除操作之前)锁过期自动释放了,然后b线程获取了锁,还在执行具体的操作,a删除的是b的锁,这就造成的分布式下安全隐患
解决方案
在比较删除步骤嵌入原子性的LUA脚本在释放锁的比较释放进行做原子化操作