Redis面试题

一、Redis缓存

1、缓存预热、缓存穿透、缓存雪崩、缓存击穿

  • 缓存预热: 系统上线后,提前将相关数据加载到缓存中,避免用户先查库,然后再查缓存。

  • 缓存穿透: 指缓存和数据库中都没有的数据,导致所有的请求都落在数据库上,造成数据库短时间内承受大量请求而崩掉。
    解决方案:
    1)缓存空对象:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
    2)布隆过滤器:是指在客户端和redis之间又加了一层布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储查询压力,但不一定是准确的,返回存在却不一定是存在。
    Redisson实现的布隆过滤器:底层主要是先去初始化一个比较大的数组,里面存放的是二进制0或1,在一开始全是0,当存储一个key时,经过3次hash计算,模于数组长度找到数据的下标后把数组中原来的0改为1,这样就能通过三个数组的位置标明一个key的存在。
    缺点:存在一定的误判率,一般可以设置这个误判率,5%以内一般都能接受。

  • 缓存雪崩: 指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
    解决方案:
    1)给不同key的TTL(过期时间)添加随机值,将失效时间分散开来。
    2)缓存预热:指系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题

  • 缓存击穿: 缓存击穿问题也叫热点key问题,就是被一个高并发访问并且缓存重建业务较复杂的key突然失效了,无数请求访问会在瞬间给数据库带来巨大的冲击。
    解决方案:
    1)互斥锁:用锁的方式只让一个线程来重建缓存数据,其他线程等待缓存构建。适用于强一致性,性能没那么高
    2)逻辑过期:设置热点key永不过期,一个线程来获取互斥锁开启写入线程,其他线程获取互斥锁失败,则获取缓存中的旧数据。适用于不严格要求数据一致性。

2、redis作为缓存,MySQL的数据如何与缓存进行同步(保证双写一致性)

  • 采用redisson实现的读写锁来保证强一致性:
    在读的时候添加 共享锁,可以保证读读不互斥,读写互斥。更新数据时,使用 排他锁,读写、读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。需注意要将读方法和写方法上的锁使用同一把锁。
    排他锁底层使用的是setnx,保证了同时只能有一个线程操作锁住的方法。
    延迟双删: 如果是写操作,先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能出现脏数据,并不能保证强一致性,所以没有采用它。

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

3、redis作为缓存,如何持久化数据?

  • redis中提供了两种数据持久化的方式:RDB、AOF。
  • RDB是一个快照文件,他是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。
  • AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据。
  • 两种方式中,RDB恢复的速度比较快。因为是二进制文件,在保存的时候体积也比较小,所以恢复的比较快,但是它有可能会丢数据,通常项目中也会使用AOF来恢复数据,虽然AOF恢复数据比较慢,但是数据安全要高些,在AOF文件中可以设置刷盘策略,如可设置每秒批量写入一次命令。
    在这里插入图片描述在这里插入图片描述

4、redis的key过期后会立即删除吗?(redis的数据过期策略)

  • Redis对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉,可以按照不同的规则进行删除,这种删除规则就被称之为数据的过期删除策略。
    在这里插入图片描述

5、假如缓存过多,内存有限,被占满了怎么办?(redis的数据淘汰策略)

  • 当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
  • 在redis这提供了8种不同的策略,默认使用的是noeviction,不删除任何数据,内存不足直接报错。
  • 数据库有1000万数据,Redis只能缓存20w数据,如何保证Redis中的数据都是热点数据?
    使用allkeys-lru策略,挑选最近最少使用的数据淘汰,留下的都是经常访问的热的数据。
  • Redis的内存用完了会发生什么,主要看数据淘汰策略是什么,如默认使用的是noeviction,会直接报错。
    在这里插入图片描述

6、

二、Redis分布式锁

1、Redis分布式锁如何使用?(使用场景)

  • 举例:
    抢券功能:如果是单体项目,直接用synchronized(this)同步代码块就能解决并发问题。但如果是服务集群部署,将同一份代码部署在多台机器中,则不能再使用同步锁,此时则可以使用redis的分布式锁来解决。

  • 分布式锁所要解决的问题的本质是:能够对分布在多台机器中的线程对共享资源的互斥访问。

  • Redis实现分布式锁主要利用redis的 setnx 命令。setnx是SET if not exists(如果不存在,则SET)的简写。

// 添加锁 NX是互斥,EX是设置超时时间
// 注意不能将该条命令分成两条命令实现,因其两条命令不能保证原子性
SET lock value NX EX 10
// 释放锁 删除
DEL key

2、Redis分布式锁如何合理的控制锁的有效时长?

  • 在获取Redis分布式锁锁成功后执行业务,如果执行业务的时间超时过期 或 在执行业务的途中服务宕机了,此时Redis会自动释放锁,从而可能造成数据的不一致性。

  • 解决方法:
    1)根据业务执行时间预估,但一般都可能无法预期,所以不建议使用
    2)给锁续期,再创建一个新的线程来监控业务执行了多久,如果监控到业务执行的时间比较长,就增加线程持有锁的时长,redisson实现的分布式锁已经实现了该功能。

