【Redis面试】基础题总结(上)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【Redis面试】基础题总结(上)

1.Redis是单线程还是多线程?


答:redis6.0版本之前的单线程指的是其网络i/o和键值对读写是由一个线程完成的

redis6.0引入的多线程是指网络请求过程采用了多线程,而键值对的读写仍然是单线程,所以redis依然是并发安全的

也即是说只有网络请求模块和数据操作模块是单线程的,而它的持久化,集群数据同步等,其实是额外的线程执行的。


2.redis单线程为什么还能这么快?


1.命令执行是基于内存的,一条命令在内存里才操作的时间是几十纳秒

2.命令执行是单线程的,没有线程切换的开销

3.基于多路复用机制提升redis的i/o利用率

4.高效的数据存储结构:全局hash表以及多种高级数据结构。比如:跳表,压缩列表,链表等等。


4.redis底层是如何用调表来存储的?


答:调表:将有序链表改造为近似折半查找算法,可以快速进行插入,删除,查找操作。

7ee916f713114e9bbcc8aaee61118740.png

5.redis设置的key过期了为什么没有释放内存?


答:1.在设置key的时候,设置了过期时间,在key还没有过期的这段时间内,重新设置了这个key的值,没有设置过期时间,那么这个key就永久存在了。

2.redis对于过期的key的处理一般有惰性删除和定时删除两种策略

惰性删除:当读写一个已过期的key时,会触发惰性策略,判断key是否过期,如果过期了直接删掉。

定时删除:由于惰性删除无法保证冷数据及时的被删除掉,所以redis会将设置了过期时间的key放到一个独立的字典中,并对该字典每秒进行10次 的扫描,扫描不会全部扫描,

这里采用的时一种简单的贪心策略。逻辑如下:

a.从过期字典中随机选择20个key

b.删除20个中已过期的lkey

c.如果已过期key的比例超过25%,则重复步骤a。


6.redis中key没设置过期时间,为啥被redis主动删除了?


答:当redis已用内存超过maxmemory限定时,触发主动清理策略

主动清理策略在redis4.0之前,有6中,4.0之后又加了两种共8种。

针对设置了过期时间的key的处理策略:

1.volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后顺序进行删除。

2.volatile-random:在设置了过期时间的key中进行随机删除

3.volatile-lru:会使用LRU算法筛选设置了过期时间的键值对进行删除

4.volatile-lfu:会使用lfu算法

针对所有key进行处理

5.allkeys-random:从所有键值对中随机选择并删除数据

6.allkeys-lru:使用LRU算法在所有数据中删除

7.allkeys-lfu:使用lfu算法

8.noeviction:不会剔除任何数据,拒绝所有的写入操作,并返回客户端信息错误“OOM command not allowed when used memory”,此时redis只响应读操作。

LRU(Least Recently Used)是按照最近最少使用原则来筛选数据,即最不常用的数据会被筛选出来!


7.redis淘汰key的算法LRU与LFU


LRU算法:(Least Recently Used 最近最少使用),淘汰很久没被访问的数据,以最近一次访问时间作为参考)

LFU算法:(Least Frequently Used 最不经常使用),淘汰最近一段时间访问次数最少的数据,以次数作为参考。

绝大多数采用LRU策略,当存在大量热点缓存数据时,LUF可能更好

标准LRU:把所有的数据组成一个链表,表头和表尾分别表示MRU和LRU端,即最常使用端和最少使用端。刚被访问的数据会被移动到MRU端,而新增的数据也是刚被访问的数据,也会被移动到MRU端。当链表的空间被占满时,它会删除LRU端的数据。


近似LRU:Redis会记录每个数据的最近一次访问的时间戳(LRU)。Redis执行写入操作时,若发现内存超出maxmemory,就会执行一次近似LRU淘汰算法。近似LRU会随机采样N个key,然后淘汰掉最旧的key,若淘汰后内存依然超出限制,则继续采样淘汰。可以通过maxmemory_samples配置项,设置近似LRU每次采样的数据个数,该配置项的默认值为5。


LRU算法的不足之处在于,若一个key很少被访问,只是刚刚偶尔被访问了一次,则它就被认为是热点数据,短时间内不会被淘汰。


LFU算法正式用于解决上述问题,LFU(Least Frequently Used)是Redis4新增的淘汰策略,它根据key的最近访问频率进行淘汰。LFU在LRU的基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用LFU策略淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出内存。如果两个数据的访问次数相同,LFU再比较这两个数据的访问时间,把访问时间更早的数据淘汰出内存。


8.redis的主从同步是如何实现的?


