redis常见问题

目录

一、缓存穿透

二、缓存击穿

三、缓存雪崩

四、redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

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

六、数据过期策略

七、数据淘汰策略

八、Redis分布式锁使用场景

九、Redis分布式锁实现原理(setnx,redisson)

十、主从复制,主从同步流程(高并发)

十一、哨兵模式,集群脑裂 (高可用)

十二、分片集群、数据读写规则

十三、 Redis是单线程,为什么速度还这么快?

十四、Redis是什么?

十五、Redis优缺点?

十六、Redis为什么这么快?

十七、既然Redis那么快,为什么不用它做主数据库,只用它做缓存?

十八、Redis应用场景有哪些?

十九、Redis 数据类型有哪些?

二十、Redis的内存用完了会怎样?

二十一、keys命令存在的问题?

二十二、Redis常见性能问题和解决方案?

二十三、说说为什么Redis过期了为什么内存没释放?

一、缓存穿透

1、缓存穿透
  • • 查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库。

  • • 即:大量请求根本不存在的key

2、查询流程

图片

图片

3、出现原因
  • • 业务层误将缓存和库中的数据删除了,也可能是有人恶意攻击,专门访问库中不存在的数据

4、解决方法一(设置value为null)

缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存 

图片

  • • 优点:简单

  • • 缺点:消耗内存,可能导致不一致性

5、解决方法二(布隆过滤器)

图片

**bitmap(位图):**相当于是一个以 (bit) 位为单位的数组,数组中每个单元只能存储二进制数0或1。

布隆过滤器作用:布隆过滤器可以用于检索一个元素是否在一个集合中。

图片

 当访问id存在的时候,会根据布隆过滤器的hash函数获取hash值来计算相应的位置并且修改0为1。当查询的时候就会根据生成的hash函数获取hash值判断对应位置是否为1,如果都为1,则可以进入redis缓存查询,否则不可以。

误判:为空的数据计算的hash值在数组中都为1,但实际上该数据不存在

图片

误判率:数组越小误判率就越大,数组越大误判率就越小,但是同时带来了更多的内存消耗。一般误判率是5%。

  • • 布隆过滤器优点:内存占用较少,没有多余key。

  • • 布隆过滤器优点:实现复杂,存在误判。

6、概括

Redis的使用场景

  • • 根据自己简历上的业务进行回答

  • • 缓存:穿透、击穿、雪崩、双写一致、持久化、数据过期、淘汰策略

  • • 分布式锁:setnx、redisson

什么是缓存穿透,怎么解决

  • • 缓存穿透:查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库。

  • • 解决方案一:缓存空数据

  • • 解决方案二:布隆过滤器

7、 面试官:什么是缓存穿透?怎么解决?
  • • 缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。

  • • 解决方案的话,我们通常都会用布隆过滤器来解决它。

8、 面试官:你能介绍一下布隆过滤器吗?
  • • 布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是redisson实现的布隆过滤器。

  • • 它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。

  • • 当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。

二、缓存击穿

1、概念
  • • 给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。

  • • 即:redis中一个热点key过期(大量用户访问该热点key,但是热点key过期)

图片

2、解决方法一(互斥锁)
  • • 优点:强一致性

  • • 缺点:性能差

  • • 适用于与交易相关的业务

图片

当线程1查询缓存的时候,未命中缓存,然后获取互斥锁成功。此时线程1去查询数据库,将数据写入缓存,最后释放锁。

在线程1查询缓存的时候,未命中缓存,然后线程2去获取互斥锁,获取失败,因为线程1获取到了互斥锁,此时线程2只能休眠一会再重试。当线程1写入缓存成功后并释放锁的同时,线程2重试命中缓存,命中成功直接获取缓存。

3、解决方法二(逻辑过期)
  • • 优点:高可用,性能好

  • • 缺点:数据不一致

  • • 适用于注重用户的体验

图片

4、概括
  • • 缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。

  • • **解决方案一:**互斥锁,强一致,性能差。

  • • **解决方案二:**逻辑过期,高可用,性能优,不能保证数据绝对一致。

5、 面试官:什么是缓存击穿 ? 怎么解决 ?
  • • 缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。

  • • 解决方案有两种方式:

  • • 第一可以使用互斥锁:当缓存失效时,不立即去load db,先使用如 Redis 的 setnx或者Redisson去设置一个互斥锁。当操作成功返回时再进行 load db的操作并回设缓存,否则重试get缓存的方法

  • • 第二种方案可以设置当前key逻辑过期,大概是思路如下:

    • • ①在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间。

    • • ②当查询的时候,从redis取出数据后判断时间是否过期。

    • • ③如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新。

  • • 当然两种方案各有利弊:

    • • 如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么高,锁需要等,也有可能产生死锁的问题

    • • 如果选择key的逻辑删除,则优先考虑的高可用性,性能比较高,但是数据同步这块做不到强一致。

三、缓存雪崩

1、缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

2、查询流程

图片

