1、什么是缓存穿透 ? 怎么解决 ?
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存就形同虚设(只有数据库查到了,才会让redis缓存,但现在的问题是查不到),会频繁的去访问数据库。
解决方案:
- 缓存空对象:如果该数据在缓存和数据库中都不存在,就缓存一个空值到redis中,并且超时时间设置得短一点,如2分钟,以防占用太多redis空间。
- 布隆过滤:布隆过滤器是处于redis之前的一段过滤器,底层是根据哈希来实现的,客户端的所有请求都会通过该过滤器进行过滤,由于哈希的性质,若该过滤器都查不到数据,则直接返回错误信息;若查到了则放行,但也不一定存在该数据(存在哈希冲突)。
2、什么是缓存击穿? 怎么解决 ?
缓存击穿也叫热点Key问题,一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增。
解决方案:
- 互斥锁:只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求。
- 优点:强一致性。
- 缺点:性能差。
- 逻辑过期:①在缓存中多设置一个逻辑过期字段,而不真正设置过期时间。②查询时,通过过期字段来判断当前key是否过期。③若过期,则另外开一个线程去数据库查询并同步缓存数据,当前线程则返回旧数据。
- 优点:性能高,具有高可用性。
- 缺点:无法保证数据绝对一致。
3、什么是缓存雪崩? 怎么解决 ?
缓存雪崩是指在同一时间段,大量缓存的key同时失效,或者Redis服务宕机,导致大量请求到达数据库。
解决方案:
- 给不同的Key的TTL添加随机值,让其在不同时间段分批失效。
- 利用Redis集群提高服务的可用性。(哨兵模式,集群模式)
- 给缓存业务添加降级限流策略。(保底策略)
- 给业务添加多级缓存。(可以理解为穿了好几件防弹衣)。
4、redis做为缓存,mysql的数据如何与redis进行同步呢?
业务中有优惠券秒杀功能,要求实时性比较高,因此采用读写锁保证redis和mysql的强一致性。 主要采用redisson实现的读写锁,读的时候添加共享锁(读锁),保证读读不互斥,读写互斥;更新数据的时候添加排他锁(写锁),读读、读写都互斥。 这样可以防止写数据的时候其他线程读数据,避免了脏数据。
排他锁是如何保证读写、读读互斥的呢?
排他锁底层使用的是setnx,保证了同时只能有一个线程操作锁住的方法。
延时双删
延时双删也是分布式系统中保持redis和mysql一致性的常用策略,但不具有强一致性。
- 延时双删:当前为写操作时,先删除redis中的缓存,再更新数据库,短暂延时之后再次删除redis中的缓存。
- 为什么要两次删除redis缓存?
- 防止数据库还没更新完,有别的线程读取了数据库的脏数据,并更新redis缓存。
- 为什么要延时删除?
- 数据库一般是主从模式,需要给主节点一些时间同步数据到从节点中。
- 为什么要两次删除redis缓存?
5、redis做为缓存,数据的持久化是怎么做的?
Redis是内存数据库,宕机后数据会消失,需要提供持久化策略。在Redis中提供了两种数据持久化的方式:RDB 和 AOF。
- RDB(Redis DataBase):是一个快照文件,它把redis内存存储的数据写到磁盘上,当
redis 宕机恢复数据的时候,方便从 RDB 的快照文件中恢复数据。
-
优点:RDB是二进制压缩文件,占用空间小,便于传输,恢复数据速度较快。
- 缺点:不能保证数据的完整性,两次备份之间会有数据丢失。
- 创建RDB文件的两个命令:
- SAVE:会阻塞Redis服务器进程,直到RDB文件创建完毕为止。
- BGSAVE:会派生出一个子进程负责创建RDB文件,父进程继续处理命令请求。
-
- AOF(append only file):当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据。(AOF会记录过程,RDB只管结果)
- 优点:文件较大,恢复速度较慢。
- 缺点:数据的完整性较高。
6、Redis的数据过期策略有哪些 ?
Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。
- 惰性删除:设置该key过期时间后,不去管它,当需要该key时,再检查其是否过期,如果过期,我们就删掉它,反之返回该key。
- 优点:对CPU友好,只在使用该key时才进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。
- 缺点:对内存不友好,过期的key将一直存在于内存中不会释放。
- 定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key。
- 优点:可以通过限制删除操作执行的时长来减少对 CPU性能的影响。
- 缺点:这个操作时长难以把控。
7、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和LFU两种策略:
- LRU(Least Recently Used):最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
- LFU(Least Frequently Used):最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。
数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中数据都是热点数据 ?
使用allkeys-lru(最近最少使用)淘汰策略,留下来的都是经常访问的热点数据。
Redis的内存用完了会发生什么?
看数据淘汰策略是什么,如果是默认的配置( noeviction ),会直接报错。
8、Redis分布式锁的实现
项目中的优惠券秒杀抢单功能存在超卖问题,可以使用Synchronized锁解决。但如果是在集群模式下, 多台服务器会对应多个jvm, synchronized锁可以锁住单台服务器的多线程,多台服务器就锁不住了,此时需要有一个多服务器共享的锁监视器,即分布式锁。
Redis实现分布式锁主要利用Redis的setnx命令(SET if not exists) ,该命令需要设置锁的过期时间,以防止服务宕机从而导致锁永远无法释放的问题。这个过期时间设置长了会影响性能,设置短了又可能会提前释放锁导致线程安全问题,这就需要合理的控制锁的有效时长。
Redisson实现分布式锁如何合理的控制锁的有效时长?
于是我们使用采用redisson实现的分布式锁,底层是setnx和lua脚本(保证原子性)。在redisson的分布式锁中,提供了一个WatchDog(看门狗)机制:一个线程获取锁成功以后,会定期给锁续期(默认每10s续期一次)。
Redisson的这个锁,可以重入吗?
可重入,这样做是为了避免死锁的产生。多个锁重入需要判断是否是同一线程,在redis中进行存储的时候使用的hash结构,来存储线程信息和重入的次数:大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数。
Redisson锁能解决主从数据一致的问题吗?
不能解决,但可以使用redisson提供的红锁来解决,但是使用红锁性能太低了,如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁。
9、Redis的集群方案
主从复制
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中。
- 全量同步:从节点第一次与主节点建立连接的时候使用全量同步。
-
从节点请求主节点同步数据,从节点会携带自己的replication id和offset偏移量。
-
主节点判断是否是第一次请求,主要判断依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己replication id和offset发送给从节点,让从节点与主节点的信息保持一致。
-
在同时主节点会执行bgsave生成rdb文件,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致。
-
-
增量同步:
-
从节点请求主节点同步数据,主节点还是判断是不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步。
-
10、怎么保证Redis的高并发高可用?
首先可以搭建主从集群(解决高并发),再加上使用redis中的哨兵模式(解决高可用),哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;
如果master故障,Sentinel会将一个slave提升为master。 当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用。
11、redis的分片集群
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决: 海量数据存储问题 和 高并发写的问题。
分片集群的作用:
- 集群中有多个master,每个master保存不同数据。(解决海量数据存储和高并发写的问题)
- 每个master都可以有多个slave节点。(解决高并发读的问题)
- master之间通过ping监测彼此健康状态。
- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点。(路由)
Redis分片集群中数据是怎么存储和读取的?
- Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽。
- 将16384个插槽分配到不同的master节点。
- 读写数据:根据key的有效部分计算哈希值,对16384取余。(有效部分,如果key前面有大括号,大括号的内容就是有效部分,如果没有,则以key本身做为有效部分)余数做为插槽,寻找插槽所在的节点。
12、Redis是单线程的,但是为什么还那么快?
- Redis是基于C语言编写,是基于内存操作,执行速度非常快。
- 采用单线程,避免不必要的上下文切换,多线程还需要考虑线程安全问题。
- 使用了I/O多路复用模型,非阻塞IO。
13、I/O多路复用模型
Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度, I/O多路复用模型主要就是实现了高效的网络请求。
- 阻塞IO:用户区进程在“内核区准备数据”和“拷贝数据”两个阶段都处于阻塞状态。
- 非阻塞IO:第一个阶段是非阻塞,第二阶段阻塞。虽然是非阻塞,但都是忙等,没有提高性能,还会使CPU空转,使用率暴涨。
- IO多路复用:是利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。
- 监听Socket的方式:
- select、poll:只会通知用户进程有Socket就绪,但不确定具体是哪个Socket ,需要用户进程逐个遍历Socket来确认。
- epoll:在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间。
- 监听Socket的方式:
redis使用I/O多路复用结合事件的处理器来应对多个Socket请求,
- 连接应答处理器
- 命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程来处理回复事件
- 命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程