【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
相关文章
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
存储 NoSQL 算法
阿里面试:亿级 redis 排行榜,如何设计?
本文由40岁老架构师尼恩撰写,针对近期读者在一线互联网企业面试中遇到的高频面试题进行系统化梳理,如使用ZSET排序统计、亿级用户排行榜设计等。文章详细介绍了Redis的四大统计(基数统计、二值统计、排序统计、聚合统计)原理和应用场景,重点讲解了Redis有序集合(Sorted Set)的使用方法和命令,以及如何设计社交点赞系统和游戏玩家排行榜。此外,还探讨了超高并发下Redis热key分治原理、亿级用户排行榜的范围分片设计、Redis Cluster集群持久化方式等内容。文章最后提供了大量面试真题和解决方案,帮助读者提升技术实力,顺利通过面试。
|
1月前
|
存储 NoSQL 算法
面试官:Redis 大 key 多 key,你要怎么拆分?
本文介绍了在Redis中处理大key和多key的几种策略,包括将大value拆分成多个key-value对、对包含大量元素的数据结构进行分桶处理、通过Hash结构减少key数量,以及如何合理拆分大Bitmap或布隆过滤器以提高效率和减少内存占用。这些方法有助于优化Redis性能,特别是在数据量庞大的场景下。
面试官:Redis 大 key 多 key,你要怎么拆分?
|
2月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
2月前
|
NoSQL 算法 Redis
Redis面试篇
Redis面试篇
66 5
|
1月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
2月前
|
缓存 NoSQL 算法
面试题:Redis如何实现分布式锁!
面试题:Redis如何实现分布式锁!
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?