3、解决方法
  • • 给不同的Key的TTL添加随机值。

  • • 集群模式利用Redis集群提高服务的可用性哨兵模式。(哨兵模式,集群模式)

  • • 给缓存业务添加降级限流策略。(nginx或者Spring Cloud GateWay)

  • • 给业务添加多级缓存。

4、面试官:什么是缓存雪崩?怎么解决 ?
  • • 缓存雪崩意思是设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。

  • • 解决方案主要是可以将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

5、打油诗速记
  • • 《缓存三兄弟》

  • • 穿透无中生有key,布隆过滤null隔离。

  • • 缓存击穿过期key,锁与非期解难题。

  • • 雪崩大量过期key,过期时间要随机。

  • • 面试必考三兄弟,可用限流来保底 。

四、redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

1、双写一致性
  • • 当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。

  • 图片

    读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间。

  • • 写操作:延迟双删。

图片

那么先删除缓存还是先修改数据库呢?(2.3)

那么为什么要删除两次缓存呢?(4)

那么为什么要延时删除呢?(5)

2、先删除缓存再修改数据库

此时缓存和数据库的值分别为10,10

图片

图片

如下是正常的情况,数据库和缓存的值修改正常 

图片

但是一般而言线程是交叉进行的

图片

 此时缓存和数据库的值分别为10,20。造成了数据库和缓存不一致情况

图片

图片

3、先修改数据库再删除缓存

此时缓存和数据库的值分别为10,10

图片

图片

如下是正常的情况,数据库和缓存的值修改正常  

图片

但是一般而言线程是交叉进行的

图片

  此时缓存和数据库的值分别为10,20。造成了数据库和缓存不一致情况

图片

图片

4、为什么要进行两次删除缓存呢?

为了保证缓存和数据库的一致性。因为不管是先修改数据库还是先删缓存都会导致数据库和缓存的值不同。而两次删除缓存会避免这种情况发生。但是一般情况下数据库都是主从分离的,所以可能出现主从数据库数据不一致的情况。

5、为什么要进行延迟双删呢?

一般情况下数据库都是主从分离的,所以可能出现主从数据库数据不一致的情况。为了避免这种情况,就会延迟一会,等待主节点同步到从节点。所以要延时,但是这个延时时间不好控制,在这个过程中依然可能会出现脏数据。所以延时双删只是控制减少了脏数据的出现,但无法避免脏数据的出现。

6、一致性方法一(强一致性)

使用分布式锁来避免数据的不一致性

图片

但是这种普通的读写加锁性能太差,所以可以使用读写锁来处理这种问题。因为缓存一般都是读多写少,所以可以分别使用读锁和写锁来进行加锁

  • • 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作

  • • 排他锁:独占锁writeLock,加锁之后,阻塞其他线程读写操作

图片

 读锁相关代码,注意读锁和写锁的锁名必须一致

图片

 写锁相关代码

图片

但是此方法虽然保持了强一致,但是性能不高。 

7、一致性方法二(异步通知保证数据的最终一致性)

图片

8、概括

redis做为缓存,mysql的数据如何与redis进行同步呢? (双写一致性)

  • • 介绍自己简历上的业务,我们当时是把文章的热点数据存入到了缓存中,虽然是热点数据,但是实时要求性并没有那么高,所以,我们当时采用的是异步的方案同步的数据

  • • 我们当时是把抢券的库存存入到了缓存中,这个需要实时的进行数据同步,为了保证数据的强一致,我们当时采用的是redisson提供的读写锁来保证数据的同步

那你来介绍一下异步的方案(你来介绍一下redisson读写锁的这种方案)

允许延时一致的业务,采用异步通知

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

强一致性的,采用Redisson提供的读写锁o

  • • 共享锁: 读锁readLock,加锁之后,其他线程可以共享读操作

  • • 排他锁:独占锁writeLock也叫,加锁后,阻塞其他线程读写操作

9、面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
  • • 嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,需要让数据库与redis高度保持一致,因为要求时效性比较高,我们当时采用的读写锁保证的强一致性。

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

10、面试官:那这个排他锁是如何保证读写、读读互斥的呢?
  • • 其实排他锁底层使用也是setnx,保证了同时只能有一个线程操作锁住的方法

11、面试官:你听说过延时双删吗?为什么不用它呢?
  • • 延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。

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

在Redis中提供了两种数据持久化的方式:

  • • RDB

  • • AOF

1、RDB