3、Redisson实现的分布式锁执行流程(实现原理)

  • Redisson实现的分布式锁也是基于setnx 命令实现的,只是对其做了很多增强和优化。

  • 一个线程获取到分布式锁之后,就可以操作redis数据库了,不同的是Redisson会另开一个线程,类似看门狗一样,用来监控这个持有锁的线程,一般是每隔(releaseTime过期时间 / 3)的时间给持有锁的线程做一次续期。假设过期时间为30秒,则每10秒的时间都会给持有锁的线程续期,每次续期时间都为30秒。

  • 当业务完成以后,手动释放锁,同时需要通知这个监控线程锁已被删除了,则不需要再做监听了。

  • 若线程一开始获取分布式锁失败了,并不会直接中断失败,而是运用了while循环来不断的尝试获取锁,设置了一定的循环次数去获取锁,如果超过了这个次数就直接失败了。(重试机制)

  • 所有的加锁、释放锁、设置过期时间等操作都是基于lua脚本完成的。lua脚本的作用就是能保证基于redis实现的多行代码中的原子性。
    在这里插入图片描述

public void redisLock() throws InterruptedException {
	// 获取锁(重入锁),设置执行锁的名称
	RLock lock = redissonClient.getLock("lock1");
	// 尝试获取锁,参数分别是:分布式锁的最大等待时间 、 锁自动释放时间 、 时间单位
	// 第二个参数可不填,如果传入了第二个参数,代表可自己能确定业务的执行时间,则不会再另起一个线程做监控。
	// boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS)
	boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
	// 判断是否获取成功
	if(isLock) {
		try {
			System.out.println(""执行业务);
		} finally {
			// 释放锁
			lock.unlock();
		}
	}
}

4、Redisson实现的分布式锁是否是可重入锁?(可重入锁)

  • redis实现的分布式锁是不可重入的,但Redisson实现的分布式锁是可重入的,与Java中的ReentranLock是一样的,都是判断是否是同一个线程来判断是否可重入,如果是同一个线程则可重入。

  • 当业务比较复杂时,锁粒度比较细时 会用到重入锁的,可以避免多个锁当中产生死锁问题。

  • Redisson中的实现
    1)利用hash结构记录线程id和重入次数
    2)获取锁之后在原有的重入次数中加1,释放锁成功时再直接减1,直到重入次数为0时才可以将该锁的信息删除。
    在这里插入图片描述

5、Redisson实现的分布式锁能否解决主从数据的一致性?(不能)

  • 假如redis的集群架构有一个主节点(主库),用于写数据;两个从节点(从库),用于读数据。当主节点发生了写数据时,则需要将数据同步到两个从节点中。当Java应用获取锁往主库中写入数据后,还没来得及将数据同步到从库中,主库就宕机了(挂了),则此时就出现了主从数据不一致的情况。主库宕机后,redis使用的哨兵模式,则会从两个从库中选择一个作为主库,此时还能去获取锁,则出现了两个线程拥有同一把锁的情况,导致脏数据。

  • RedLock(红锁):不能只在一个redis实例上创建锁,应该是在多个实例上创建(n / 2 + 1)个,避免在一个redis实例上加锁。但使用红锁,会导致业务实现比较复杂、性能差、运维繁琐。

  • Redisson实现的分布式锁不能解决主从数据的一致性,但是可以使用redisson提供的红锁来解决,红锁的性能太低,如果业务中硬性要求要保证数据的强一致性,建议采用zookeeper实现的分布式锁。

  • Redis 主要保证的是高可用性,zookeeper才可以保证数据的强一致性。

6、Redis分布式锁

三、Redis集群

1、Redis集群有哪些方案?

  • Redis中提供的集群方案有三种:
    1)主从复制:单节点redis的并发能力是有上限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离。
    2)哨兵模式:sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,用来实现主从集群自动故障恢复,可保证redis的高并发高可用
    3)分片集群:可解决海量数据存储问题、高并发写问题,

在这里插入图片描述
在这里插入图片描述

2、主从数据同步流程(主从复制原理)

  • 主从同步:单节点redis的并发能力是有上限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离,一般是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中。

  • 主从同步分为两个阶段:全量同步、增量同步。

  • 主从全量同步:指从节点第一次与主节点建立连接时使用的同步,即全量同步。
    1)从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。
    replication id:数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的id,slave则会继承master节点的id。
    offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大,slave完成同步时也会记录当前同步的offset,如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
    2)主节点判断是否第一次请求,主要判断的依据是,主节点和从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset偏移量发送给从节点,让从节点与主节点的信息保持一致。
    3)同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致。
    4)如果在rdb生成执行期间,依然有请求到了主节点,主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件
    5)最后把这个日志文件发送给从节点,这样就能保证主节点和从节点完全一致了,后期再同步数据时,都是依赖这个日志文件

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

