Redis面试题

redis常用数据结构

redis常用的数据类型有String、list、hash、set、Zset

1. String是普通的字符串,存储一些简单的数据,例如用户登陆的时候后端保存短信验证码

2. list相当于java中的集合,有序,假如存储12345,查看时则是54321,场景例如网页中的轮播图、网页中特价商品

3. hash 相当于Java中的hashmap,可以定义一个键之后,后面设置多个值,例如用于存储用户信息,商品信息等等

4. set 是String类型的无序集合,但是其中的元素是唯一的,中间不能出现重复元素,当输入时有重复元素,存储的时候能够自动去重,需要去重的少量信息,比如身份证信息、手机号码作为黑白名单共同好友查询

5. Zset是一个有序集合,但存储的时候成员名字不能重复,就跟一个班级成绩一样,名字不能重复,但是分数可以重复,查询的时候会按照成绩高低进行排序,例如视频网站的点击量排名、收藏量排名等等

Zset底层维护了跳表结构!--》JUC并发包中有类似结构

跳表结构是什么样的结构

跳表结构是一种基于链表的数据结构,具有快速查找、插入、删除等特点,用于解决有序链表查找效率低下的问题。跳表通过添加多级索引来实现跨越式查找,使得查找复杂度从线性时间复杂度O(n)降低至对数时间复杂度O(log n)。跳表起源于1989年William Pugh发表的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》,在实践中应用广泛,常用于高并发的数据结构设计中。

跳表结构是由多个层级的链表结构组成的。在跳表中,元素都是有序的,且每一级链表中的元素个数逐渐递减。

跳表中,每个元素包含两个重要信息:当前元素值及指向同一层级或下一层级的下一个元素的指针。每一级链表中的第一个元素称为头节点,每一级链表中的最后一个元素称为尾节点。

为了实现快速查找,跳表中每个节点还记录着相应节点在下一层级链表中的位置,这就是跳表中的索引。跳表为原链表,从A在第0层开始查找,根据索引,跳到第1层的节点B,然后根据下一级索引,跳到第2层的节点C,以此类推,直到跳到最高层的节点。

在跳表中,建立索引的策略是按一定的规则(比如一定的概率),以一定的频率在原始链表上建立新的索引,从而实现跨越式查找。这样,当搜索某个元素时,可以根据索引快速跳过一些不需要搜索的节点,从而提高搜索效率。

插入和删除元素的操作本质上也是查找操作。当需要插入或删除元素时,先按照查找的方式在跳表中找到相应的位置,然后通过修改指针实现插入和删除。

跳表的时间复杂度为O(log n),空间复杂度为O(n)。在实际应用中,跳表在某些特定场合下可以替代平衡树,具有访问效率高、小优点。

关于跳表的一个小例子

假设我们要在一个有序链表中查找某个元素,如果按照一般的遍历方式,时间复杂度为O(n);但是如果在这个有序链表上再建立一层索引,每两个元素取一个作为索引节点,那么每次查找时,我们可以先在索引链表中查找,再在对应区间的链表中查找,这样每一次查找的区间缩小了一半,所以平均查找时间复杂度就可以降为O(log n)。

这个例子中,我们就使用了跳表的思想来优化查找操作的效率。

redis的持久化机制有哪些?

持久化的方案有两种,分别是RDB和AOF,根据不同的场景,可以选择只使用其中一个或者一起使用

RDB是通过保存快照的方式来对数据进行持久化处理,所保存的数据的本身,默认使用的是bgsave来保存快照数据。

RDB的优缺点:

优点:

● 基于二进制文件完成数据备份,占用空间少,便于文件传输。

● 能够自定义规则,根据Redis繁忙状态进行数据备份

缺点:

● 无法保证数据完整性,会丢失最后一次快照后的所有数据

● bgsave执行每次执行都会阻塞Redis服务进程创建子线程,频繁执行影响系统吞吐量

RDB方式会出现数据丢失的问题,对于这个问题,可以通过Redis中另一个持久化方式解决:AOF

当AOF持久化开启之后,Redis会将客户端发送的所有更改数据的命令保存至磁盘中,通过读取AOF文件,按照顺序获取所记录的数据命令,来达到恢复数据的效果。

AOF的触发机制有三种,分别为:

● always:每操作一条命令,都往磁盘当中同步一次,该方式效率最高,不会丢失数据,安全性最高,但是十分浪费资源。