RDB全称Redis Database Backup file (Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。

主动备份

图片

被动备份

图片

 save 300 10:表示300秒内,有10个key被修改可以执行bgsave

2、RDB执行原理

bgsave的子进程执行的时候不会阻塞主进程,不过在开启子进程的时候会阻塞主进程,但是由于时间是纳秒级所以几乎毫无影响。

bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。fork采用的是copy-on-write技术

  • • 当主进程执行读操作时,访问共享内存

  • • 当主进程执行写操作时,则会拷贝一份数据,执行写操作

图片

        在linux系统中进程不能直接操作物理内存,需要页表来进行虚拟内存和物理内存直接的映射。主进程通过页表关联到物理内存真正的地址,这样就能对物理内存进行读和写操作了。

        当开启RDB的时候,主进程就会fork(将主进程的页表数据复制过去,因此子进程就有了和主进程相同的映射关系了,这样子进程就可以根据页表进行对物理内存中数据的读取了)一个子进程。然后子进程就可以去物理内存读取数据并且写入到磁盘中去,生成新的RDB文件,并且将新的RDB文件覆盖旧的RDB文件中去。

        但是子进程在写RDB文件的过程中,主进程可以接受用户的请求来修改内存中的数据。这就导致了读写冲突,甚至可能产生脏数据。为了避免这种问题的发生,fork底层会采用Copy On Write的技术。在fork的过程中,就会将数据标记为Read—Only(只读)模式,任何一个进程只能来读数据不能来写数据。如果主进程进行写数据,那么就会将物理内存中的数据拷贝一份,然后对这个拷贝的数据进读和写,避免了脏写的情况。

3、AOF

AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。 

图片

AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:

图片

AOF的命令记录的频率也可以通过redis.conf文件来配: 

图片

图片

因为 AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF 文件中的内容会越来越多,文件的体积也会越来越大。

  • • 体积过大的 AOF 文件很可能对Redis服务器、甚至整个宿主计算机造成影响

  • • 并且 AOF 文件的体积越大,使用AOF文件来进行数据还原所需的时间就越多

所以 Redis 服务器就会选择一个时间点创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个 AOF 文件所保存的数据库状态相同,但新 AOF 文件不会包含任何浪费空间的冗余命令,所以新 AOF 文件的体积通常会比旧 AOF 文件的体积要小得多

因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。

图片

4、RDB和AOF对比

图片

5、面试官: redis做为缓存,数据的持久化是怎么做的?
  • • 在Redis中提供了两种数据持久化的方式: 1、RDB 2、AOF

6、这两种持久化方式有什么区别呢?
  • • RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。

  • • AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据。

7、这两种方式,哪种恢复的比较快呢?
  • • RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令。

8、RDB 优劣势

优势

  • • 一旦采用该方式,那么你的整个 Redis 数据库将只包含一个文件,这样非常方便进行备份,比如你可能打算每 1 天归档一些数据

  • • 我们可以很容易的将一个一个 RDB 文件移动到其他的存储介质上

  • • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

  • • RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。

劣势

  • • 如果你需要尽量避免在服务器故障时丢失数据,那么 RDB 不适合你。虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。因此你可能会至少 5 分钟才保存一次 RDB 文件。在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。

  • • 每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端;如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒(当然了,AOF 重写也需要进行 fork() ,不过无论 AOF 重写的执行间隔有多长,数据的持久性都不会有任何损失)

9、AOF 优劣势

优势:

  • • 使用 AOF 持久化会让 Redis 的持久性比较好(much more durable):你可以设置不同的 fsync 策略,比如无 fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。

  • • AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek , 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。

  • • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。导出(export) AOF 文件也非常简单:举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

劣势:

  • • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积

  • • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

  • • AOF 在过去曾经发生过这样的 bug :因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。(举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug) 测试套件里为这种情况添加了测试:它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。虽然这种 bug 在 AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。

六、数据过期策略

1、假如redis的key过期之后,会立即删除吗?

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

2、Redis数据删除策略-惰性删除
  • • 惰性删除:设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

图片

  • • 优点:对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查

  • • 缺点:对内存不友好,如果一个key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放

3、Redis数据删除策略-定期删除
  • • **定期删除:**每隔一段时间,我们就对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key)。

  • • 定期清理有两种模式:

    • • SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的hz 选项来调整这个次数。

    • • FAST模式执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms。

  • • **优点:**可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

  • • 缺点: 难以确定删除操作执行的时长和频率。

4、Redis选择的策略
  • • Redis的过期删除策略: 惰性删除 + 定期删除两种策略进行配合使用。

七、数据淘汰策略

  • • 数据的淘汰策略:当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。

1、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算法进行淘汰。

2、 LRU与LFU区别
  • • **LRU(Least Recently Used)**最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

    • • key1是在3s之前访问的,key2是在9s之前访问的,删除的就是key2。

  • • LFU (Least Frequently Used) 最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

    • • key1最近5s访问了4次, ey2最近5s访问了9次,删除的就是key1。

3、淘汰策略使用建议
  • • 优先使用 allkeys-lru 策略。充分利用LRU 算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。

  • • 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用 allkeys-random,随机选择淘汰。

  • • 如果业务中有置顶的需求,可以使用 volatile-lru 策略,同时置顶数据不设置过期时间,这些数据就一直不被删除会淘汰其他设置过期时间的数据。

  • • 如果业务中有短时高频访问的数据,可以使用 allkeys-lfu 或 volatile-lfu 策略。

4、关于数据淘汰策略其他的面试问题

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

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

Redis的内存用完了会发生什么?

  • • 主要看数据淘汰策略是什么? 如果是默认的配置 (noeviction ),会直接报错。

5、概括

数据淘汰策略

  • • Redis提供了8种不同的数据淘汰策略,默认是noeviction不删除任何数据,内存不足直接报错。

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

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

平时开发过程中用的比较多的就是allkeys-lru (结合自己的业务场景) ,因为allkeys-lru保存下来的数据一般都是热点数据。