从版本2.8开始。redis使用psync命令来完成主从数据同步,同步过程分为全量复制和部分复制,全量复制一般用于初次复制的场景,部分复制用于处理网络中断等原因造成的数据丢失的场景,psync命令需要以下参数的支持:

1.复制偏移量:主节点处理写命令后,会把命令长度做累加记录,从节点在接收到写命令后,也会做累加,从节点会每秒上报一次自身的复制偏移量给主节点,而主节点会保存从节点的复制偏移量

2.挤压缓冲区:保存在主节点上的一个固定长度队列,默认为1m,当主节点有连接的从节点时被创建,主节点处理写命令时,不但会把命令发送给从节点,还会写如挤压缓冲区,缓存区是先进先出的队列。可以保存最近已复制的数据,用于部分复制和命令丢失时的数据补救。

3.主节点运行id:每个redis节点启动之后都会动态分配一个40位的16进制字符串作为运行id,如果使用ip和端口的方式标识主节点,那么主节点重启后变更了数据集(RDB/AOF),从节点再基于复制偏移量复制数据是不安全的,因此当主节点的id变化后,从节点将做全量复制。

psync命令的执行过程及返回结果,如下图:

a,若回复+FULLRESYNC,则从节点将触发全量复制;

b,若回复+CONHTINUE,则从节点触发部分复制;

c,若回复-err,说明主节点版本过低,无法识别psync命令

8f4e196b18a14d40ad570f151cc78429.png


9.如何实现redis的高可用?


实现redis的高可用主要有哨兵模式和集群两种方式:

哨兵:

redis sentinel 是一个分布式架构,它包含若干个哨兵节点和数据节点。每个哨兵节点会对数据节点和其余哨兵节点进行监控,当发现节点不可达时会对节点做下标识。如果被标识的是主节点,它就会与其他哨兵节点进行协商,当多数哨兵节点认为主节点不可达时,他们会选举一个哨兵节点来完成自动故障转移的工作,同时还会将这个变化实时的通知给应用方,整个过程是自动的,不需要人工介入,有效解决了redis的高可用问题。

哨兵模式的特征:

1.会定期监控数据节点,其他哨兵节点是否可达

2.会将故障转移的结果通知给应运方

3.可以将从节点晋升为主节点,并维护后续的主从关系

4.哨兵模式下,客户端连接的是哨兵节点集合,从中获取主节点的信息

5.节点的故障判断是由多个哨兵共同完成的,可以防止误判

6.哨兵节点集合是由多个哨兵节点组成的,即使个别节点不可以,整个集合依然是健壮的

7.哨兵节点也是独立的redis节点,他们不存储数据,只支持部分命令

集群:

redis集群采用虚拟槽区分来实现数据分片,它把所有的键根据哈希函数映射到0-16383整数槽内,计算公式为slot=CRC165(key)&16383,每一个节点负责维护一部分槽及槽所映射的键值数据,

虚拟槽区分具有的特点:

1.解耦数据和节点之间的关系,简化了节点扩容和收缩的难度

2.节点自身维护槽的映射关系,不需要客户端或代理服务维护槽分区元数据

3.支持节点,槽,键之间的映射查询,用于数据路由,在线伸缩等场景。


f9d797638d3d497c877c10f02922f77f.png

10.缓存穿透,缓存击穿,缓存雪崩有什么区别,该如何解决?


缓存穿透:

问题描述:客户端查询根本不存在的数据,使得请求直达存储层,导致其负载过大,甚至宕机。出现这种状况的原因,可能是业务层将缓存和库中的数据删除了,也可能是人为恶意攻击,专门访问不存在的数据。

解决方案:1.缓存空对象,存储层未命中后,仍将空值存入缓冲层,客户端再次访问数据时,缓冲层会直接返回空值。

2.布隆过滤器:将数据存入布隆过滤器,访问缓存之前以过滤器拦截,若请求的数据不存在直接返回空值。

缓存击穿:

问题描述:一份热点数据,它 的访问量非常大,在其缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。

解决方案:1.永不过期:热点数据不设置过期时间,这是物理层上的永不过期。或者为每个数据设置逻辑过期时间,当发现数据逻辑过期时,使用单独的线程重新缓存。

2.加互斥锁:对数据的访问加互斥锁,当一个线程访问数据时,其他线程只能等待,这个线程访问过后,缓存中的数据将被重建,届时其他线程就可以直接从缓存中获取

缓存雪崩:

问题描述:在某一时刻缓存层无法继续提供服务,导致所有的请求直达存储层,导致数据库宕机。可能时缓存中大量数据同时过期,也可能是redis节点发生故障,导致大量请求无法的到处理。

