1.Redis的使用场景:
①缓存:穿透、击穿、雪崩问题;双写一致、持久化问题;数据过期、淘汰策略问题。
②分布式锁:setnx、redisson。
③计数器
④保存token
⑤消息队列
⑥延迟队列
2.Redis缓存穿透如何解决?
缓存穿透:查询一个不存在的数据,DB查询不到数据也不会写入Redis中,导致每次请求都会查询数据库,数据库压力过大会宕机(通过假的id恶意查询)。
解决方案:
①缓存空数据(查询结果为空的数据,仍把空结果进行缓存{key:1,value:null});
缺点:内存消耗,存在数据不一致的问题(查询后数据更新,再次查询缓存中仍为null)。
②采用布隆过滤器(拦截不存在的数据,即请求先通过布隆过滤器,若存在则进入缓存查询,否则直接返回。注意:缓存预热时需要预热布隆过滤器);
布隆过滤器:一个存放二进制0或1的数组,通过对key三次hash取得对应数组下标并将其初始化的二进制0改为1。查询时三次hash后的数组下标内的值都为1则存在,否则不存在。
缺点:实现复杂,存在误判(查询数组下标内存放的值正好都为1,可通过设置误判率来解决或增大数组长度)。
3.缓存击穿问题是什么?如何解决缓存击穿?
缓存击穿:设置为过期时间的key过期时,恰好该时间对这个key有大量的并发请求,请求需从后端DB加载数据并回设到缓存中,大量并发的请求可能会瞬间压垮数据库
解决方案:
①添加互斥锁(具有强一致性,性能差):只能有一个线程获取锁并重建数据,在该线程写入缓存之前,其他线程需循环等待。
②逻辑过期(具有高可用性,性能优):获取到互斥锁的访问线程会重新开启一个线程重新构建数据库,开启后该线程以及在数据存入缓存之前访问的线程均返回过期数据,数据存入缓存后访问的线程获取新数据。
4.什么是缓存雪崩?如果发生缓存雪崩该如何解决?
缓存雪崩:是指在同一时段大量的缓存key同时失效或Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
①给不同的key设置不同的过期时间(如:对key的TTL添加随机值,已降低过期重复率);
②对于Redis服务宕机问题,可以利用Redis集群提高服务的可用性(如:哨兵模式、集群模式);
③给缓存业务添加降级限流策略(如:在ngxin或spring cloud gateway中设置限流规则),该策略可以作为系统的保底策略,适用于穿透、击穿、雪崩;
④给业务添加多级缓存(如:添加Guava或Caffeine作为一级缓存、Redis作为二级缓存)。
5.MySQL的数据如何与Redis进行同步呢(即双写一致性)?
先介绍业务背景:是对一致性要求高?还是允许延迟一致?
(1)双写一致性(即实时性较高):当修改了数据库的数据同时也需要更新缓存中的数据,要使缓存和数据库中的数据保持一致。
读数据:缓存命中,直接返回;缓存未命中,查询数据库写入缓存,设定超时时间。
写数据:延迟双删(即删除缓存----修改数据库--(延时)--删除缓存),该方法不可用存在一下问题。
●先删除缓存还是先修改数据库(都可能存在脏数据的问题)
●为什么要删除两次缓存?(存在脏数据的风险,所以删除两次,但仅能控制一部分)
●为什么要延时删除?(采用中从结构,主节点更新后,从节点需要一定的时间更新,而延时多久不好控制,延时过程中肯能存在脏数据)
因此一般不采用延迟双删的方法,而是过添加分布式锁(排他锁、共享锁)来解决读脏数据的问题,该方法保证了数据的强一致性,但性能较差。
(2)允许数据短暂的不一致(即实时性不是很高,可采用异步的方式)
①异步通知保证数据的最终一致性
②基于Canal的异步通知
6.Redis作为缓存,数据的持久化是什么做的?
①RDB(Redis数据快照)----即将内存中的所有数据都记录到磁盘中,当Redis实例故障重启后,从磁盘读取快照文件恢复数据。
②AOF(追加文件)----即处理每一个写命令时都会记录在AOF文件中,可以看作是命令日志文件。AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF。
7.redis的key过期后,会立即删除吗?
数据过期策略:当Redis中数据所设置的有效时间过期后,需要将数据从内存中删除。此时可以按照不同的规则(即数据的删除策略,例如:惰性删除、定期删除,注:一般采用两者配合使用)进行删除。
①惰性删除----设置该key过期后,不去管它,当需要key时再检查是否过期,如果过期就删掉该数据,否则返回该key。
注意:对CPU友好,无需花费时间检查key是否过期;但是对内存不友好,如大量key过期且一直没有使用,则大量内存得不到释放。
②定期删除----每隔一段时间,就会对一些key进行检查,删除里面过期的key。存在SLOW模式和FAST模式。
注意:可通过限制删除操作执行的时长和频率来减少删除操作对cpu的影响,但难以确定删除操作执行的时长和频率。
8.假如缓存过多,内存是有限的,内存被占满了怎么办?
数据的淘汰策略:当Redis中内存不够用时,此时在想向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
Redis中支持的8种不同策略:
①noeviction:不淘汰任何key,内存满时不允许写入新数据,该策略为Redis的默认策略。
②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算法进行淘汰。
使用建议:
●优先使用allkeys-lru策略。充分利用LRU算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。
●如果业务中数据访问频率差别不大,没有明显的冷热数据区分,建议使用allkeys-random,随机选择淘汰。
●如果业务中有置顶的需求,可以使用volatile-lru策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,而是会淘汰其他设置过期时间的数据。
●如果业务中有短时高频访问的数据,可以使用allkeys-lfu或volatile-lfu策略。
9.Redis作为分布式锁,是如何实现的?
应用场景:秒杀抢购、定时任务、幂等性场景。
注意:抢单任务可通过加锁解决,本地jvm下加synchronized锁可解决;如果在集群情况下,synchronized锁只能用于本地多线程情况,无法解决集群下的多线程冲突,这时需要加入外部锁(分布式锁)来解决冲突。
采用redisson实现的分布式锁,其底层是setnx和lua脚本(保证原子性);redisson实现的分布式锁中的有效时长控制,通过WatchDog来监控线程任务是否执行完成,若没有则会给持有锁的线程续期(默认是每隔10秒续期一次)。
redisson锁是可重入锁,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用hash结构来存储线程信息和重入次数。
redisson锁不能解决主从数据一致的问题,但可以提供红锁来解决,使用红锁性能将会降低。如果业务中非要保证数据的强一致性,建议采用zookeeper实现。
10.Redis集群有哪些方案?
Redis的集群方案:主从复制、哨兵模式、分片集群。
(1)主从复制(解决高并发问题):单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据。
主从数据同步的流程:
1.全量同步:
①从节点请求主节点同步数据(replication id、offset);
②主节点判断是否是第一次请求,是第一次请求与从节点同步版本信息(replication id和offset);
③主节点执行bgsave生成RDB文件,并发送给从节点去执行;
④在RDB生成执行期间,主节点会以命令的方式记录到缓存区(一个日志文件);
⑤把生成之后的日志文件发送给从节点进行同步。
2.增量同步:
①从节点请求主节点同步数据,主节点判断是不是第一次请求,不是第一次就获取从节点的offset值;
②主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步。
(2)哨兵模式(解决高可用问题):主从集群不能保证集群的高可用,当主节点宕机后集群丧失了写数据的能力,而Redis提供了哨兵(sentinel)机制来实现主从集群的自动故障恢复。作用如下:
①监控----Sentinel会不断检查master和slave是否按预期工作;
②自动故障恢复----如果master故障,sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;
③通知----sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新消息推送给Redis的客户端。
Redis集群的脑裂问题:由于网络原因,主节点master和哨兵处于不同的网络分区,哨兵检测不到主节点从而去检测从节点,从而导致哨兵将从节点选为新的主节点,从而导致出现两个主节点master。而客户端仍然链接的旧的master,当网络恢复后,哨兵会将旧的master强制降为slave并与新的master进行同步数据(同步数据时,旧的master会清空节点内数据),从而导致网络问题期间客户端写入旧的master中的数据丢失。
解决:可以修改Redis的配置,可以设置最少的节点数量(min-replicas-to-write)以及缩短主从数据同步的延迟时间(min-replicas-max-log),达不到要求就拒绝请求,就可以避免大量数据丢失。
(3)分片集群(解决海量数据存储和高并发写的问题):
◆集群中有多个master,每个master保存不同数据,且都可以有多个slave节点;
◆master之间通过ping监测彼此之间的状态;
◆客户端请求可以访问集群任意节点,最终都会被转发到正确的节点。
其Redis分片集群中的数据存储和读取如下:
●Redis分片集群引入哈希槽的感念,Redis集群有16384个哈希槽,并将16384个插槽分配到不同的实例;
●读写数据:根据key的有效部分(若key前面有大括号,则大括号内的内容就是有效部分;否则,key本身作为有效部分)计算哈希值,对16384取余,余数作为插槽位置,寻找插槽所在的实例。
11.Redis是单线程的,但是为什么还是那么快?
(1)Redis是纯内存操作,所以执行速度非常的快;
(2)采用单线程避免了不必要的上下文切换可竞争条件,多线程则还要考虑线程安全问题;
(3)使用I/O多路复用模型,非阻塞IO。
12.解释一下I/O多路复用模型?
Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要就是实现了高效的网络请求。
常见的IO模型:阻塞IO(Blocking IO)、非阻塞IO(Nonblocking IO)、IO多路复用(IO Multiplexing)。
I/O多路复用:是指利用单个线程来监听多个Socket,并在某个Socket可读、可写时得到通知,从而避免无效等待,充分利用cpu资源。目前的I/O多路复用都是采用epoll模型实现的,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,从而提升性能。
监听Socket的方式:select、poll、epoll。其中select和poll只会通知用户进程有Socket就绪,但不确定具体是哪个Socket,需要用户进程逐个遍历Socket来确定;而epoll则会在通知用户进程Socekt就绪的同时,把已就绪的Socket写入用户空间。
Redis网络模型:就是使用I/O多路复用结合事件的处理器来应对多个Socket请求
▶连接应答处理器;
▶命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程处理回复事件;
▶命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。