● everysec:每次执行写入命令,都将aof_buf缓冲区文件全部写入到AOF文件中,并且每隔一秒同步到磁盘当中一次,该方式兼备了效率和安全,即使出现宕机情况,也就丢失一秒钟的数据。

● no:将同步操作交给操作系统来做,该方式最快,但是最不安全。

RDB是默认开启的,AOF需要手动开启

RDB性能优于AOF

AOF的安全性优于RDB

AOF的优先级高于RDB

RDB存储某个时刻的数据快照,AOF存储写命令

RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF默认使用everysec,每一秒保存一次。

redis的高可用方案(主从、哨兵、集群)

● (主从)redis的持久化也没有办法完全保证数据的不丢失,我们要想避免这种情况,我们需要搭建多个服务器,通过主写从读方法,主节点来写数据,从节点来读数据,即使一个服务器突然间宕机了,也不会造成全部的数据丢失。

● (哨兵)主从更好的提高了数据的安全性,但是因为某个主节点服务器宕机了,我们需要人工干预选取新的节点,而且还十分麻烦,但是呢,你是真的知道主节点宕机了吗?我们可以引用哨兵模式来判断,它有效的解决了主从存在的问题。

哨兵也是一台单独的redis服务器,它只服务于主从服务库,然而哨兵服务器使用一台的话,也不能完全解决问题,因为单个哨兵服务器也会出现故障,所以我们一般都设置三个哨兵服务器,一主二从三哨兵,主要有三个功能:

监听:主要是监听主从服务器的状态

通知:当被监控的某个Redis服务器出现问题时,会向管理员或者其他应用程序发送通知

自动故障迁移:解决以上问题的核心操作,当一个主服务器不能工作时,就会开始一次自动故障迁移操作,让与失效服务器绑定的服务器升级成为主节点,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,会用新的服务器代替失效服务器。

● (集群)经典主从集群,解决了数据丢失的风险,增加数据的安全性,主从集群可以随意切换,因为上面的都是基于一个主节点实现的,一个主节点的写能力和存储数据是有限的,随着时间的推移,数据量会变得十分庞大,过程当中一旦主节点宕机,恢复数据会变得十分缓慢,如何解决这个问题,在Redis3.0之后就有了Redis CLuster分片集群,就是将现在的一个主节点,换成多个主节点,然后每个主节点再绑定多个节点,这样子不仅可以保证数据的安全性,还能对数据进行分片存储。

Redis Cluster的工作模式:

Redis Cluster是无中心化思想,采用哈希槽来处理数据和实例之间的映射关系,一个切片集群共有16384个哈希槽,只给Master(主节点)分配,当搭建集群,redis会自动将16384个哈希槽分配个各个主节点,数据读写的时候,根据键值对的key,按照CRC16算法计算一个16bit的值,然后再对16384取余,得到0~16383范围内的数字再去与主节点绑定,当槽位分配完之后,每个实例会把自己的哈希槽信息发给和它相连的其他节点,来完成哈希槽分配信息的扩散。当客户端连接的时候,实例会把哈希槽的分配信息发给客户端,客户端就可以通过槽位对数据进行定位读写。

注意:需要把16384个槽位都分配完,要不然Redis集群无法正常工作。

现在没有哨兵模式,如果某个主节点宕机怎么办,首先就是各个主节点之间也是互相通信的,他们之间有一个pingpong机制,当其中一个主节点宕机的时候,某个主节点ping不通了,他就会向所有节点发送消息,然后其他节点也会去ping,如果也ping不通,当票数超过2/n+1时,就会将故障主节点的从节点选为主节点,替换原来的主节点,并且占用原来的哈希槽。

为什么是六台服务器呢?

三主三从,三个主节点直接可以相互制约,相互投票,每一个主节点都有一个备份节点,当一个主节点宕机了,三个主节点之间可以通过投票的方式,来选取备份节点为主节点。

redis key的删除策略?

● 定时删除:对于设定了过期时间的key,同时还创建了一个定时器,当key达到删除时间,定时器就会被触发,此时就会将过期的key删除,该策略对内存空间足够友好, 但对CPU非常不友好,会拉低系统性能,因此不建议使用。

● 惰性删除:为了解决定时删除会占用大量CPU的问题,因此产生了惰性删除,它不持续关注key的过期时间,而是在获取key时,它才会检查key是否过期,如果过期则删除该key,这样子的话,对CPU很友好,但是对内存就不友好了,因为有的key假如一直没有用到,则会占用内存