6、 面试官: Redis的数据淘汰策略有哪些?
  • • 嗯,这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足直接报错。可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU。

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

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

  • • 我们在项目设置的是allkeys-lru,挑选最近最少使用的数据淘汰,把一些经常访问的key留在redis中。

7、面试官:数据库有1000万数据,Redis只能缓存20w数据,如何保证Redis中的数据都是热点数据?
  • • 嗯,我想一下~~可以使用 alkeys-lru(挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据

8、面试官: Redis的内存用完了会发生什么?
  • • 会触发数据淘汰策略,默认为noeviction,不删除任何数据,内部不足直接报错。但是redis有8中淘汰策略,其中有两种主要概念就是LRU和LFU。其余的几种淘汰策略主要围绕这两种进行。

八、Redis分布式锁使用场景

需要结合项目中的业务进行回答,通常情况下,分布式锁使用的场景:

  • • 集群情况下的定时任务、抢单、幂等性场景

1、抢卷场景下的代码流程

图片

这是它的流程: 

图片

2、模拟本地多线程情况下抢卷的执行流程

正常情况:

图片

线程1查询优惠价,查看库存数量是否充足,如果充足就减库存,否则抛出异常。在线程1执行完后线程2进行同样的流程。

多线程情况:

图片

 假设此时库存剩余1

图片

 线程1查询库存看到库存为1,查完后切换到线程2查询到库存也是1。然后再切换到线程1取查看库存是否充足,结果充足,然后库存减1为0。再切换到线程2查看库存为1,结果充足,然后减少库存库存为-1。

解决方法:

添加互斥锁

图片

 流程如下:

图片

3、服务集群部署情况

图片

使用setnx加锁情况:

图片

图片

图片

结果发生8080和8081服务器都能查询到库存为1。这是因为setnx是本地锁只能在当前的JVM进行加锁,但是集群部署是多台服务器,每个服务器都有自己的JVM,就导致了数据异常的情况。

使用redisson分布式锁情况:

图片

线程1获取分布式锁之后,其他服务器包括本服务器的线程不能再获取锁,只有当线程1释放锁之后才可以获取锁。

九、Redis分布式锁实现原理(setnx,redisson)

1、setnx命令

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

图片

2、setnx执行流程

图片

3、setnx命令缺陷

死锁问题

根据上图流程,线程在获取锁成功以后,在执行业务的时候突然服务器宕机了,但是此刻依旧没有释放锁,导致锁无法释放就会导致死锁。

代码未执行完但锁时间到期了

如果添加了锁的过期时间,会出现业务代码未执行完就会释放锁。这是因为锁的时间难以预估出现代码未执行完出现锁提前释放,无法保证代码的原子性。 

4、Redis实现分布式锁如何合理的控制锁的有效时长?
  • • 根据业务执行时间进行评估

  • • 给锁续期

5、Redisson实现分布式锁的使用流程

注:releaseTime过期时间

图片

当线程1尝试获取锁并且获取锁成功的时候,就会新开一个线程Watch dog,Watch dog会监听锁的情况,每隔releaseTime/3时间会做一次续期,每次续期就会重置过期时间。比如说过期时间为30秒,那么每30/3=10秒进行一次续期,将过期时间重置为30秒。如果业务执行完成的话,就会手动释放锁。释放锁以后watch dog就不需要再进行监听了,因为key已经被删除了。

如果线程2尝试获取锁,但是线程1占用着锁,就会获取锁失败,但是线程2不会终止,而是在有限的次数不断循环,直到获取锁位置。

6、redisson执行代码

图片

首先是获取锁,然后是尝试获取锁,根据尝试获取锁的结果判断是否获取锁成功,如果获取锁成功那么就可以执行业务代码。

7、Redisson实现的分布式锁-锁可重入

如下图代码:

图片

  • • 可重入的逻辑:每一个线程都有自己的线程id,加锁首先判断是否为同一个线程id,如果为同一个线程id,那么就可以加锁。

在add1()方法中,线程1创建了一把锁名为heimalock。然后在加锁的代码中执行add2()方法。但是add2()方法中也添加了一把锁,也名为heimalock。在redis中根据hash结构来记录线程id和重入次数。如下图为当前的线程id和可重入情况:

图片

 key为heimalock,field为当前线程id,value为重入锁的次数。因为当前线程执行了add1()和add()2方法所以拥有两把锁。

当add2()方法释放锁时候,重入次数就会减1,如下图

图片

执行add1()方法中的lock.unlock()方法就会释放锁,如下图

图片

因此可重入锁优点是可以避免死锁,提高了程序的可靠性和效率 

8、Redisson实现的分布式锁——主从一致性

一般情况下都会使用redis的主从集群架构,主节点写数据,从节点读数据

图片

此时线程1获取到了锁,对主节点进行写数据,但是突然主节点宕机了导致修改的数据还没有同步到从节点。那么就会开启哨兵模式,将从节点设置为主节点。但会出现一种情况,线程1因为宕机没有释放锁,并且信息没有同步到从节点。所以新节点就会从新设置的主节点获取同一把锁,因此线程2就获取到了和线程1相同的锁。

图片

这时候会出现两个线程获取同一把锁,如果业务还在执行就可能出现脏数据的情况。因为宕机的节点修改后的数据没有同步到先建立的主节点,导致现场2获取的数据不是提交过的数据。

图片

为了解决这种主从一致性的情况,redis提供了另外一种锁(不推荐):

  • • RedLock(红锁):不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n /2 + 1),避免在个redis实上加锁。

