Redis使用场景及解决方案
使用场景
缓存、分布式锁、计数器、保存token、消息队列、延迟队列等
Redis缓存问题
缓存穿透
查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库
解决方案
缓存空数据:
就是redis中查询返回的数据为null,则把这个null缓存到redis中。
优点:简单、易操作。
缺点:浪费空间,会有很多空间用于缓存空数据、可能会出现数据不一致的情况(数据库新增数据,redis中已经存在该数据的缓存所以不能及时更新缓存)
布隆过滤器:
在接口请求和redis中间再加一层布隆过滤器,过滤器如果判定该值不存在则直接返回空,不再访问redis和数据库,如果布隆过滤器中存在则访问redis、数据库
优点:内存占用少、没有多余的key
缺点:实现复杂,存在误判
缓存击穿
给某一个key设置了过期时间。当key过期的时候,恰好这个时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把数据库压垮。
解决方案
互斥锁:
优点:强一致性
缺点:性能差
逻辑过期:
优点:高可用
缺点:不能保证数据的强一致性
缓存雪崩
在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力,出现这种问题的原因是大量的key设置了相同或者类似的过期时间,所以同一时间段才会有大量的缓存key过期
解决方案
给不同的key的过期时间添加随机值
搭建redis集群,提高redis的可用性
给缓存业务添加降级限流策略
给业务添加多级缓存
Redis持久化问题
Redis的过期策略
Redis数据删除策略
惰性删除
设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key
优点:对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查
缺点:对内存不友好,如果一个key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放
定期删除
每隔一段时间,我们就对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key)
定期删除数据有两种模式分别如下:
SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的hz 选项来调整这个次数
FAST模式执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms
优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。
缺点:难以确定删除操作执行的时长和频率
Redis中一般是将这两个策略配合着使用
Redis数据淘汰策略
当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
Redis中支持八种不同的策略来选择要删除的key
Redis分布式锁
分布式锁需要满足的条件
**互斥性:**在任意时刻,只有一个客户端能持有锁。
**不发生死锁:**即使有一个客户端在持有锁的期间崩溃而没有主动解锁也能保证后续其他客户端能加锁。
**同一性:**加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁。
**容错性:**只要大多数Redis节点正常运行,客户端就能够获取和释放锁。
Redis实现分布式锁的几种方案
setnx+expire
先用setnx来抢锁,如果抢到锁,再用expire给锁设置一个过期时间,这样持有锁超时时释放锁,防止锁忘记释放(注意这两个指令如果不是一起执行则无法保证原子性,因为在setnx抢到锁之后,redis如果出现宕机的情况,则该锁将无法被释放)
setnx+value(系统时间+过期时间)
设置value值为当前系统时间和过期时间的和,如果value小于当前系统时间则说明该锁已经失效
通过开源框架-Redisson
如果已经超过了加锁的过期时间,可是业务还没执行完成,这个时候怎么做呢?是把过期时间延长吗?显然不合理,可以通过开源框架-Redisson优化这个问题,简单来说,Redisson就是当一个线程获得锁以后,给该线程开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。假设两个线程争夺统一公共资源:线程A获取锁,并通过哈希算法选择节点,执行Lua脚本加锁,同时其看门狗机制会启动一个watch dog
(后台线程),每隔10秒检查线程,如果线程A还持有锁,那么就会不断的延长锁key的生存时间。线程B获得锁失败,就会订阅解锁消息,当获取锁到剩余过期时间后,调用信号量方法阻塞住,直到被唤醒或等待超时。一旦线程A释放了锁,就会广播解锁消息。于是,解锁消息的监听器会释放信号量,获取锁被阻塞的线程B就会被唤醒,并重新尝试获取锁。
只有同一个线程执行的时候才能重入,否则需要等待锁,该种重入策略类似于java自带的锁的重入策略
redis的哨兵模式会在一个redis主节点宕机之后随机在从节点中推举出一个从节点为主节点
该种方式导致两个线程拥有了同一个锁,这时就会产生脏数据
AP思想是保证服务高可用能保证数据的最终一致性
CP思想就是保证数据的强一致性