当前位置:   article > 正文

JAVA面试八股文之Redis相关

JAVA面试八股文之Redis相关

在这里插入图片描述

Redis6.0为什么要用多线程?

提高请求数,redis的瓶颈在于内存,使用分一个子线程去进行IO操作

在Redis中存一个list集合怎么实现排序?

list类型中有一个sort命令可以排序。(sort key)

Redis的5大基本类型的底层原理?

Redis底层使用一个redisObject来表示所有的key和value
在这里插入图片描述
type:表示属于哪种基本类型;
encoding:表示底层使用数据结构;
ptr:指向底层数据结构的指针;
vm:虚拟内存,默认是关闭的;

  • String:动态字符串SDS
  1. int:字符串类型的整型,底层使用int实现,比如set a 100;
  2. raw:动态字符串,大于39字节,分配两次;
  3. embstr:动态字符串,小于39字节,内存类型,只分配一次;
  • List 类型:链表
  1. zipList:列表的长度小于512,元素的字节数小于64,使用ziplist,它进行了压缩;
  2. linkedList:不满足上面的条件就使用linkedlist,双向链表;
  • set类型:intset或者hashtable
  1. intest:集合长度512且元素都是整数,使用intset;
  2. hashtble:hashtable底层使用字典结构(hash),键为字符串对象,value为null;
  • hash类型:ziplist或者hashtable
  1. 键值对数量小于512,且所有键值对长度小于64字节,使用ziplist压缩;
  2. 键值对数量大于512,且其中有键值对长度大于64字节,使用hashtable;
  • zset类型:ziplist或者skiplist
  1. 集合长度小于128,元素的字节数小于64使用ziplist压缩;
  2. 上面不满足使用skiplist,底层使用跳表实现;

缓存穿透?

查询到空数据,缓存中没有,数据库中也没有,当有大量的空数据访问,会造成数据库压力过大,容易宕机。

解决方法:

1.缓存空数据:查到数据库中也没有,在缓存中缓存一个空数据,下次访问直接从数据库中获取

2.布隆过滤器:用于检索一个数据是否存在于一个集合中,(底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一个key的存在。)访问时先判断布隆过滤器中是否有数据,没有则直接返回。不过存在误判问题。

缓存击穿?

对于设置了过期时间的数据,缓存在某个时间点过期,而恰好这个时间有大量的数据进行访问。

解决方法:
双重锁

互斥锁:在缓存中没有查询到时,先获取互斥锁,查询数据库重建缓存,然后释放锁。此时其他线程只能等待。

(强一致性,性能差)

逻辑过期:在缓存中没有查询到时,一个线程先获取到互斥锁,再开一个线程去查询数据库重建缓存,其他线程则返回过期的时间。(高可用,性能优)

缓存雪崩?

大量的缓存在同一时间失效,或者redis宕机,请求直接到数据库。

解决方法:

给key的过期时间设置一个随机值,

搭建redis集群

给业务添加多级缓存,

给业务添加降级限流策略。

redis做为缓存怎么保持和mysql数据进行同步?(双写一致性)

存强一致性的

  • redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。(强一致性)

允许延时一致的业务

  • 写入数据库后,清空缓存

  • 设置过期时间来保证最终一致性

  • 使用canal同步(阿里的canal组件实现数据同步:不需要更改业务代码,部署一个canal服务。canal服务把自己伪装成mysql的一个从节点,当mysql数据更新以后,canal会读取binlog数据,然后在通过canal的客户端获取到数据,更新缓存即可。)

  • 使用MQ中间中间件,更新数据之后,通知缓存删除

先删除缓存或者删除数据库都会出现问题,所有需要延时双删
在这里插入图片描述

redis做为缓存,数据的持久化是怎么做的?

redis的数据持久化有两种,RDB和AOF

RDB:是以快照的形式把redis存储在内存的数据转到磁盘上去,当redis宕机恢复后,再从快照文件中加载数据。(二进制文件,体积小,恢复快)

快照分为手动快照和自动快照:

手动快照分为前台快照(save)和后台快照(bgsave);