解决方案:1.避免数据同时过期:设置过期时间时,附加一个随机数,避免大量key同时过期。

2.启用降级和熔断措施:在发生雪崩时,若访问的数据不是核心数据,则直接返回预定义信息/空值/错误信息。或者在发生雪崩时,对于访问缓存接口的请求,客户端并不会把请求发给redis,而是直接返回。

3.构建高可用的redis服务:采用哨兵或集群模式,部署多个redis实例,个别节点宕机,依然可以保持服务的整体可用。


11.redis的持久化策略


redis支持rdb持久化,aof持久化,rdb-aof混合持久化三种


RDB:redis DataBase,是redis默认采用的持久化方式,它以快照的方式将数据持久化到硬盘中,rdb会创建一个经过压缩的二进制文件,以.rdb结尾,内部存储了各个数据库的键值对数据等信息,rdb持久化的触发方式有两种:

1.手动触发;通过SAVE和BGSAVE命令触发rdb持久化操作,创建.rdb文件;

2.自动触发:通过配置选项。让服务器在满足指定条件时自动执行BGSAVE命令。

其中SAVE命令执行期间,redis服务器将阻塞,知道.rdb文件创建完毕为止,而BGSAVE命令是异步版本的SAVE命令,它会使用redis服务器进程的子进程,创建.rdb文件。BGSAVE命令在创建子进程时会存在短暂的阻塞,之后服务器便可以继续处理其他客户端的请求。总之,BGSAVE命令是针对SAVE阻塞问题做的优化。redis内部所有涉及RDB的操作都采用BGSAVE的方式,而save命令已废弃

RDB持久化的优缺点如下:

优:RDB生成紧凑的压缩的二进制文件,体积小,使用该文件恢复数据的速度非常快

缺:BGSAVE每次运行都要执行fork操作创建子进程,属于重量级操作,不宜频繁执行

所以RDB持久化没办法做到实时的持久化


AOF:

Append Only File,解决了数据持久化的实时性,是目前redis持久化的主流方式,aof以独立日志的方式,记录了每次写入命令,重启时重新执行aof文件中的命令来恢复数据。工作流程为:

命令写入(append),文件同步(sync),文件重写(rewrite),重写加载(load)

68aa497ebe2c4ab78e3910c844c6e93c.png

aof以文本协议格式写入命令,eg:


*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n

文本协议的优点:

1.具有很好的兼容性

2.直接采用文本协议格式,可以避免二次处理的开销

3.文本协议具有可读性,方便直接处理和修改

AOF持久化的同步机制:

为了提高程序的写入性能,现代操作系统会把针对磁盘的多次操作优化为一次操作

1.当程序调用write对文件写入时,系统不会直接写入硬盘,而是写入缓冲区

2.当达到指定时间周期或缓冲区写满时,系统才会执行flush操作,将缓冲区中的数据洗至磁盘中

这种优化机制虽然提高了性能,但是给程序的写入带来了不确定性

1.对于aof这样的持久化功能来说,冲洗机制将直接影响aof持久化的安全性

2.为了消除不确定性,redis向用户提供了appendfsync选项,来控制系统冲洗aof的频率;

3.linux的glibc提供了fsync函数,可以将指定文件强制从缓冲区刷到硬盘,上诉选项正是基于此函数

ae4e0fa61b68475abcc2db3d73eacb55.png

AOF的优缺点:

优:与RDB持久化可能丢失大量数据相比,aof持久化安全性更高,通过everysec选项,用户可以将数据丢失的时间窗口限制在1秒以内

缺:文本协议比二进制大得多,aof需要通过执行aof文件中的命令来恢复数据库,其恢复速度比rdb慢,aof在进行重写时也需要创建子进程,在数据库体积较大时将占用大量资源,会导致服务器短暂阻塞。


RDB-AOF混合持久化:

从4.0开始引入,这种模式是基于aof持久化构建而来的,用户可以通过配置文件的aof-user-preamble yes配置项开启aof混合持久化,redis服务器在执行aof重写操作时,会按如下原则处理数据:

1.像执行BGSAVE一样,根据数据库当前状态生成对应的rdb数据,并写入aof中

2.对于重新之后执行的redis命令,则以协议文本的方式追加到aof文件的末尾,即rdb数据之后

通过使用混合持久化,用户可以同时获得rdb持久化和aof的优点,服务器可以通过aof文件包含rdb数据来实现快速的数据恢复操作,又可以通过aof文件包含的aof数据来将丢失数据的时间窗口设置在1s内。


12.redis线上数据如何备份?