图片

9、概括

redis分布式锁,是如何实现的?

  • • 先按照自己简历上的业务进行描述分布式锁使用的场景

  • • 我们当使用的redisson实现的分布式锁,底层是setnx和lua脚本(保证原子性)

Redisson实现分布式锁如何合理的控制锁的有效时长?

  • • 在redisson的分布式锁中,提供了一个WatchDog(看门狗),一个线程获取锁成功以后WatchDog会给持有锁的线程续期 (默认是每隔10秒续期一次)

Redisson的这个锁,可以重入吗?

  • • 可以重入,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用的hash结构来存储线程信息和重入的次数

Redisson锁能解决主从数据一致的问题吗

  • • 不能解决,但是可以使用redisson提供的红锁来解决,但是这样的话,性能就太低了,如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁

10、 面试官: Redis分布式锁如何实现?
  • • 嗯,在redis中提供了一个命令setnx(SET ifnot exists)

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

11、面试官:好的,那你如何控制Redis实现分布式锁有效时长呢?
  • • 嗯,的确,redis的setnx指令不好控制这个问题,我们当时采用的redis的一个框架redisson实现的。

  • • 在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了。

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

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

13、面试官: redisson实现的分布式锁能解决主从一致性的问题吗?
  • • 这个是不能的,比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再获取锁,就会出现两个线程获取同一把锁,导致脏数据的出现。

十、主从复制,主从同步流程(高并发)

1、主从复制

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。

图片

2、主从同步原理——主从同步变量

图片

 注:repl_baklog用来记录除第一次同步以后的数据

流程:

  • • 首先从节点执行replicaof命令用来建立连接, 然后从节点请求数据同步。主节点收到请求以后进行判断是否第一次进行同步,通过判断replid是否一致。因为刚开始每个数据库的replid都不同,只有当第一次同步后主从replid才会一致。因此主节点会根据replid判断是否是第一次同步。

  • • 如果是第一次同步,那么主节点会返回它的节点信息给从数据库,从节点保存版本信息以后,主节点会进行bgsave将redis的数据生成RDB文件,然后将RDB文件发送给从数据库。然后从数据库删除本地文件加载RDB文件。在生成RDB文件也需要时间,因此也会有主节点数据的写入,因此在生成RDB文件期间将写入的记录保存到repl_baklog中。再将repl_baklog中的命令发送给从节点,从节点收到后执行收到命令实现主从同步。

  • • 如果不是第一次同步,那么在第2步从节点会将自己的偏移量和replid发送给主节点。主节点判断replid不是第一次同步,就会将数据版本信息返回给从节点,里面也包括replid和offset。从节点然后保存这些信息。然后同步的时候不再发送RDB文件,而是将repl_baklog文件发送给从节点。至于发送多少呢?由偏移量计算觉得,比如从节点偏移量是50,主节点偏移量是80,那么主节点就会发送50-80这些偏移量的数据。当发送repl_baklog后,会将从节点的偏移量改变为最新即80。

3、主从同步原理——主从增量同步

图片

流程:

  • • 从节点重启或者非第一次同步之后数据发生变化,则进行主从增量同步。从节点发送replid和offset后,主节点通过replid是不是第一次同步,如果不是第一次同步回复continue给从从节点。然后主节点去repl_baklog获取offset数据和从节点的offset进行对比,发送偏移量的数据给从节点。然后从节点获取数据后进行同步。

4、概括

介绍一下redis的主从同步?

  • • 单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现般都是一主多从,主节点负责写数据,从节点负责读数据。

能说一下,主从同步数据的流程?

  • • 全量同步

    • • 从节点请求主节点同步数据 (replication id、 offset)。

    • • 主节点判断是否是第一次请求,是第一次就与从节点同步版本信息 (replication id和offset)

    • • 主节点执行bgsave,生成rdb文件后,发送给从节点去执行。

    • • 在rdb生成执行期间,主节点会以命令的方式记录到缓冲区(一个日志文件)。

    • • 把生成之后的命令日志文件发送给从节点进行同步。

  • • 增量同步

    • • 从节点请求主节点同步数据,主节点判断不是第一次请求,不是第一次就获取从节点的offset值。

    • • 主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步。

5、面试官: Redis集群有哪些方案,知道吗?
  • • 嗯~~,在Redis中提供的集群方案总共有三种:主从复制、哨兵模式、Redis分片集群。

6、面试官:那你来介绍一下主从同步
  • • 嗯,是这样的,单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中。

7、面试官:能说一下,主从同步数据的流程?
  • • 嗯~~,好!主从同步分为了两个阶段,一个是全量同步,一个是增量同步。

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

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

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

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

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

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

