redis面试题

序号内容
1基础面试题
2JVM面试题
3多线程面试题
4MySql面试题
5集合容器面试题
6设计模式面试题
7分布式面试题
8Spring面试题
9SpringBoot面试题
10SpringCloud面试题
11Redis面试题
12RabbitMQ面试题
13ES面试题
14Nginx、Cancal
15Mybatis面试题
16消息队列面试题
17网络面试题
18Linux、Kubenetes面试题
19Netty面试题

Redis基础

对redis的理解

Redis是一个基于Key-Value存储结构的Nosql开源内存数据库,它提供了5种常见的数据类型,像String、Map、ZSet、Set、List ,针对于不同的结构呢,可以解决不同场景的问题。其次由于Redis是一个基于内存的一个存储并且在数据结构上做了大量的一些优化,所以IO性能会比较好,在实际开发里面呢,我们会把它用在应用和数据库之间的一个分布式缓存中间件,并且它又是一个非关系数据库的存储,不存在表之间关联查询的一些问题,所以它可以很好的去提升应用程序的数据IO效率,最后作为企业级开发来说,它又提供了主从复制+哨兵,以及集群的方式去实现高可用(,在Redis集群里面呢,通过hash槽的方式去实现数据的分片,进一步提升了整体的一个性能和可扩展性)

redis是单线程还是多线程

对于Redis整体而言肯定不是只有一个线程,redis的单线程主要是指 Redis 在网络 IO和键值对读写是采用一个线程来完成的,这也是 Redis 对外提供键值存储服务的核心流程。但对于 Redis 的其他功能来说,比如持久化、异步删除、集群数据同步等,其实都是由额外的线程执行的。

redis为什么这么快

1、redis是纯内存的KV操作。在计算机中,CPU的速度要大于内存,内存速度远大于硬盘速度。redis的操作都是基于内存的,绝大部分都是纯内存操作,非常迅速。

2、redis执行命令都是是单线程操作。redis只有 网络请求模块和数据操作模块是单线程的,其他模块还是多线程的。 省去多线程时CPU上下文切换的时间,也不用考虑各种锁的问题,不存在加锁释放锁的操作,也就没有死锁的问题导致性能消耗。

3、IO多路复用。redis采用网络IO多路复用机制,在网络IO操作中能并发的处理大量的请求。IO多路复用是一种同步IO模型,单线程/进程可以同时处理多个IO请求。

