面试--redis篇
Redis-使用场景
如果发生了缓存穿透、击穿、雪崩,该如何解决?
缓存穿透
查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存中,导致每次请求都查数据库
解决方案
- 缓存空数据,查询返回的数据为空,但仍将这个空结果缓存到redis中
- 优点:简单
- 缺点:消耗内存,可能会发生不一致的问题
- 布隆过滤器
- 优点:内存占用较少,没有多余key
- 缺点:实现复杂、存在误判
布隆过滤器原理
误判率:数组越小误判率就越大,数组越大误判率越小
缓存击穿
给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮
解决方案
互斥锁
逻辑过期
缓存雪崩
同一时间段大量的缓存key同时失效或者redis服务宕机,导致大量请求到达数据库,带来巨大压力
解决方案
- 给不同的key的TTL添加随机值
- 利用redis集群提高服务的可用性 (哨兵模式、集群模式)
- 给缓存业务添加降级限流策略 (nignx 或者spring cloud gateway)
- 给业务添加多级缓存 (Guava 或者 Caffeine)
mysql的数据如何与redis进行同步?(双写一致性问题)
一定一定要设置前提,先介绍自己的业务背景
双写一致:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致
延时删除:需要等一会主节点将数据同步到从节点中
删除两次:因为先删除缓存再修改数据库由于线程并发的缘故,可能会导致数据不一致性,所以再删除一次缓存
解决方案
读写锁
共享锁(读锁):读不互斥、写互斥
排他锁(写锁):读写都互斥
特点:强一致性、性能低
这个项目的秒杀券的库存是放到了缓存中,要求实时的数据同步,为了保证数据的一致性,当时采用的是redisson提供的读写锁保证数据同步
那你来介绍一下redisson读写锁的这种方案
强一致性的,采用读写锁
1、共享锁(读锁,readLock):加锁之后、其他线程可以共享读操作
2、排他锁(独占锁,writeLock):阻塞其他线程读写读写操作排他锁是如何保证读写、读读互斥的呢?
排他锁底层使用的是setnx,保证了同时只能有一个线程操作锁你听说过延时双删吗?为什么不用它?
延时双删,如果是写操作,操作就是先把缓存中的数据先删除,然后更新数据库,最后再延时删除缓存中的数据,其实这个延时多久不好确定,在延时的过程中可能出现脏数据,并不能保证强一致性,所以没有采用
异步通知
回答:这个项目中blog数据是放到缓存中,因为实时性要求不高,所以采用的是异步的方案同步
那你来介绍一下异步的方案?
允许延时一致的业务,采用异步通知
1、采用MQ中间件,更新数据之后,通知缓存删除,需要保证MQ的可靠性
2、采用canal中间件,不需要修改业务代码,伪装为mysql的一个从节点,canal通过读取binlog数据更新缓存
基于Canal
redis持久化问题
redis作为缓存,数据的持久化是怎么做的呢?
RDB
全称 redis Database backup file(Redis 数据备份文件),也被叫做Redis数据快照。把内存中的所有数据都记录到磁盘中,当redis实例故障重启后,从磁盘读取快照文件,恢复数据
RDB的执行原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取内存数据并写入RDB文件
fork采用copy-on-write技术:
- 当主进程执行读操作时,访问共享内存
- 当主进程执行写操作时,则会拷贝一份数据,执行读写操作
AOF
AOF全称为Append Only File(追加文件),Redis处理的每一个写命令都会记录在AOF文件,可以看作是命令日志文件
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF
AOF的命令记录频率也可以通过redis.conf文件来配
由于是记录命令,AOF文件比RDB文件大得多,而且AOF会记录对同一个key多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果
RDB和AOF的对比
如果对数据安全性较高,在实际开发中往往会结合两者来使用
参考回答
redis的过期策略
数据过期策略:redis对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除的规则就是数据的删除策略
惰性删除
设置key过期时间后,不去管它,当需要该key时,再检查其是否过期,如果过期,就删掉,反之,返回该key
- 优点:对CPU优好,只有在使用key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查
- 缺点:对内存不优好,如果一个key过期了,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放
定期删除
每隔一段时间,对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key)
两种模式
slow模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf的hz选项来调整这个次数
fast模式执行频率不固定,但两次间隔不超过2ms,每次耗时不超过1 ms
优点:通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响
缺点:难以确认删除操作执行的时长和频率
redis过期策略:惰性删除+定期删除两种策略进行配合使用
参考问答
redis的数据淘汰策略
当redis内存不够用时,需要再向redis中添加新的key,那么redis就会按照某一中规则将内存中的数据删除掉,这种数据的删除规则称为内存的淘汰策略
八种数据淘汰策略
使用建议
数据库中有1000万数据,redis只能缓存20万数据,如何保证redis中的数据都是热点数据?
使用allkeys-lru数据淘汰策略,留下来的都是经常访问的热点数据redis中的内存用完了会发生什么?
主要看数据淘汰策略是什么,如果是默认的配置,会直接报错
参考问答
分布式锁-使用场景
适用于集群环境下的定时任务、抢单、幂等性场景
redis实现分布式锁主要利用redis的setnx命令,setnx = set if not exists
redis实现分布式锁如何合理的控制锁的有效时长?
根据业务执行时间评估
给锁续期
Redisson实现分布式锁
- 可重入:同一个线程执行业务可两次获取相同的锁(依据线程id)
- 可超时续约:watchDog
- 可重试:订阅机制
redisson实现分布式锁主从一致性:
RedLock(红锁):不能只在一个redis实例中创建锁,应该在多个redis实例中创建锁(n/2+1),避免在一个redis实例上加锁
问题
- 实现复杂
- 性能差
- 运维繁琐
参考问题
但是可以采用redisson提供的红锁解决,但是这样的话性能较低,如果非要保证数据的强一致性的话,建议采用zookeeper实现的分布式锁
redis其他问题
redis集群有哪些方案
主从复制
主从同步原理
主从全量同步
-
主从增量同步
(slave重启或者后期数据变化)
介绍一下redis的主从同步
单节点的redis并发能力是有限的,为了提高redis的并发能力,需要搭建redis集群,一般是一主多从,主节点负责写操作,从节点负责读操作
能说一下主从同步数据的流程吗?
参考问答
哨兵模式
实现主从集群的自动故障恢复
- 监控:Sentinel(哨兵)会不断检查master和slave是否按照预期工作
基于心跳机制检测服务状态,每隔1秒向集群中的每个实例发送ping命令
- 主观下线:如果某个sentinel节点发现某实例未在规定时间内响应,则认为该实例主观下线
- 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过sentinel数量的一半
- 自动故障恢复:如果master故障,sentinel会将一个slave提升为master。当故障恢复后以新的master为主
哨兵选主规则
- 首先判断从节点断开时间的长短,多超过指定值就排除该从节点
- 然后判断从节点的slave-priority,越小优先级越高
- 如果slave-priority相同,则判断slave节点的offset值,越大优先级越高
- 最后判断salve节点的运行id大小,越小优先级越高
- 通知:当集群发生故障master节点转移时,会将最新的信息推送给redis的客户端
哨兵模式可能的其他问题-脑裂
由于网络原因,sentinel访问不了主节点,那么按照哨兵规则,他会将一个从节点升为主节点,这是集群中就会出现两个master节点
如果此时老的master网络恢复了,那么sentinel会将老的master强制转为slave
这时新的master会进行数据同步,老的master会将自己的数据清空
min-replicas-write 1 表示最少的slave节点为1(设置至少1个才能同步数据)
min-replicas-max-lag 5 表示数据复制和同步的延迟不超过5秒(5秒内slave必须同步master的数据,缩小数据丢失的时间)
如何保证redis的高并发高可用
哨兵模式:实现主从集群的自动故障恢复(监控、自动故障恢复、通知)你们使用的redis是单点还是集群,哪种集群
主从(一主一从)+ 哨兵就可以了,一般单节点不超过10G内存,如果redis内存不足则可以给不同服务分配独立的redis主从节点redis集群脑裂,该怎么解决?
集群脑裂是由于主节点和从节点和sentinel处于不同的网络分区,当由于网络问题,sentinel没有感知到主节点时,就会选取一个slave升为master,这时就会有两个master,就像大脑分裂了一样。这样会导致客户端还在老的master节点那里写入数据,而这些新数据无法同步。当网络恢复后,sentinel会将老的master节点将为slave节点,这是再从新的master同步数据,而原来的数据会被清空,导致数据丢失
解决办法:修改redis的配置,可以设置最少的从节点数量以及缩短主从同步数据同步的延迟时间
参考问答
分片集群
有两个问题没有解决
海量数据存储问题
高并发写的问题
分片集群的特征
数据读写的流程
redis分片集群中引入了哈希槽的概念,redis集群中有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放到那个槽,集群的每个节点负责一部分hash槽
分片集群有什么作用?
集群中有多个master,每个master保存不同数据(存储海量数据,高并发写)
每个master都可以有多个slave(高并发读)
master之间通过ping检测彼此健康状态
客户端请求可以访问集群任意节点,最终都会转发到正确节点(自动选择路由)redis分片集群中数据是怎么存储和读取的?
redis分片集群中引入了哈希槽的概念,redis集群中有16384个哈希槽
将16384个插槽分配到不同的实例
读写数据:根据key的有效数据计算哈希值,对16384取余(有效部分,如果key前面有大括号,大括号中的内容就是有效部分,如果没有,key本身作为有效部分),余数做为插槽,寻找插槽所在的实例
参考问答
redis是单线程的,为什么还那么快
- redis是纯内存操作,执行速度非常快
- 采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
- 使用I/O多路复用模型,非阻塞IO
解释一下I/O多路复用模型
redis的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要就是实现了高效的网络请求
前置知识:
用户空间和内核空间
常见的IO模型:阻塞IO、非阻塞IO、IO多路复用
redis网络模型
用户空间和内核空间
阻塞IO
非阻塞IO
IO多路复用
具体的监听方式
redis网络模型
redis支持不同的多路复用实现,并将这些实现进行封装,提供了统一的高性能事件库
多线程
能解释一下IO多路复用吗?
1、指利用单线程同时监听多个socket,并在某个socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的IO多路复用都是采用的epoll模式实现,他会在通知用户进程Socket就绪的同时,把已就绪的socket写入用户空间,不需要挨个遍历socket来判断是否就绪,提升了性能
2、redis网络模型
使用IO多路复用结合事件的处理器来应对多个socket请求
连接应答处理器
命令回复处理器,在redis6.0之后,为了提升更好的性能,使用了多线程来回复处理事件
命令请求处理器,在redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程