十一、哨兵模式,集群脑裂 (高可用)

1、哨兵的作用

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:

  • • 监控: Sentinel会不断检查您的master和slave是否按预期工作。

  • • **自动故障恢复:**如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主。

  • • **通知:**Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端。

图片

2、服务状态监控

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令

  • • 主观下线: 如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。

  • • 客观下线: 若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

图片

 哨兵选主规则

  • • 首先判断主与从节点断开时间长短如超过指定值就排该从节点。因为断开时间越长,丢失的数据越多,所以选择断开时间短的,丢失数据少的节点为主节点。

  • • 然后判断从节点的slave-priority值,越小优先级越高。

  • • 如果slave-prority一样,则判断slave节点的offset值,越大优先级越高。因为offset的值越大,说明该从节点与主节点的数据差越少,所以选offset大的为主节点。

  • • 最后是判断slave节点的运行id大小,越小优先级越高。

3、Redis集群脑裂

这是正常情况的redis主从架构

图片

假如此时因为网络的原因 ,将主节点和从节点分开来了。并且主节点是一个分区,其他从节点是另外一个分区。这个时候从节点分区就会从从节点选出一个主节点,然后就会出现两个master主节点,就好像脑裂了一样。

不过老的master没有挂,只是网络出现的问题,客户端还可以去对老的主节点进行写数据。这就是脑裂的现象。

但是老的主节点写入的数据不能同步到新的主节点。

图片

此时网络恢复了,哨兵会将老的master强制降为salve节点。这个savle就会送master中同步数据,就会把自己的数据清空然后同步master的数据。

图片

 然后客户端连接新的master,变成正常情况。不过丢失的数据依旧丢失了

图片

 解决方法:

  • • redis中有两个配置参数

    • • min-replicas-to-write 1 表示最少的salve节点为1个。必须每个主节点至少有一个从节点,才可以接受客户端的请求,否则失败。

    • • min-replicas-max-lag 5表示数据复制和同步的延迟不能超过5秒。

4、概括

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

  • • 哨兵模式:实现主从集群的自动故障恢复(监控、自动故障恢复、通知)

你们使用redis是单点还是集群,哪种集群

  • • 主从(1主1从)+哨兵就可以了。单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。

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

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

  • • **解决:**我们可以修改redis的配置,可以设置最少的从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求就可以避免大量的数据丢失

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