4、计算向数据移动。(list数据在redis内存中,客户端想要让list中第二个元素加一,客户端把计算指令告诉redis缓存,redis缓存在内存中直接计算,这个叫计算向数据移动。

memcached是数据向计算移动。(memcached中有list数据,客户端想要让list中第二个元素加一,客户端把list数据从memcached拿过来,客户端计算完后再传回给memcached,这个叫数据想计算移动)

redis和memcached的区别

1、redis是单线程的多路 IO 复用模型。memcached是多线程,非阻塞IO模式

2、redis支持RDB和AOF持久化。memcached不支持持久化

3、redis支持原生 cluster 模式,可以实现主从复制,读写分离。memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据

4、在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。memcached的数据则会一直在内存中。

5、redis适合复杂数据结构,有持久化,高可用需求,value存储内容较大。memcached是纯key-value,适合数据量非常大,并发量非常大的业务。

Redis是线程安全的吗

redis是单线程执行的,内部能够保证线程安全,但是外部使用的时候,业务逻辑需要我们自行保障。

redis server 本身是一个线程安全的K-V数据库,在redis server 上执行的指令,不需要任何的同步机制,不会存在线程安全的问题。

redis 6.0以后增加了多线程模型,但也只是用来处理IO事件,对于指令的执行过程,任然是主线程来执行。

Redis为什么没有采用多线程来执行指令

1、如果采用多线程意味着对redis的所有指令操作,都必须要考虑线程安全的问题。也就是说需要加锁来解决。

2、虽然redis server中指令执行是原子的,但是如果有多个redis客户端同时执行多个指令的时候,就无法保证原子性。如果两个redis client 同时获取redis server上的key,同时进行修改。因为多线程环境下的原子性无法被保障,以及多线程情况下的共享资源访问的竞争问题,使得数据的安全性无法被保障。

Redis数据类型

string

基本命令:set、get、setrange、getrange、append、strlen、getset。

key中有一个属性 type,描述的是value的类型。string这个value的类型(type)包含三个:字符串(string),数值(int),位图(bitmap)。

底层数据结构:SDS,redis自己实现的可修改的字符串结构,它支持在不重新分配内存的情况下对字符串进行扩展和修改。

位图(bitmap)

  • 实际上是一个以位为单位的数组,虽然Redis没有特定的Bitmap类型,但可以通过String类型实现位图功能。
  • 用于布尔值存储、用户在线状态、特征标记等

list

list是可重复的。有序的,是插入的顺序

使用redis的list的命令能够模拟成队列(LPUSH和RPOP)和栈(RPUSH 和 LPOP) 。

使用lindex、lset可以替代数组。

使用blpop或者brpop能模拟简单的阻塞队列。blpop:移除和获取list中的第一个元素。brpop:移除和获取list中的最后一个元素。

正负索引:正索引表示从列表的左端开始计数,从 0 开始递增;负索引表示从列表的右端开始计数,从 -1 开始递减。-1 表示列表中的最后一个元素,-2 表示倒数第二个元素。

底层数据结构:双向链表。双向链表节点的结构中包含值和指向前一个节点和下一个节点的指针。

Redis实现异步队列

使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

hash

hash的value值为 key value 集合。类似map。

命令是H开头的。HSET(设置hash上一个字段或者多个字段的值)、 HGET(获取key对应的集合的value)、 HMGET (获取指定key对应的对个集合的值)、HINCRBY(指定字段增加所提供的整数)

可以做数值计算。场景:点赞、收藏、详情页。可以用来存储对象(例如存储用户,用户有对应的姓名,性别,年龄)。

底层数据结构:哈希表。每个键值使用一个哈希节点来表示,通过拉链法解决hash冲突,并在节点超过一定阈值的时候自动扩容

set

集合命令是S开头的。set是去重的,无序的。不维护排序,不维护插入和弹出的顺序。

SADD:集合中新增对象

SREM:移除集合中的元素

SMEMBERS:获取集合中的元素。SMEMBERS key

执行常见的集合运算,例如:交集(SINSERT)、并集(SUNION)、差集(SDIFF)。

可以实现随机事件。可以用于存储唯一值的场景。数据去重,标签管理

底层数据结构:基于hash表和整数数组实现。当集合元素较少时,redis使用整数数组存储元素,以节省空间。元素多的时候,使用hash表存储元素,以保持较高 的性能

sorted set

​ 有序集合。命令是使用Z开头的。应用:排行榜、

​ sorted set 底层是通过跳跃表和hash表实现的。跳跃表用于存储有序集合的元素和分数,用于保持元素的有序性。hash表用于存储元素和分数之间的关系,支持元素的查找。

​ 跳跃表是一种有序的数据结构。有序集合中每个元素都关联着一个分数(score),这个分数决定了成员在有序集合中的排列顺序。跳跃表的插入和删除额时间复杂度为O(log N)。使用zrange命令时需要注意,如果数据量大的时候返回会慢。

Redis实现延时队列

​ 使用sortedset,使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。

Redis持久化

RDB

​ RDB是一种进行存储快照的持久化方式。在指定的时间间隔内,Redis将内存中的数据集以二进制形式存储到硬盘上的一个文件中。这个文件通常被称为“dump.rdb”。

实现方式

​ 命令方式:1、save方式。会阻塞进程。关机维护的时候使用save命令。2、bgsave方式。调用fork创建子进程。避免了对主线程的阻塞,这也是 Redis RDB 的默认配置

bgsave做快照允许数据修改
  • 如果主线程执行读操作,则主线程和 bgsave 子进程互相不影响;
  • 如果主线程执行写操作,则被修改的数据会复制一份副本,然后 bgsave子进程会把该副本数据写入 RDB 文件,在这个过程中,主线程仍然可以直接修改原来的数据

优缺

1、定期保存,RDB在保存快照时对性能的影响相对较小。

2、 RDB文件是一个压缩的二进制文件,非常紧凑,恢复快

3、在两个快照之间,如果发生故障,那么最近写入的数据可能会丢失。

4、对于非常大的数据集,保存快照可能会导致明显的延迟。

AOF

​ AOF持久化是通过记录下所有的写操作命令(不包括读取命令),并追加到文件末尾来实现的。通过重新执行这些命令,Redis可以重建原始的数据集。在Redis启动时,它会读取并重新执行这些命令来恢复数据。

AOF写后日志的风险

  • 数据可能会丢失:如果 Redis 刚执行完命令,此时发生故障宕机,会导致这条命令存在丢失的风险。
  • 可能阻塞其他操作:AOF 日志其实也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。

AOF重写机制

​ AOF是redis里面一种数据持久化方式,它采用了追加指令的方式。近乎实时的去实现数据指令的持久化,因为AOF会将每个数据更改的操作指令都追加到aof文件中。所以容易导致AOF文件过大,造成IO性能的问题。

​ Redis为了解决这个问题,设计了AOF重写机制,就是把AOF中指令进行压缩,只保留最新的数据指令。aof文件里面存储了某个key的多次变更记录,但实际上,在做最终数据恢复的时候,只需要执行最新的指令就行了。

步骤

1、发生重写时,redis会fork一个子进程,这个子进程继承了父进程的状态,子进程只需要把内存中当前的数据写到aof中

2、此时,父进程仍在接收请求,父进程把这些新来的请求放到了aof_rewrite_buffer(缓冲区)中。

3、子进程写完aof后,通过信号通知父进程。父进程再把aof_rewrite_buffer(缓冲区)中新增的内容也写到aof文件中。

4、使用新aof替换旧aof。

混合持久化

​ 混合持久化模式下,Redis会定期创建RDB快照,并在需要时只记录最新的写入命令到AOF文件中。这样做的好处是,当需要重新加载数据时,Redis可以先加载RDB文件以获得大部分数据,然后执行AOF文件中的剩余命令,以此快速完成数据恢复。

开启:在配置文件中配置 aof-use-preamble yes

Redis和mysql一致性

异步缓存更新

​ 通过先删除redis数据,再更新mysql数据。更新完mysql后,通过监听mysql的binlog来更新redis中的数据。监听到binlog数据后,先根据可以删除一次redis数据,再更新redis,更新的时候如果服务异常,加try catch,在catch中重试,如果catch中不行,就在查询的时候更新到redis中。如果服务器挂掉后,导致binlog监听的数据丢了,重启的时候重新更新所有redis数据。

Redis集群

Redis集群(cluster)模式,分治

​ 集群(cluster)模式下,集群中没有中心节点,所有的节点既是数据存储节点,也是控制节点。redis通过使用hash 槽(hash slot)算法,将数据分布在不同的节点。理论上节点可以无限扩容。槽位共16384个,分布在不同的节点上。

redis cluster模式可以是主从的,也可以是单节点的

槽位分配算法

​ 集群在初始化时,会预定16384个槽位,每个槽位都有变化0~16383。在集群创建或者新节点加入的时候,通过自动分配机制或者手动管理的方法将这16384个槽位均匀的分配到各个节点(可以是均匀的,也可以是根据节点的性能动态的分配)。分配完后,这些信息会被保存在集群元数据中。

槽位为什么是16384个

Redis key的路由计算公式:slot = CRC16(key) % 16384
CRC16 算法,产生的hash值有 16 bit 位,可以产生 65536(2^16)个值 ,也就是说值分布在 0 ~ 65535 之间
Redis Cluster 模式下,节点之间会互相发送心跳,正常的心跳数据包携带节点的完整配置。槽位为65536时,占用8K的空间。
如果使用18384,占用2K的空,足够1000个节点使用了。使用16384比65536少占用4倍空间。

如何存/取值

​ 当客户端需要执行某个键(key)的操作时,它会首先根据键名计算对应的哈希槽。这通常是通过哈希函数(如CRC16)对键名进行哈希计算,然后取模16384得到一个槽位编号。然后查询本地的集群元数据,找到负责处理该槽位的节点。找到节点后执行存取操作。

扩容、删除节点时数据迁移

使用Redis提供的槽位迁移工具(如redis-trib.rb或Redis Cluster的管理命令),将指定范围的槽位从现有节点迁移到新节点。

在删除节点之前,必须先将该节点上的所有槽位迁移到其他节点。这同样可以通过Redis提供的槽位迁移工具来完成。迁移过程中,数据会从要删除的节点移动到其他节点,同时更新集群的元数据。

添加主节点:

127.0.0.1:7007 是新增节点,127.0.0.1:7001 是已经新增集群节点(这里可以随便写一个已知的集群节点 ip : port

redis-trib.rb add-node 127.0.0.1:7007 127.0.0.1:7001。

添加从节点:

添加7008成为7006的从节点

redis-trib.rb add-node 127.0.0.1:7008 127.0.0.1:7006

删除节点:

redis-trib.rb reshard 127.0.0.1:7007

Redis哨兵模式

sentinel是一个运行在特殊模式下的redis服务器。哨兵的监控也是集群部署,一般是过半判定是redis故障的时候。哨兵的监控集群一般是基数个节点。

作用:

  • 监控:周期性的心跳检测,检测主从服务器是否正常运行。

  • 提醒:通过api通知redis的一些问题。

  • 自动迁移:如果主节点异常,sentinel把一个从节点提升为主节点,并配置其他从节点使用新的主节点,使用redis的程序也会被通知使用新的主节点。

哨兵是如何知道主有几个从的?

哨兵每10秒向被连接的主服务器发送INFO命令,通过INFO命令的返回值获取主服务器的信息以及从服务器信息。

哨兵是如何知道其他哨兵,哨兵间的通信

通过redis的发布订阅,哨兵向主节点的 _sentinel _ :hello频道发送消息。所有订阅了这个频道的哨兵都会收到这条消息,其他的哨兵收到消息后,提取其中的IP,端口,运行ID等信息,并进行检查,如果id不同,说明是其他哨兵发送的,根据发送的信息中参数,对相应的主服务器进行更新。

如何选主

哨兵在找不到主的时候,投票选主。

通过Raft算法,当得票数为:哨兵数量/2 + 1 时 ,将成功主节点。

选主后数据是如何同步的

选完主后,新主节点告诉从节点自己的地址,从节点连接到新主节点,如果从节点是首次连接,或者主节点的数据已经过期,那么执行全量同步。

如果从节点已经执行过全量同步,并且在同步过程中没有断开连接,那么在之后的同步过程中,主节点会将最近的写操作发送给从节点,从而实现增量同步。

一旦从节点完成了全量同步和增量同步,它就会持续地与主节点保持连接,并接收主节点的写操作,以保持数据的一致性。

Redis主从复制模式

客户端可以访问主,也可以访问从。主客户端一般用作写操作,从客户端一般用于读操作。

redis使用的是异步复制,其特点是低延迟和高性能。但是一致性没那么强,弱一致性。可能会丢失数据。

主从复制原理

​ 当启动一个 slave 的时候,它会发送一个 PSYNC 命令给 master 。如果这是 slave 初次连接到 master ,那么会触发一次 全量复制。此时 master 会启动一

个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给

slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave 如果跟

master 有网络故障,断开了连接,会自动重连,连接之后 master 仅会复制给 slave 部分缺少的数据。

为什么使用集群

主要是为了扩展写能力和存储能力。哨兵模式归根节点还是主从模式,在主从模式下我们可以通过增加salve节点来扩展读并发能力,但是没办法扩展写能力和存储能力,存储能力只能是master节点能够承载的上限

Redis过期策略和算法

当maxmemory限制达到的时候,redis通过配置maxmemory-policy(策略类型)(redis的配置文件redis.conf中配置)指令来解决。maxmemory 配置最大内存。maxmemory设置为0表示没有内存限制。

策略

定时过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

算法

LRU 算法:最近最少使用算法; LFU 算法:最不常用算法,根据使用频率计算,4.0 版本新增

noeviction:当内存达到配置的最大限制时,不保存新值,返回错误。

allkeys-lru:保留最新使用的key,删除最近最少使用的key

allkeys-lfu:保留常用key,删除不常用的key

volatile-lru:从设置了过期时间的 key 中使用 LRU 算法进行淘汰

volatile-lfu:从设置了过期时间的 key 中使用 LFU 算法进行淘汰

allkeys-random:从所有 key 中随机淘汰数据

volatile-random: 从设置了过期时间的 key 中随机淘汰数据

volatile-ttl:设置了过期时间的key中,淘汰过期时间剩余最短的

Redis事务

multi:开启事务

exec:执行所有的命令,如果有问题,则会撤销命令。

由于redis的执行命令的是单线程执行的,所以事务是要等到exec命令到达后才能执行。无论前面的命令多么靠前,只要exec没有到达,当前事务都不会执行。

watch:redis的乐观锁。使用watch命令监控事务,如果key对应的value发生了改变,则监控的这个redis事务在exec提交事务的时候会失败。

redis事务不支持回滚。

Redis事务支持隔离性吗

Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。

Redis事务保证原子性吗,支持回滚吗

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis缓存常见问题

击穿

redis做缓存的时候。redis的key在某个时间点过期,或者通过淘汰策略,淘汰了一些key。刚被清掉的key,突然有大量的请求来查询这个key,这时redis中已经没有这个key了,大量的请求就去访问数据库。

处理方法

由于redis是单线程的,大量请求发过来的时候,通过使用setnx(redis中没有的话,才能设置key成功)命令设置这个key,相当于加了一把锁,后面的请求再去执行setnx的时候,就会发现redis中已经存在了这个key。

代码流程:

1、所有的请求都去redis中获取key。

2、如果key为空,则都执行setnx。

  • 如果setnx锁挂掉?可以对锁加过期时间
  • 锁超时怎么处理?另起一个线程去数据库取数据。

3、setnx成功的请求,去查询数据库。失败的请求sleep一会儿。

4、等setnx成功的那个线程获取数据成功后,唤醒sleep的线程。

穿透

在redis做缓存的情况下。查询系统中根本不存在的数据,redis中没有,数据库中也没有。请求无法从redis获取数据,会直接请求到数据库。数据库做一些不存在的查询。

使用布隆过滤器解决问题。

布隆过滤器只能增加,不能删除。使用布谷鸟过滤器,能删除。

布隆过滤器

布隆过滤器是属于redis的模块,需要单独安装。启动redis服务的时候外挂上去。

布隆过滤器是由位数组和多个哈希函数构成。

当一个元素添加到布隆过滤器时,它会通过多个哈希函数将该元素映射到位数组中,并将这些位置的值设置为1。当查询时,同样通过多种hash算法,将要查询的值映射,如果所有的映射的值的位置都为1,则元素存在。如果任何一个位置的值为0,则不一定存在。

缺点:

由于通过哈希算法,可能产生哈希冲突,可能会导致产生误判。

不支持删除操作,

需要事先确定容量和hash函数数量:

不适合动态数据集:数据频繁变化,布隆过滤器需要频繁调整容量。会增加额外的成本。

布谷鸟过滤器

类似布隆过滤器。一种用于快速判断一个元素是否存在于一个集合中的数据结构。主要解决了布隆过滤器中的动态扩容和删除的问题。

缺点:比较复杂,消耗内存

couting bloom过滤器

雪崩

大量的key同时失效。间接造成大量的访问同时访问数据库。

处理方法

1、设置不同的过期时间,让缓存均匀的过期。

2、使用主备两次缓存。主缓存按照经验设置,失效后从数据库加载最新值。次缓存有效期长,主缓存更新的时候同步更新次缓存。

缓存预热

缓存预热就是在系统上线前,如果redis 是空的,避免用户在请求的时候先查数据库,再将数据缓存到redis中,

1、使用脚本固定触发预热的过程。

2、数据量不大的情况下项目启动时加载缓存。

3、数据量大的时候,项目启动的时候加载热点key。

Redis消息订阅

发送者:publish,订阅者:subscribe。

subscribe命令可以客户端订阅任意数量的频道,每当有新消息发送到被订阅的频道时,信息就会发送给所订阅指定频道的客户端。

为什么使用redis的发布订阅

1、redis的发布订阅是实时通信的和事件驱动的。

2、功能相对简单,适用于较为简单的消息发布和接收。

3、可靠性要求要求那么高的情况下。

项目中使用的是基于channel的发布/订阅。channel的发布订阅是通过字典实现的,redis中有一个pubsub_channels字典类型,这个字段的key存了订阅的频道,字典的value存的是订阅消息的客户端,以链表的形式存储。在发布消息时,通过先定位到字典的key,然后将信息发送给字典中所有的客户端。

Redis管道Pipline

redis是基于客户端-服务端模型的服务器。客户端发送请求到服务器,并以阻塞的方式从socket读取数据,获取服务端的响应。服务端处理请求命令,并发送响应回客户端。

Pipelining:一次发送多个命令给服务器而不用等待响应,最后统一读回多个返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值