1.写crontab定时调度脚本,每小时都copy一份rdb或aof到另外一台机器中去,保持最近48小时的备份

2.每天都保留一份当日的数据备份到一个目录中去,可以保留最近一个月的

3.每次copy的时候,都把太旧的备份给删了


13.如何保证缓存与数据库的双写一致性?

四种同步策略:

要想保证缓存与数据库的双写一致,有四种:

1.先更新缓存,在更新数据库

2.先更新数据库,再更新缓存

3.先删除缓存,再更新数据库

4.先更新数据库,再删除缓存

问题1:更新与删除缓存哪种方式更合适?

更新:

优:每次数据变化都及时更新,所以查询时不容易出现未命中的情况

缺:更新缓存消耗大,数据需要经过复杂的计算再写入缓存,频繁的更新操作就会影响服务器的性能,如果是写入数据频繁的业务场景,那么可能频繁的更新缓存时,却没有业务读取该数据

删除:

优:操作简单,无论更新是否复杂,都将缓存中数据删除

缺:删除缓存后,下一次查询缓存会出现未命中,这时需要重新读取一次数据库

从上面的比较来看,一般情况下,删除缓存是更优的方案。

问题2:先操作数据库还是先操作缓存?

在两步操作都正常的情况下:

a:删除缓存,再更新数据库

1.进程A删除缓存

2.进程B读取缓存失败

3.进程B读取数据库成功,得到旧的数据库

4.进程B将旧的数据更新到缓存

5.进程A将新的数据更新到数据库

最终:数据库和缓存二者数据不一致

b:更新数据库,再删除缓存:

1.进程A更新数据库

2.进程B查询缓存成功

3.进程A删除缓存

可见最终缓存和数据库的内容时是一致的虽然B读到了旧的数据,但是这两步的执行速度快影响不大

最终结论:先更新,再删除缓存是影响更小的方案。

当然如果根据实际情况不得不使用先删除,再更新,则可以通过延时双删的策略解决,具体如下:

1.删除缓存;

2.更新数据库;

3.sleepN毫秒;

4.再次删除缓存;

阻塞一段时间后,再次删除缓存,就可以把这个过程中缓存的不一致数据删掉。

如果是读写分离的结构:


8c01cafb255b4890be6e4f4c78577493.png

进程A先删除缓存,再更新数据库,然后主同步到从,而在同步之前,可能会有进程B访问了缓存,当发现数据不存在时,会从数据库访问,然后同步到缓存,这样也会导致不一样,这个问题解决方案依然时采用双删的策略,但是在评估延长时间的时候,要考虑主从数据库同步的时间。

第二次删除失败了则么办?

依然增加重试的次数,但次数要有限制,超出限制后要采用报错,记日志,发邮件提醒等措施。


如果两步中出现失败时,无法判断哪个更好。出现失败时采用重试机制解决。


bbffda3dad40480d9e0508f04b8fc8a9.png

以先更新,再删除为例:

1.更新数据库成功;

2.删除缓存失败;

3.将此数据加入消息队列;

4.业务代码消费这条数据;

5.业务代码根据这条消息的内容,发起重试机制,即从缓存中删除这条记录。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
26天前
|
存储 NoSQL Java
可能是最漂亮的Redis面试基础详解
我是南哥,相信对你通关面试、拿下Offer有所帮助。敲黑板:本文总结了Redis基础最常见的面试题!包含了Redis五大基本数据类型、Redis内存回收策略、Redis持久化等。相信大部分Redis初学者都会忽略掉一个重要的知识点,Redis其实是单线程模型。我们按直觉来看应该是多线程比单线程更快、处理能力更强才对,比如单线程一次只可以做一件事情,而多线程却可以同时做十件事情。但Redis却可以做到每秒万级别的处理能力,主要是基于以下原因:(1)Redis是基于内存操作的,Redis所有的数据库状态都保存在
可能是最漂亮的Redis面试基础详解
|
14天前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
21天前
|
NoSQL 算法 Redis
Redis面试篇
Redis面试篇
31 5
|
21天前
|
缓存 NoSQL Java
Java中redis面试题
Java中redis面试题
28 1
|
2天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
27天前
|
NoSQL Redis
redis 的 key 过期策略是怎么实现的(经典面试题)超级通俗易懂的解释!
本文解释了Redis实现key过期策略的方式,包括定期删除和惰性删除两种机制,并提到了Redis的内存淘汰策略作为补充,以确保过期的key能够被及时删除。
44 1
|
19天前
|
缓存 NoSQL 算法
面试题:Redis如何实现分布式锁!
面试题:Redis如何实现分布式锁!
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
22天前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
50 2
|
26天前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
24 0