6、面试官:你们使用redis是单点还是集群,哪种集群
  • • 嗯!,我们当时使用的是主从 (1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务。

7、redis集群脑裂,该怎么解决呢?
  • • 嗯!这个在项目很少见,不过脑裂的问题是这样的,我们现在用的是redis的哨兵模式集群的。

  • • 有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将oldmaster降为salve,这时再从新master同步数据,这会导致old master中的大量数据丢失。

  • • 关于解决的话,我记得在redis的配置中可以设置: 第一可以设置最少的salve节点个数,比如设置至/要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失 。

十二、分片集群、数据读写规则

1、分片集群结构

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

  • • 海量数据存储问题

  • • 高并发写的问题

图片

使用分片集群可以解决上述问题,分片集群特征:

  • • 集群中有多个master,每个master保存不同数据。

  • • 每个master都可以有多个slave节点。

  • • master之间通过ping监测彼此健康状态。

  • • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点。

图片

2、分片集群架构——数据读写

Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个key通过CRC16 校验后对16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash 槽。

图片

对于每个master,会根据master的数量对哈希槽进行均分。然后对于每个写或者读的key经过CRC16的计算其hash值并取模16384就可以获取所在的集群位置。 

3、概括

redis的分片集群有什么作用

  • • 集群中有多个master,每个master保存不同数据

  • • 每个master都可以有多个slave节点

  • • master之间通过ping监测彼此健康状态

  • • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

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

  • • Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽。

  • • 将16384个插槽分配到不同的实例。

  • • 读写数据: 根据key的有效部分计算哈希值,对16384取余(有效部分,如果key前面有大括号,大括号的内容就是有效部分,如果没有,则以kev本身做为有效部分)余数做为插槽,寻找插槽所在的实例。

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

5、面试官: Redis分片集群中数据是怎么存储和读取的?
  • • 嗯~,在redis集群中是这样的,Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围,key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。

  • • 取值的逻辑是一样的

十三、 Redis是单线程,为什么速度还这么快?

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

Redis是纯内存操作,执行速度非常快
采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
使用I/O多路复用模型,非阻塞IO

2、能解释一下I/O多路复用模型?
  • • 用户空间和内核空间

  • • 常见的IO模型

    • • 阻塞IO (BlockingIO)

    • • 非阻塞IO (Nonblocking IO)

    • • IO多路复用 (IO Multiplexing)

  • • Redis网终模型

3、用户空间和内核空间

Linux系统中一个进程使用的内存情况划分两部分: 内核空间、用户空间

  • • 用户空间:只能执行受限的命令 (Ring3),而且不能直接调用系统资源必须通过内核提供的接口来访问

  • • 内核空间:可以执行特权命令 (Ring0),调用一切系统资源

Linux系统为了提高lO效率,会在用户空间和内核空间都加入缓冲区

  • • 写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备

  • • 读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区

图片

但是会出现两个问题

  • • 用户空间和硬件之间隔着内核空间,如果用户空间接受消息,但是内核空间没有从硬件传来的消息就会一直等待,这个等待时间就会造成时间浪费。

  • • 用户空间向硬件发送消息,就会将用户缓冲区的数据拷贝到内核空间。这么一层一层的拷贝会造成巨大的资源浪费。

4、阻塞IO

顾名思义,阻塞IO就是两个阶段都必须阻塞等待:

  • • 阶段一:

    • • 用户进程尝试读取数据(比如网卡数据)

    • • 此时数据尚未到达,内核需要等待数据

    • • 此时用户进程也处于阻塞状态

  • • 阶段二:

    • • 数据到达并拷贝到内核缓冲区,代表已就绪

    • • 将内核数据拷贝到用户缓冲区

    • • 拷贝过程中,用户进程依然阻塞等待

    • • 拷贝完成,用户进程解除阻塞,处理数据

图片

用户想从内核获取数据:

  • • 用户空间的用户应用发送recvfrom函数尝试进行系统调用读取数据,但是内核空间没有数据,那么该线程就会一直进行等待,用户线程处于阻塞状态。

  • • 当内核空间有数据的时候,那么就需要将内核中的缓冲区的数据拷贝到用户区的缓冲区,在这个拷贝的过程中,用户的进程处于阻塞状态。当拷贝完成之后,用户进程拿到数据,这个阻塞状态才会结束。

可以看到,阻塞IO模型中,用户进程在两个阶段都是阻塞状态

5、非阻塞IO

顾名思义,非阻塞IO的recvfrom操作会立即返回结果而不是阻塞用户进程。

  • • 阶段一:

    • • 用户进程尝试读取数据(比如网卡数据)

    • • 此时数据尚未到达,内核需要等待数据

    • • 返回异常给用户进程

    • • 用户进程拿到error后,再次尝试读取

    • • 循环往复,直到数据就绪

  • • 阶段二

    • • 将内核数据拷贝到用户缓冲区

    • • 拷贝过程中,用户进程依然阻塞等待

    • • 拷贝完成,用户进程解除阻塞,处理数据

图片

流程:

  • • 用户进程进行系统调用,但是内核空间没有数据就会返回EWOULDBLOCK异常给用户进程。用户进程收到了异常后就会取消阻塞状态然后再进行尝试系统调用,一致循环下去,直到内核空间有数据。

  • • 当内核空间有数据的时候就会将数据拷贝给用户空间,这个拷贝的过程是阻塞状态。当拷贝完成就会取消阻塞状态。

可以看到,非阻塞IO模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制会导致CPU空转,CPU使用率暴增。

6、I/O多路复用
  • • I/O多路复用:是利用单个线程来同时监听多个Socket,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。

  • • 阶段一:

    • • 用户进程调用select,指定要监听的Socket集合

    • • 内核监听对应的多个socket

    • • 任意一个或多个socket数据就绪则返回readable

    • • 此过程中用户进程阻塞

  • • 阶段二:

    • • 用户进程找到就绪的socket

    • • 依次调用recvfrom读取数据

    • • 内核将数据拷贝到用户空间

    • • 用户进程处理数据

图片

过程:

  • • 用户进程会调用select函数去监听Socket集合,Socket集合中有多个socket。当某个socket可读或者可写的时候就会将该socket加入就绪队列。然后就会返回readable给用户线程。

  • • 用户线程收到readable就会立马调用recvfrom函数将内核空间的数据拷贝到用户空间,然后拷贝完成返回用户线程,用户线程收到返回后进行监听Socket集合中其他的可用socket,然后反复调用recvfrom,过程如上。

7、多路复用I/O通知方式

I/0多路复用是利用单个线程来同时监听多个Socket,并在某个Socket可读、可写时得到通知,从而避免无效的等待充分利用CPU资源。不过监听Socket的方式、通知的方式又有多种实现,常见的有: 

  • • select

  • • poll

  • • epoll

差异:

  • • select和poll只会通知用户进程有Socket就绪,但不确定具体是哪个Socket,需要用户进程逐个遍历Socket来确认。

  • • epoll则会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间 。用户进程就可以直接看到是哪个Socket了。

8、Redis网络模型

Redis通过I/O多路复用来提高网络性能,并且支持各种不同的多路复用实现,并且将这些实现进行封装,提供了统一的高性能事件库。

图片

Redis6.0以后添加的多线程,其中分别为 命令回复处理器和Redis命令转换这里添加了多线程。

流程:

redis通过I/O多路复用来进行对socket的监听,然后将socket分发到对应的处理器,这个过程称为I/O多路复用+事件派发。

当监听到可用的socket的时候就会将通知用户线程,然后用户线程将内核空间的数据拷贝到用户空间。拷贝后用户线程接受其数据,将数据转化为redis命令。选择执行其命令把结果写入缓冲区,然后将缓冲区的信息发送给命令回复处理器。命令回去处理器将结果返回给客户端。

9、概括

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

I/O多路复用

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

Redis网络模型

  • • 就是使用I/O多路复用结合事件的处理器来应对多个Socket请求

  • • 命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程来处理回复事件命令回复处理器。

  • • 命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。

10、 面试官: Redis是单线程的,但是为什么还那么快?
  • • 完全基于内存的,C语言编写。

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

  • • 使用多路I/0复用模型,非阻塞IO。

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

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

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

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

十四、Redis是什么?

Redis(Remote Dictionary Server)是一个使用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。

十五、Redis优缺点?

1、优点
  • • 基于内存操作,内存读写速度快。

  • • 支持多种数据类型,包括String、Hash、List、Set、ZSet等。

  • • 支持持久化。Redis支持RDB和AOF两种持久化机制,持久化功能可以有效地避免数据丢失问题

  • • 支持事务。Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。

  • • 支持持久化。Redis支持RDB和AOF两种持久化机制,持久化功能可以有效地避免数据丢失问题

  • • Redis命令的处理是单线程的。Redis6.0引入了多线程,需要注意的是,多线程用于处理网络数据的读写和协议解析,Redis命令执行还是单线程的。

2、缺点
  • • 对结构化查询的支持比较差。

  • • 数据库容量受到物理内存的限制,不适合用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的操作。

  • • Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

十六、Redis为什么这么快?

  • • 基于内存:Redis是使用内存存储,没有磁盘IO上的开销。数据存在内存中,读写速度快。

  • • IO多路复用模型:Redis 采用 IO 多路复用技术。Redis 使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O上浪费过多的时间。

  • • 高效的数据结构:Redis 每种数据类型底层都做了优化,目的就是为了追求更快的速度。

十七、既然Redis那么快,为什么不用它做主数据库,只用它做缓存?

  • • 事务处理:Redis只支持简单的事务处理,对于复杂的事务无能为力,比如跨多个键的事务处理。

  • • 数据持久化:Redis是内存数据库,数据存储在内存中,如果服务器崩溃或断电,数据可能丢失。虽然Redis提供了数据持久化机制,但有一些限制。

  • • 数据处理:Redis只支持一些简单的数据结构,比如字符串、列表、哈希表等。如果需要处理复杂的数据结构,比如关系型数据库中的表,那么Redis可能不是一个好的选择。

  • • 数据安全:Redis没有提供像主数据库那样的安全机制,比如用户认证、访问控制等等。

虽然Redis非常快,但它还有一些限制,不能完全替代主数据库。所以,使用Redis作为缓存是一种很好的方式,可以提高应用程序的性能,并减少数据库的负载

十八、Redis应用场景有哪些?

  • • 缓存热点数据:缓解数据库的压力。

  • • 利用 Redis 原子性的自增操作,可以实现计数器的功能,比如统计用户点赞数、用户访问数等。

  • • 分布式锁:在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

  • • 简单的消息队列:可以使用Redis自身的发布/订阅模式或者List来实现简单的消息队列,实现异步操作。

  • • 限速器:可用于限制某个用户访问某个接口的频率,比如秒杀场景用于防止用户快速点击带来不必要的压力。

  • • 好友关系:利用集合的一些命令,比如交集、并集、差集等,实现共同好友、共同爱好之类的功能。

十九、Redis 数据类型有哪些?

1、基本数据类型
  • • String:最常用的一种数据类型,String类型的值可以是字符串、数字或者二进制,但值最大不能超过512MB。

  • • Hash:Hash 是一个键值对集合。

  • • Set:无序去重的集合。Set 提供了交集、并集等方法,对于实现共同好友、共同关注等功能特别方便。

  • • List:有序可重复的集合,底层是依赖双向链表实现的。

  • • ZSet:有序Set。内部维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。

2、特殊的数据类型
  • • Bitmap:位图,可以认为是一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在 Bitmap 中叫做偏移量。Bitmap的长度与集合中元素个数无关,而是与基数的上限有关。

  • • Hyperloglog。HyperLogLog 是用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。典型的使用场景是统计独立访客。

  • • Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如定位、附近的人等。

二十、Redis的内存用完了会怎样?

如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回)。

也可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。

二十一、keys命令存在的问题?

redis的单线程的。keys指令会导致线程阻塞一段时间,直到执行完毕,服务才能恢复。scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是O(1),但是要真正实现keys的功能,需要执行多次scan。

scan的缺点:在scan的过程中如果有键的变化(增加、删除、修改),遍历过程可能会有以下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键。

二十二、Redis常见性能问题和解决方案?

  • • Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。

  • • 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

  • • 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。

  • • 尽量避免在压力较大的主库上增加从库

  • • Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。

  • • 为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。

二十三、说说为什么Redis过期了为什么内存没释放?

1、可能是覆盖之前的key,导致key过期时间发生了改变

当一个key在Redis中已经存在了,但是由于一些误操作使得key过期时间发生了改变,从而导致这个key在应该过期的时间内并没有过期,从而造成内存的占用。

2、Redis过期key的处理策略导致内存没释放
  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值