● 定期删除:定期删除是对定时删除和惰性删除做了平衡,redis默认每秒运行10次对具有过期时间的key进行一次扫描,但是不会扫描全部的key,因为这样会大大延长扫描时间。每次默认只会扫描20个key,同时删除这20个key中已经过期的key,如果这20个key中过期key的比例超过25%,则继续扫描

redis 缓存淘汰策略?

Redis的内存参数配置,在64位操作系统中,如果未设置或设置0,代表无限制,而在32位系统中,默认内存大小为3GB。但是在实际生产环境下,一般会设置物理内存的四分之三左右。当客户端执行命令添加数据时,Redis会检查内存空间大小,如果超过最大内存,则会触发内存淘汰策略。淘汰策略分为以下三种八类:

1.对设置了过期时间数据淘汰:

● 设置了过期时间,且最近最久没有使用的数据进行淘汰

● 设置了过期时间,且最近最少没有使用的数据进行淘汰

● 设置了过期时间,且即将过期的数据进行淘汰

● 设置了过期时间的数据进行随机淘汰

2.对所有数据淘汰:

● 从所有数据中,删除最近最久没有被使用的数据进行淘汰

● 从所有数据中,删除最近最少没有被使用的数据进行淘汰

● 从所有的数据中,随机删除

3.不淘汰:(默认)

● 当内存空间不足时,直接返回错误信息

如何保证redis 缓存双写一致性?

保证 Redis 缓存双写一致性可以采用以下两种方式:

  1. 使用数据库写后更新缓存的方案

这种方式是在数据库写入成功后再去更新存,可以保证双写一致性,但是会影响写入操作的性能,尤其是在高并发的情况下,可能会造成响应延迟。

  1. 使用特定队列实现异步更新缓存

这种方式是使用异步方式更新缓存,即先写入数据到数据库中,然后再将更新操作以消息的方式发送到一个特定的队列中,由消费者异步地完成缓存的更新,从而能够提高写入操作的性能,同时也保证了双写一致性。

具体实现可以使用消息中间件来实现,比如使用 RabbitMQ、Kafka 等,当有写入操作时,首先将数据写到数据库中,然后将消息发送到消息中间件中,由消费者异步地更新缓存。同时可以使用 Redis 的事务机制来实现对缓存的原子性操作,保证了数据的一致性和正确性。

什么是redis缓存双写一致性?

Redis缓存双写一致性是指Redis作为缓存系统时,同时对缓存和数据存储进行更新操作时,保证缓存和数据存储的数据保持一致。这个过程需要确保在数据存储和缓存中都能够正确地处理数据更新,防止出现数据不一致的情况。

通常情况下,为了提高性能,我们会将一些热点数据缓存在Redis中,当更新这些数据时,需要同时更新缓存和数据库中的数据。如果更新操作只操作了数据库中的数据,而没有更新缓存中的数据,当后续读取该数据时,就会出现缓存和数据库中数据不一致的问题。反之如果只更新了缓存中的数据,而没有更新数据库中的数据,那就有导致缓存和数据库之间的数据不一致问题。

为了解决这个问题,Redis提供了多种方法来保证数据一致性,比如“写后读”、使用队列等等。实现Redis缓存双写一致性有助于确保数据的正确性和可靠性。

redis的延迟双删

什么是延迟双删

延迟双删策略是分布式系统中数据库存储和缓存数据保持一致性的常用策略,但它不是强一致。其实不管哪种方案,都避免不了Redis存在脏数据的问题,只能减轻这个问题,要想彻底解决,得要用到同步锁和对应的业务逻辑层面解决。

为什么要进行延迟双删?

一般我们在更新数据库数据时,需要同步redis中缓存的数据,所以存在两种方法:

第一种方案:先执行update操作,再执行缓存清除。

第二种方案:先执行缓存清除,再执行update操作。

这两种方案的弊端是当存在并发请求时,很容易出现以下问题:

第一种方案:当请求1执行update操作后,还未来得及进行缓存清除,此时请求2查询到并使用了redis中的旧数据。

第二种方案:当请求1执行清除缓存后,还未进行update操作,此时请求2进行查询到了旧数据并写入了redis。

如何实现延迟双删?

所以此时我们需要使用第三种方案:先进行缓存清除,再执行update,最后(延迟N秒)再执行缓存清除。

延迟双删用比较简洁的方式实现 mysql 和 redis 数据最终一致性,但它不是强一致。