3、哨兵模式(保证redis的高并发高可用)

  • 哨兵模式:实现了主从集群的自动故障恢复(监控、自动故障恢复、通知),如果master故障,sentinel(哨兵)会将一个slave提升为master。当故障实例恢复后也会以新的master为主,同时sentinel(哨兵)也充当redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给redis的客户端,即一般项目采用哨兵模式可保证redis的高并发高可用。

  • 哨兵的作用:
    1)集群监控:负责监控 redis master 和 slave 进程是否正常工作。sentinel(哨兵)会不断的检查master和slave是否按预期工作。
    2)自动故障恢复:如果 master node 故障挂掉了,会自动转移到 slave node 上,将一个slave提升为master,当故障实例恢复后也以新的master为主。
    3)消息通知:如果某个 redis 实例有故障,那么sentinel哨兵负责发送消息作为报警通知给管理员,sentinel充当redis客户端的服务发现来源,会将最新信息推送给redis的客户端。

  • 服务状态监控:sentinel基于心跳机制监测服务状态,每隔1s向集群的每个实例发送ping命令
    1)主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
    2)客观下线:如果超过指定数量quorum的sentinel都认为该实例已经下线,则该实例客观下线,quorum值最好超过sentinel实例数量的一半。

  • 哨兵选主规则:
    1)首先判断主与从节点断开时间长短,如果超过指定值就排该从节点
    2)然后判断slave-priority值,越小优先级越高
    3)如果slave-priority一样,则判断slave节点的offset值,越大优先级越高
    4)最后判断slave节点的运行id大小,越小优先级越高。

4、Redis集群脑裂,该怎么解决?

  • 集群脑裂:指使用哨兵模式集群时,由于主节点master和从节点和sentinel(哨兵)处于不同的网络分区,使得sentinel没有心跳感知到主节点master,所以通过选举的方式提升了一个从节点slave为主节点master,这样就存在两个主节点,就像大脑分裂了一样,会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel(哨兵)会将老的主节点降为slave从节点,这时再从新的主节点同步数据,这会导致老的主节点中的大量数据丢失。

  • 解决办法:可以修改redis的配置,可设置最少的slave节点个数,比如设置至少要有一个从节点才能同步数据;可设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,可避免大量数据丢失。

min-replicas-to-write 1  表示最少的slave节点为1个
min-replicas-max-lag 5   表示数据复制和同步的延迟不能超过5

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

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

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

  • redis分片集群引入了哈希槽(slot)的概念,redis集群有16384个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围,key通过CRC16校验后对16384取模来决定哪个槽,通过槽找到对应的节点进行存储。

7、Redis是单线程,为什么还那么快?

  • Redis是纯内存操作,执行速度非常快
  • 采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
  • 使用I/O多路复用模型,非阻塞IO
  • 例如 bgsave和bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞

在这里插入图片描述

8、IO多路复用模型

在这里插入图片描述

  • redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要就是实现了高效的网络请求。

  • Linux系统中一个进程使用的内存情况划分部分:用户空间和内核空间
    用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问。
    内核空间可以执行特权命令(Ring0),调用一切系统资源。

  • 常见的IO模型
    1)阻塞IO(Blocking IO)
    2)非阻塞IO(Nonblocking IO)
    3)IO多路复用(IO Multiplexing)

  • redis网络模型

  • Linux系统为了提高IO效率,会在用户空间和内核空间都加入缓冲区
    写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备
    读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区
    在这里插入图片描述

四、

相关推荐

  1. Redis面试

    2024-07-10 08:34:06       46 阅读
  2. Redis面试5

    2024-07-10 08:34:06       35 阅读
  3. Redis面试4

    2024-07-10 08:34:06       38 阅读
  4. Redis面试8

    2024-07-10 08:34:06       42 阅读
  5. Redis面试7

    2024-07-10 08:34:06       36 阅读
  6. Redis面试9

    2024-07-10 08:34:06       32 阅读
  7. redis 面试(二)

    2024-07-10 08:34:06       30 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 08:34:06       4 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 08:34:06       5 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 08:34:06       4 阅读
  4. Python语言-面向对象

    2024-07-10 08:34:06       4 阅读

热门阅读

  1. jQuery Mobile 实例

    2024-07-10 08:34:06       7 阅读
  2. Electron 简单搭建项目

    2024-07-10 08:34:06       7 阅读
  3. adb 常用的命令总结

    2024-07-10 08:34:06       12 阅读
  4. gcc: options: -specs

    2024-07-10 08:34:06       10 阅读
  5. Python题解Leetcode Hot 100之栈和堆

    2024-07-10 08:34:06       7 阅读
  6. docker容器如何与本地配置文件关联

    2024-07-10 08:34:06       11 阅读
  7. SQL 字段类型-上

    2024-07-10 08:34:06       14 阅读
  8. C++ 入门04:数组与字符串

    2024-07-10 08:34:06       7 阅读
  9. 简谈设计模式之原型模式

    2024-07-10 08:34:06       11 阅读
  10. GPT带我学-设计模式-13策略模式

    2024-07-10 08:34:06       13 阅读