出现原因:
查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库。
原理:缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存
优点:简单
缺点: 消耗内存,可能会发生不一致的问题
原理:在进行缓存预热时,同时也会预热过滤器
,当查询一个不存在的数据时,会经过过滤器查询是否存在,若不存在,则直接返回,不会去查询redis,也不会去查数据库。
布隆过滤器的过滤流程:
原理就是将请求元素进行(3次)多次哈希,记录哈希值为1的区域,下次查询会根据请求元素计算的哈希值位置都为1 来判断是否需要去查询redis。
误判情况:
误判率:数组越小误判率就越大,数组越大误判率就越小,但是同时带来了更多的内存消耗。
出现原因:
给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮
原理就是当线程一进来去redis查询数据时,数据过期了,这时候线程一就会加上一把互斥锁,别的线程进来获得锁失败只能休眠或者重试,当线程一进行缓存重建完成后,会释放锁,别的线程进来也可以查到数据了。
原理就是:
①:在设置key的时候,设置一个过期时间字段
一块存入缓存中,不给当前key设置过期时间
②:当查询的时候,从redis取出数据后判断时间是否过期
③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新
缓存雪崩是指在同一时段
大量的缓存key同时失效
或者Redis服务宕机
,导致大量请求到达数据库,带来巨大压力。
双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据
,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。
参考链接:redis的延迟双删策略总结------作者:Hellboy_M
采用的是redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。
把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据
RDB文件是一种紧凑、可压缩的
二进制文件
,它包含了Redis的键值对
数据、过期时间
、数据类型等信息
RDB文件的加载
:当Redis服务器启动时,它会检查是否存在RDB文件。如果存在,Redis会读取RDB文件,并将其中的数据加载到内存中进行恢复。如果是通过
BGSAVE命令生成RDB文件
,那么Redis会在子进程中完成这个过程,然后继续处理客户端请求。
如果是通过SAVE命令生成RDB文件
,那么Redis会阻塞客户端请求,直到RDB操作完成才继续处理。
Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件
存储的是Redis服务器接收到的写操作命令。它记录了所有的写操作命令,包括对不同类型数据的操作。
因为是记录命令,AOF文件会比RDB文件大的多
。而且AOF会记录对同一个key的多次写操作
,但只有最后一次写操作才有意义
。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能
,用最少的命令达到相同效果。
AOF的执行原理?
RDB与AOF对比
设置该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中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
Redis支持8种不同策略来选择要删除的key:
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
(Least Recently Used)最近最少使用
。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
key1是在3s之前访问的, key2是在9s之前访问的,删除的就是key2
LFU
(Least Frequently Used)最少频率使用
。会统计每个key的访问频率,值越小淘汰优先级越高。
key1最近5s访问了4次, key2最近5s访问了9次, 删除的就是key1
数据淘汰策略-使用建议
Redis实现分布式锁主要利用Redis的setnx命令
。
死锁的情况,就是在拿到锁执行业务的时候,服务突然宕机,导致锁没有被释放
解决办法
就是给锁设置过期时间
锁的失效时长怎么控制:
但是这两种方式都不是很靠谱,实现起来也很复杂,可以使用redisson实现的分布式锁
其中:枷锁成功后,可以保证业务执行完成才会去释放锁,业务如果未完成,锁的时间到期了,看门狗会每隔30秒做一次续约,直到业务执行完成释放锁的时候会给看门狗一个信号,不需要对锁续时间了。
如果在执行线程一的时候,拿到了分布式锁,线程二也进来了,这个时候线程二想要拿到锁,发现拿不到,就会进行一个重试机制,当然如果重试到一定的次数会停止重试获取锁的操作。
参考链接:Redission可重入,锁重试,锁续约,watchDog机制------->作者:
alonePointer
锁重试和续约------>作者:阿千弟
针对同一个线程多次请求获取分布式锁的情况
,Redisson使用一个计数器来记录当前线程对锁的获取次数。初始时计数器为1,每次成功获取锁后,将计数器加1;每次释放锁后,将计数器减1。只有当计数器归零时,才会真正释放锁。
参考链接:Redission可重入,锁重试,锁续约,watchDog机制------->作者:
alonePointer
锁重试和续约------>作者:阿千弟
RedLock(红锁):不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n / 2 + 1),避免在一个redis实例上加锁。
使用redisson提供的红锁来解决,但是这样的话,性能就太低了,如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁。
参考链接:Redisson 分布式锁主从一致性问题-----作者:刘婉晴
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
一般redis都是
读多写少
,主节点执行写操作,然后同步到从节点,从节点只执行读操作。
其中:
Replication Id:
简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid。
offset:
偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
流程:
请求
主节点同步数据(replication id、 offset
)是否是第一次请求
,是第一次就与从节点同步版本信息(replication id和offset)生成rdb文件后,发送给从节点去执行
主节点会以命令的方式记录到缓冲区
(一个日志文件)从节点进行同步
主从是否同步取决于主从offset偏移量是否相等
第二部分:主从增量同步(一般是从节点slave重启或后期数据变化)
流程:
请求主节点同步数据
,主节点判断不是第一次请求
,不是第一次就获取从节点的offset值
命令日志中获取offset值之后的数据
,发送给从节点进行数据同步
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
其中服务状态监控:
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例
发送ping命令
:
主观下线:
如果某sentinel节点发现某实例未在规定时间响应
,则认为该实例主观下线。
客观下线:
若超过指定数量(quorum)的sentinel都认为该实例主观下线
,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
主与从节点断开时间长短
,如超过指定值就排该从节点slave-priority值,越小优先级越高
则判断slave节点的offset值,越大优先级越高
脑裂(Split Brain)是指由于网络分区或其他故障导致多个主节点同时存在的情况。这会导致数据不一致和服务不可用的问题。
由于主节点和从节点和sentinel处于不同的网络分区
,使得sentinel没有能够心跳感知到主节点,所以通过选举的方式提升了一个从节点为主,这样就存在了两个master
,就像大脑分裂了一样,这样会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将老的主节点降为从节点
,这时再从新master同步数据,就会导致数据丢失
.
解决:我们可以修改redis的配置,可以设置最少的从节点数量
以及缩短主从数据同步的延迟时间
,达不到要求就拒绝请求,就可以避免大量的数据丢失
主从解决高并发读的问题,和哨兵可以解决高可用的问题。但是依然有两个问题没有解决:
海量数据存储问题
写
的问题使用分片集群可以解决上述问题,分片集群特征:
多个master
,每个master保存不同数据
多个slave节点
master之间通过ping监测彼此健康状态
集群任意节点
,最终都会被转发到正确节点
Redis 分片集群引入了哈希槽的概念
,Redis 集群有 16384 个哈希槽
,每个 key通过 CRC16 校验
后对 16384 取模
来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
其中:Redis分片集群中数据是怎么存储和读取的?
根据key的有效部分计算哈希值
,对16384取余
(有效部分,如果key前面有大括号,大括号的内容就是有效部分,如果没有,则以key本身做为有效部分)余数做为插槽,寻找插槽所在的实例Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度, I/O多路复用模型主要就是实现了高效的网络请求。
参考链接:网络模型
Redis到底是单线程还是多线程?
核心业务部分(命令处理
),答案是单线程
整个Redis
,那么答案就是多线程
在Redis版本迭代过程中,在两个重要的时间节点上引入了多线程的支持:
4.0
:引入多线程异步处理一些耗时较旧的任务
,例如异步删除命令unlink核心网络模型中引入 多线程
,进一步提高对于多核CPU的利用率
因此,对于Redis的核心网络模型,在Redis 6.0之前确实都是单线程。是利用epoll
(Linux系统)这样的IO多路复用技术
在事件循环中不断处理客户端情况。
为什么Redis要选择单线程?
纯 内存操作
,执行速度非常快,它的性能瓶颈是网络延迟
而不是执行速度
,因此多线程并不会带来巨大的性能提升
。上下文切换,带来不必要的开销
线程安全问题
,必然要引入线程锁
这样的安全手段,实现复杂
度增高,而且性能也会大打折扣
执行流程:包括三种事件
也就是命令请求处理器在将客户端输入的命令(多线程下会有很多的请求,都等待着读)(此时是二进制)加入到缓冲区,并且解析除redis命令,这个过程是多线程的,至于执行命令把结果写入client队列之后的事还是单线程。
也就是通过命令回复处理器,开启多线程去客户端缓冲区去拿数据,在写出来,
需要指出的是,虽然 Redis 6.0 引入了多线程模型,但 Redis 的关键操作(如
命令执行、写操作
)仍然是单线程的
,这是为了保证数据的一致性
和避免竞态条件。多线程主要用于网络 I/O 操作
和接收客户端命令
,而实际的数据读写操作
仍然是单线程执行的
。因此,在 Redis 中,多线程并不意味着完全的并行执行
,仍然保持了单线程的简洁性和高性能
。
相关面试回答:
gitee-redis
素材来源:黑马程序员