延迟,是因为 mysql 和 redis 主从节点数据同步不是实时的,所以需要等待一段时间,去增强它们的数据一致性。

延迟是指当前请求逻辑处理延时,而不是当前线程或进程睡眠延迟。

mysql 和 redis 数据一致性是一个复杂的课题,通常是多种策略同时使用,例如:延迟双删、redis 过期淘汰、通过路由策略串行处理同类型数据、分布式锁等等。

Redisson实现分布式锁的原理?

结合图形分析:

● 加锁机制

线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。

线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis

● WatchDog自动延期看门狗机制

第一种情况:在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生

第二种情况:线程A业务还没有执行完,时间就过了,线程A 还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间

● lua脚本-保证原子性操作

主要是如果你的业务逻辑复杂的话,通过封装在lua脚本中发送给redis,而且redis是单线程的,这样就保证这段复杂业务逻辑执行的原子性

是否了解缓存穿透、击穿、雪崩?

缓存击穿是指,某一热点数据存储到redis中,该数据处于高并发场景下,如果此时该key过期失效,这样就会有大量的并发请求进入到数据库,对数据库产生大的压力,甚至会压垮数据库。

针对于缓存击穿这种情况,常见的解决方案有两种:

● 热数据不设置过期时间

● 使用互斥锁,可以使用redisson的分布式锁实现,就是从redis中查询不到数据时,不要立刻去查数据库,而是先获取锁,获取到锁后再去查询数据库,而其他未获取到锁的请求进行重试,这样就可以确保只有一个查询数据库并且更新缓存的请求。

缓存雪崩的情况往往是由两种情况产生:

● 情况1:由于大量 key 设置了相同的过期时间(数据在缓存和数据库都存在),一旦到达过期时间点,这些 key 集体失效,造成访问这些 key 的请求全部进入数据库。

● 情况2:Redis 实例宕机,大量请求进入数据库

针对于雪崩问题,可以分情况进行解决:

● 情况1的解决方案

○ 错开过期时间:在过期时间上加上随机值(比如 1~5 分钟)

○ 服务降级:暂停非核心数据查询缓存,返回预定义信息(错误页面,空值等)

● 情况2的解决方案

○ 事前预防:搭建高可用集群

○ 构建多级缓存,实现成本稍高

○ 熔断:通过监控一旦雪崩出现,暂停缓存访问待实例恢复,返回预定义信息(有损方案)

○ 限流:通过监控一旦发现数据库访问量超过阈值,限制访问数据库的请求数(有损方案)

缓存穿透是指,如果一个 key 在缓存和数据库都不存在,那么访问这个 key 每次都会进入数据库

● 很可能被恶意请求利用

● 缓存雪崩与缓存击穿都是数据库中有,但缓存暂时缺失

● 缓存雪崩与缓存击穿都能自然恢复,但缓存穿透则不能

针对缓存穿透,一般有两种解决方案,分别是:

● 如果数据库没有,也将此不存在的 key 关联 null 值放入缓存,缺点是这样的 key 没有任何业务作用,白占空间

● 采用BloomFilter(布隆过滤器)解决,基本思路就是将存在数据的哈希值存储到一个足够大的bitmap中,在查询redis时,先查询布隆过滤器,如果数据不存在直接返回即可,如果存在的话,再执行缓存中命中、数据库查询等操作。

redis为什么那么快?

1、最主要的原因,数据存取发生在内存中,内存IO的速度非常快。

2、Redis数据结构的选取非常合适,采用全局的链式哈希表进行存储每一个key-value,同时,Redis对于哈希扩容的处理也很高效,查询时间非常快,是趋近于O(1)的,并且当哈希桶使用个数比较多时,会进行哈希表的扩容,避免时间复杂度的增高。redis采用渐进式扩容的策略,redis中默认会有两张全局的哈希表,一开始所有的key-value都存储在第一张哈希表中,当第一张哈希表存储的元素过多。会用第二张哈希表进行扩容,然后第一张哈希表的元素重新映射到第二张哈希表中。第一张哈希表留作下一次扩容备用。

3、redis采用的网络模型是reactor模型,这种模型几乎是所有开源框架共用的一套模型。他会为每个套接字绑定一组IO处理函数,将套接字的IO处理转变为事件处理,在事件处理函数内执行IO的具体操作。

4.redis的持久化很好的利用了fork子进程读时共享,写时复制的策略。利用这一点进行RDB持久化时,只要数据不进行更改,父子进程都利用的是相同的物理内存。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值