AOF:含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据,因为AOF文件中保存的都是命令,所以必然会出现重复的命令,Redis采用重写机制来解决这个问题,重写要解决的问题就是删除AOF文件中重复的命令,已经被删除或者过期的命令。(默认关闭)
在这里插入图片描述
RDB缺点:容易丢失数据,耗时效率低;
RDB优点:适用于数据备份,方便传输,恢复大数据快,最大化redis性能(后台快照)
AOF优点:数据更加耐久(一旦出现故障最多丢失一秒数据),只进行追加的日志文件,体积过大会进行重写,容易读懂;
AOF缺点:体积大,速度低;
实际是两个一起用;

Redis的数据过期策略有哪些 ?

惰性删除和定期删除

惰性删除:当key过期时,只有当需要key时,去查询key判断是否过期,当过期就删除该key。

定期删除:每隔一段时间就对一些key检查,过期就删除。

Redis的数据淘汰策略有哪些 ?

这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内存不足直接报错

是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU。

LRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?

可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据。

Redis分布式锁如何实现 ?

在redis中提供了一个命令setnx(SET if not exists)

由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,只有当redis中没有这个key时才可以添加。(在没有过期或删除key的时候是其他客户端是不能设置这个key的

如何控制Redis实现分布式锁有效时长呢?

采用的redis的一个框架redisson实现的。在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间(每个锁默认时间是30s,如果30s之内业务代码没有执行完,会有定时任务自动续期,(1/3的时间)10s中续期1次,续的的时间还是30s)就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了。
在这里插入图片描述

还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自旋不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。

/**
1、加锁,默认的超时时间是看门狗的时间(30s)
2、自动给锁续期(原理是只要占锁成功会启动一个定时任务给锁重新设置超时时间,默认续期的超时时
间是看门狗的时间,定时任务会在三分之一的看门狗的时间被触发一次)
3、获取不到锁阻塞阻塞
4、锁一旦释放了就不在续期
**/
lock();

/**
1、加锁,用户指定获取锁等待的时间
2、会自动给锁续期
3、等待指定的时间后返回布尔值
// 一般使用这个方法比较多,锁的超时时间就是业务执行的最大时间。超过这个时间说明业务执行出
问题了,应该马上释放锁,因为续期没有意义了。
*/
lock(10, TimeUnit.SECONDS); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

redisson实现的分布式锁是可重入的吗?

是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数。

redisson实现的分布式锁能解决主从一致性的问题吗?

这个是不能的,比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。

我们可以利用redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,并且要求在大多数redis节点上都成功创建锁,红锁中要求是redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。

但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变的很低了,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁。

如果业务非要保证数据的强一致性,这个该怎么解决呢?

redis本身就是支持高可用的,做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用zookeeper实现的分布式锁,它是可以保证强一致性的。

Redis集群有哪些方案?

在Redis中提供的集群方案总共有三种:主从复制哨兵模式Redis分片(cluster)集群。

什么是主从同步?

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中。
在这里插入图片描述

主从同步数据的流程

主从同步分为了两个阶段,一个是全量同步,一个是增量同步

全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:

第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。

第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。

第三:在同时主节点会执行bgsave,生成RDB文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的RDB文件,这样就保持了一致。

当然,如果在RDB生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步。
在这里插入图片描述

增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步
在这里插入图片描述

怎么保证Redis的高并发高可用?

首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用。
在这里插入图片描述
在这里插入图片描述

redis集群脑裂,该怎么解决呢?

有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old master降为salve,这时再从新master同步数据,这会导致old master中的大量数据丢失。
解决方法,可以在redis的配置中可以设置:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失。

redis的分片集群有什么作用?

分片集群主要解决的是,海量数据存储的问题,集群中有多个master,每个master保存不同数据,并且还可以给每个master设置多个slave节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点。
在这里插入图片描述

Redis分片集群中数据是怎么存储和读取的?

Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。取值的逻辑是一样的。
在这里插入图片描述

Redis是单线程的,但是为什么还那么快?

1、完全基于内存的,C语言编写。

2、采用单线程,避免不必要的上下文切换可竞争条件。

3、使用多路I/O复用模型,非阻塞IO。

例如:bgsave 和 bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞。

能解释一下I/O多路复用模型?

I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。

其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;

在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。
后记

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/474031
推荐阅读
相关标签