使用场景
1. 缓存 2. 分布式锁 3. 计数器(Incr) 4. 保存token(String) 5. 消息队列(List) 6. 延迟队列(Zset)
缓存穿透
一个get请求:api/news/getById/1
定义
查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查询数据库。
解决方案一
缓存空数据,查询返回的结果为空,仍把这个空结果进行缓存。
优点:简单。
缺点:消耗内存,可能发生不一致的现象。
解决方案二
使用布隆过滤器。
优点:内存占用较少,没有多余key。
缺点:实现复杂,存在误判。
布隆过滤器原理
bitmap(位图):相当于是一个以(bit)位为单位的数组,数组中每个单元只能存储二进制数0或1。
布隆过滤器作用:布隆过滤器可以用于检索一个元素是否在一个集合中。
存在误判情况。
误判率
数组越小误判率就越大,数组越大误判率就越小,但是同时带来了更多的内存消耗。
缓存击穿
定义
给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。
解决方案一
互斥锁。
解决方案二
逻辑过期。
缓存雪崩
定义
在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案
1. 给不同的Key的TTL添加随机值。
2. 利用Redis集群提高服务的可用性(哨兵模式、集群模式)。
3. 给缓存业务添加降级限流策略(可作为系统的保底策略)。
4. 给业务添加多级缓存。
双写一致
定义
当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间。
写操作:延迟双删。存在脏数据风险。
允许延时一致的业务,采用异步通知。
1. 使用MQ中间中间件,更新数据之后,通知缓存删除。
2. 利用canal中间件,不需要修改业务代码,伪装为mysql的一个从节点,canal通过读取binlog数据更新缓存。
强一致性的业务,采用Redisson提供的读写锁。
共享锁:读锁readLock,加锁之后,其他线程可以共享读操作。
排他锁:写锁writeLock,加锁之后,阻塞其他线程读写操作。
持久化
RDB定义
RDB全称Redis Database Backup file(Redis数据备份文件),也叫Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复文件。
save 由Redis主进程来执行RDB,会阻塞所有命令。
bgsave 开启子进程执行RDB,避免主进程受到影响。
RDB执行原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入RDB文件。
AOF定义
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF。
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
RDB与AOF对比
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
RDB |
AOF | |
持久化方式 |
定时对整个内存做快照 |
记录每一次执行的命令 |
数据完整性 |
不完整,两次备份之间会丢失 |
相对完整,取决于刷盘策略 |
文件大小 |
会有压缩,文件体积小 |
记录命令,文件体积很大 |
宕机恢复速度 |
很快 |
慢 |
数据恢复优先级 |
低,因为数据完整性不如AOF |
高,因为数据完整性更高 |
系统资源占用 |
高,大量CPU和内存消耗 |
低,主要是磁盘IO资源 但AOF重写时会占用大量CPU和内存资源 |
使用场景 |
可以容忍数分钟的数据丢失,追求更快的启动速度 |
对数据安全性要求较高 |
数据过期策略
定义
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中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
策略
noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。
volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰。
allkeys-random:对全体key ,随机进行淘汰。
volatile-random:对设置了TTL的key ,随机进行淘汰。
allkeys-lru: 对全体key,基于LRU算法进行淘汰。
volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰。
allkeys-lfu: 对全体key,基于LFU算法进行淘汰。
volatile-lfu: 对设置了TTL的key,基于LFU算法进行淘汰。
LRU & LFU
LRU(Least Recently Used)最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
LFU(Least Frequently Used)最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。
建议
1. 优先使用 allkeys-lru 策略。充分利用 LRU 算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。
2. 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用 allkeys-random,随机选择淘汰。
3. 如果业务中有置顶的需求,可以使用 volatile-lru 策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,会淘汰其他设置过期时间的数据。
4. 如果业务中有短时高频访问的数据,可以使用 allkeys-lfu 或 volatile-lfu 策略。
Redis分布式锁
Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则SET)的简写。
Redisson实现
在redisson的分布式锁中,提供了一个WatchDog(看门狗),一个线程获取锁成功以后,WatchDog会给持有锁的线程续期(默认是每隔10秒续期一次)。
Redission锁可重入性
可以重入,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用hash结构,来存储线程信息和重入的次数。
Redis集群
方案
1. 主从复制
2. 哨兵模式
3. 分片集群
主从复制
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据。
原理
全量同步
1. 从节点请求主节点同步数据(replication id、 offset)。
2. 主节点判断是否是第一次请求,是第一次就与从节点同步版本信息(replication id和offset)。
3. 主节点执行bgsave,生成rdb文件后,发送给从节点去执行。
4. 在rdb生成执行期间,主节点会以命令的方式记录到缓冲区(一个日志文件)。
5. 把生成之后的命令日志文件发送给从节点进行同步。
增量同步
1.从节点请求主节点同步数据,主节点判断不是第一次请求,不是第一次就获取从节点的offset值。
2.主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步。
哨兵模式
监控
Sentinel会不断检查master和slave是否按预期工作。
基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
自动故障恢复
如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主。
哨兵选主规则
首先判断主与从节点断开时间长短,如超过指定值就排该从节点。
然后判断从节点的slave-priority值,越小优先级越高。
如果slave-prority一样,则判断slave节点的offset值,越大优先级越高。
最后是判断slave节点的运行id大小,越小优先级越高。
通知
Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端。
哨兵模式脑裂
min-replicas-to-write 1 表示最少的salve节点为1个
min-replicas-max-lag 5 表示数据复制和同步的延迟不能超过5秒
分片集群
特征
集群中有多个master,每个master保存不同数据。
每个master都可以有多个slave节点。
master之间通过ping监测彼此健康状态。
客户端请求可以访问集群任意节点,最终都会被转发到正确节点。
哈希槽
Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
读写数据:根据key的有效部分计算哈希值,对16384取余(有效部分,如果key前面有大括号,大括号的内容就是有效部分,如果没有,则以key本身做为有效部分)余数做为插槽,寻找插槽所在的实例
Redis速度快的原因
1. Redis是纯内存操作,执行速度非常快,性能瓶颈是网络延迟而不是执行速度。
2. 采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题。
3. 使用I/O多路复用模型,非阻塞IO。
I/O多路复用模型
原理
是利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。
通知方式
select和poll只会通知用户进程有Socket就绪,但不确定具体是哪个Socket,需要用户进程逐个遍历Socket来确认。
epoll则会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间。