Bootstrap

Redis面试相关问题

在这里插入图片描述

1 基础问题

1.1 有用过Redis吗?介绍下【Redis是什么?

Redis是一个C语言编写开源工具,其可以作为NoSQL数据库、缓存和消息中间件,其底层主要是基于内存管理数据,有比较出色的性能,所以Redis一般被用于高速读写的应用场景。

1.2 Redis支持哪些存储数据类型?

Redis共支持8中数据类型,基础数据类型包括:String、LIst、Set、SortSet、Hash;特殊数据类型包括:位图(bitmap),超日志(hyperLogLog),地图索引(Geo)

1.3 Redis SetSortSet区别是什么?

  1. SetSortSet都是集合,都是存储不重复的元素。在SortSet中,每个元素都有一个对应的分数,SortSet会对存储的元素进行分数排序。
  2. 使用场景不同,Set主要是对元素快速去重,SortSet则是需要元素排序的场景,例如排行榜等

1.4 SortSet 怎么实现有序的?

Sort底层采用的数据结构是哈希表+跳表,跳表本身就是一个有序列表,所以SortSet是有序的。

跳表数据结构:

1.5 Redis为什么那么快?

Redis之所以这么快,主要取决于以下因素:

  • Redis数据模型是基于内存管理的
  • Redis数据结构简单
  • Redis 采用单线程来处理主要的响应命令,避免了多线程的上下文切换和锁竞争的开销。虽然单线程可能听起来会限制性能,但在Redis的场景中,由于CPU并不是Redis的主要性能瓶颈,且单线程可以避免多线程带来的复杂性和开销,因此Redis的单线程模型反而能够提高其性能。
  • IO多路复用,对于一些耗时操作采用多线程管理,但是底层数据处理采用的是单线程。

1.6 Redis为什么要设计为单线程?

首先,Redis的单线程只是针对命令响应处理,对于一些网络操作,则是多线程。对于命令的处理,之所以采用单线程,主要有以下原因:

  • 简化数据处理操作,避免线程上下文切换带来的CPU额外消耗
  • 避免锁竞争,保证数据安全和连续性
  • Redis主要性能瓶颈在于网络IO,而不是CPU

1.7 Redis 6.x以后,为什么要把 Redis 改为多线程模式?

并没有全部改为多线程模式,而是只将IO操作改为多线程。因为Redis是基于内存操作的,在实际应用中,性能瓶颈不在于CPU,而是在于IO访问。

1.8 Redis数据淘汰机制有哪些?【Redis内存淘汰策略有哪些?】

Redis的数据只有达到配置内存上限,才会使用内存淘汰

Redis总共提供了九种淘汰策略,分别是:

  • noeviction:默认策略,内存写满后,拒绝写操作
  • volatile-random:从设置过过期时间的数据中,随机挑选已经过期的数据进行淘汰
  • allkeys-random:随机淘汰数据
  • volatile-lru(Least Recently used):从设置过过期时间的数据中,淘汰最少使用的数据
  • allkeys-lru(Least Recently used):淘汰最少使用数据
  • volatile-ttl(Time to live):从设置过过期时间的数据中,淘汰即将过期的数据
  • allkeys-ttl(Time to live):淘汰即将过期的数据【随机挑选数据,然后从这批数据中挑选过期时间最短的数据淘汰】
  • volatile-lfu(Least-Frequently-used):从设置过期时间的数据中,淘汰最不经常使用的数据
  • allkeys-lfu(Least-Frequently-used):淘汰最不经常使用的数据

1.9 LFU算法和LRU算法的区别是什么?

两者的区别就在于其基本思想,如下:

  • LFU算法基于“最近不常使用原则”,即认为使用次数最少的数据在未来被访问的可能性也较小。因此,当缓存空间不足时,LFU算法会选择淘汰使用次数最少的数据。
  • LRU算法基于“最近不使用原则”,即认为最近被访问的数据更有可能在将来再次被访问,而较长时间未被访问的数据则在未来被访问的可能性较小。因此,当缓存空间不足时,LRU算法会选择淘汰最近最少使用的数据。

1.10 Redis怎么给key设置过期时间?

key设置过期时间,可以使用EXPIREPEXPIREEXPIREATPEXPIREAT命令来给key设置过期时间。也可以通过一些命令,在插入数据时,直接设置key过期时间,例如setex命令。

expire:expire mykey timeout,时间单位为妙
pexpire: pexpire mykey timeout,时间单位为毫秒
expireat:expireat mykey timestamp,timestamp是unix时间戳,表示在这个timestamp时间戳key过期,单位是按秒计算
pexpireat:pexpireat mykey timestamp,和expireat类似,但单位是毫秒

1.11 Redis怎么查看key的剩余存活时间

可以通过TTLPTTL命令查看key的剩余生存时间(TTL,Time To Live)。如果key不存在或没有设置过期时间,TTL将返回-2,如果key存在但没有设置过期时间,则返回-1。PTTL命令的行为与TTL类似,但返回的是毫秒级的剩余生存时间。

1.12 Redis怎么持久化数据?

Redis的持久化数据主要通过两种方式实现:RDB(Redis Database Backup file)和AOF(Append Only File)。这两种方式各有特点,可以根据实际需求选择使用或结合使用。

  • RDB持久化:
    • 概念:RDB是基于内存快照来实现数据持久化的,底层会开一个守护线程,会周期性的将内存数据写入到磁盘中,生成一个二进制dump.rdb文件。反持久化的时候,只需要同步dump.rdb文件即可。
    • 触发方式:两种触发方式,一种是手动命令触发,例如使用save命令;第二种事自动触发,在Redis配置文件中,可以配置持久化的触发方式和触发周期。
  • AOF持久化:
    • 概念:AOF是基于记录Redis命令来持久化的,底层会将所有的更新命令操作会以app的方式添加到一个文件的末尾。反持久化的时候,会读取AOF文件,执行里面的命令。

1.13 AOF有几种写入模式?

三种写入模式,分别是:

  • always:每条命令都会同步实时写入到AOF文件中,安全性最高,性能最差
  • no:写入交由操作系统控制,安全性最差,性能最高
  • everysec:每秒写入一次,安全性和性能是前两种的折中

1.14 AOFRDB的区别是什么?

AOFRDB都是Redis持久化手段,两者区别主要在于实现方式、性能、安全性方面,如下:

  1. 实现方式:AOF是通过记录命令来实现持久化,RDB则是通过内存快照来实现持久化。
  2. 性能方面:在持久化过程中,AOFRDB性能更快,反持久化过程中,RDB则比AOF更快。性能消耗方面,RDB一般采用的是子进程来同步内存快照,性能消耗较AOF较小。
  3. 安全性方面:AOFRDB安全性更好。

1.15 save命令和bgsave命令有什么区别?

savebgsave命令,都是Redis的持久化命令,两者主要在性能和执行方式上有所区别。

  • save会阻塞主进程,拒绝接受网络IO操作【客户端访问操作】,会等主进程对内存信息备份完后,才会接受释放主进程。一般情况下,save命令主要是用来做Redis调试,多在测试环境中使用。
  • bgsave不会阻塞主进程,会根据主进程fork一个子进程,主进程继续响应IO操作,不会阻塞,所有的备份操作都交给子进程。子进程备份完成后,会通知主进程销毁。一般情况下,bgsave命令主要在生产环境中使用。

1.16 AOF的优缺点是什么?【为什么使用AOF?】RDB的优缺点是什么?【为什么使用RDB?】

持久化方式优点缺点
RDB1. 恢复速度快1. 数据安全性低,可能会丢失某个时间段内的数据
2. 紧凑的单一文件,适合备份2. fork子进程时可能会消耗较多CPU资源
3. 对Redis服务器性能影响较小3. 重启时需要加载整个RDB文件,如果文件很大,加载时间较长
AOF1. 数据安全性高,最多丢失1秒的数据1. AOF文件比RDB文件大,恢复数据较慢
2. 灵活性高,可以通过配置写入策略来平衡性能和数据安全性2. AOF文件写入频繁,对磁盘I/O要求较高
3. AOF文件以追加方式写入,不会破坏原有文件内容3. AOF文件需要定期重写,以减小文件体积

1.17 Redis怎么做消息队列?

Redis可以通过List数据结构实现消息队列,发布者给List尾部添加元素,订阅者可以从头部获取元素。这种方式虽然简单,但是缺点也很明显,一个消息只能被一个消费者消费。
此外,Redis提供了**发布/订阅**机制,允许消息发布者将消息发送到消息通道中,然后再分配给其他消费者订阅使用。不过这种模式有个缺点是,不支持消息持久化,消费者一旦断联,很可能引起发送期间,消息丢失。
其实Redis并不适合做消息队列,可以用其他的MQ工具代替,例如rocketMQ,kafka等。

1.18 Redis事务

1.18.1 什么是Redis事务?

Redis事务指——将多个命令加入到一个队列中,这些命令是有序的,事务执行开始后,会将这些命令一个一个执行,直到执行完毕。与传统的RDB事务不同,Redis事务缺乏_原子性隔离性_,即Redis在事务执行过程中,如果某个命令执行失败,事务的执行结果并不会回滚;**隔离性**则是在事务执行过程中,其他事务仍然可以对元素进行读写操作。

1.18.2 Redis事务怎么使用?

Redis相关的命令包含五个,分别是:multiexecdiscardwatchunwatch。其中multi开启事务,exec执行事务,discard放弃事务执行,watch监控元素,unwatch取消监控。

watch用于监视一个或多个键,如果在事务执行之前这些键被其他命令所改动,那么事务将被打断。当不再需要监视任何键时,可以使用unwatch命令取消监视。

1.18.3 Redis事务有什么特点和缺点?

特点与缺点也是相互的,其特点如下:

  1. 批量操作,支持多个命令打包成一个事务,一起操作
  2. 无回滚,事务命令中某一条执行失败,并不会回滚其他事务已经执行的操作
  3. 事务之间无隔离,事务正在操作的元素,也可以被其他事务操作

缺点也很明显,如下:

  1. 不保证原子性
  2. 事务执行过程中,会阻断IO网络操作
  3. 持久化过程中,如果系统崩盘,可能会导致事务持久化信息丢失

1.18.4 怎么解决Redis事务的缺点?

对于Redis的事务缺点,主要集中在**不保证原子性和_网络IO阻塞_问题上。
对于
不保证原子性**,可以采用如下方法解决:

  • 可以通过分布式锁的思想,将事务的封装和操作集中在服务端,事务在执行之间先获取分布式锁,如果获取成功,则继续执行,失败则拒绝处理。

对于_网络IO阻塞_问题,可以采用如下方法解决:

  • 网络阻塞的问题,一般是事务执行耗时比较长引起的,也就是大事务。可以将大事务拆分成多个小事务执行,从而避免IO阻塞。如果事务无法拆分,可以从硬件方面解决,例如采用主备库等策略,将读写隔离,一个机器被阻塞,其他机器仍然可以继续工作。

1.19 发布Redis命令时,Redis是怎么处理的?

Redis底层采用了多线程进行了网络IO服用,所有的线程接到命令后,会将命令发布到事件池中。Redis底层会开一个单线程来处理这些命令,等命令处理完成后,会将处理结果反馈给IO线程。

2 生产问题

2.1 Redis慢查询怎么解决?

Redis慢查询要先分析其原因,确定问题节点,只有找到问题节点才能解决慢查询问题。一般情况下,可以查看API接口链路,找到Redis的访问节点,也就是那个机器变慢了。

  1. 排查问题节点:
    1. 排查网络问题,是不是网络延迟,网络抖动等问题引起的慢查询。可以用redis-cli -h ip --intrinsic-latency 60查询实例60秒内,每秒访问响应时间。如果的确平均响应时间超过了慢查询阈值,那么可以确定该Redis节点的确变慢了。
    2. 查看RedisslowLog慢操作日志,找出最近的操作命令。通过这个日志,可以确定那个时间点,执行了什么样的命令比较耗时。
  2. 慢查原因分析和解决:
    1. 硬件相关问题:
      1. 网络IO问题:假如Redis的节点超过网络IO链接上限,可能会导致查询变慢。比如Redis单个实例QPS等于2W,结果某个时刻QPS等于10W,这个时候查询就会变慢。解决这个问题的方法也很简单,横向扩容机器,提升Redis整体负载。
      2. 内存不足:Redis有内存扩展机制,在直接内存不够的情况下,会使用交换内存,也可以说叫虚拟内存。虚拟实际就是磁盘,对于磁盘的访问肯定比直接内存慢。解决该问题的办法也很简单,增加直接内存大小,或者调整存储上限和回收策略,避免使用虚拟内存。
      3. 缓存雪崩和缓存穿透:这两个问题虽然不常见,但是也可以导致查询缓慢。表象是查询缓存Redis变慢了,实际是查询了数据库导致。解决这种问题很简单,尽量让数据保持活性,换句话来说,就是尽量使用缓存。雪崩可以用设置过期时间来解决,穿透可以用布隆过滤器等解决。
      4. 持久化策略不当:如果采用RDB来持久化Redis数据,RDB有两种方式,一种是save,直接用主进程来备份Redis数据,这个过程中,主进程是阻塞的。第二种是bgsave,会fork一个子进程,来备份数据,但是子进程会占用CPU资源,也会导致数据变慢。如果采用AOF来持久化,AOF存在刷盘和rewrite行为,这些行为也会导致Redis查询变慢。解决持久化问题的话,就是配置合理的Redis持久化策略,尽量避免持久化对Redis查询影响。例如采用RDB持久化可以放到Slave机器上,AOF持久化放到master上,AOF刷盘策略配置为everysec等。
    2. 程序相关问题:
      1. Redis大Key大Key会增加网络IO传输时长,导致Redis访问变慢。解决套路也是解决大key思路,比如大key小key等。
      2. Redis事务:用Redis事务也会导致查询慢问题,Redis事务是一个命令包裹,如果事务里面命令过多,也会导致慢查询。不过这个一般不存在,没人会在事务中查询数据,忽略。
      3. 使用了函数:Redis的查询基本上时间复杂度都是O(1),但是对于一些复杂函数操作,例如Sort、Sum等,就会增加时间复杂度,导致慢查询问题。解决思路就是,避免使用复杂函数,操作交给服务端来执行。

慢查询还是比较蛋疼的问题,需要分析逼逼一大堆,但是实际场景中,基本上都是硬件问题,配置这些,都有专门配套的模板使用。

2.2 Redis大key问题怎么解决?

这个问题比较常见,查Bigkey的方式也很简单,利用Scan命令,加上 --bigkeys参数即可。一般Scan命令会阻塞Redis,所以建议在从库上执行。如果没有从库,建议直接查看RDB文件,反解该文件信息。如果采用的不是RDB持久,那就重新拉个机器,再去Scan

解决Redis bigkey问题,可以从两个方向入手,第一个方向是bigkey产生之前加以预防,第二个方向是解决已经产生的bigkey
预防主要从以下几个方面考虑:

  1. 合理拆分数据,避免过多的数据都集中在一个key中。例如,将hash结构中,可以将hash key先拆分,然后再将value放入到合适不同的hash key中。【很简单的例子,hash的结构是,key - entry<key, value>,将entry<key, value>中的key按一定规则拆分,然后拼接为 key_xx - entry<key, value>即可】
  2. 给key设置过期时间,避免数据累积导致bigkey产生。例如,set结构中,如果不设置过期时间,set中数据会不停累积,累积时间长了会产生bigkey
  3. 限定元素数量:在setSortSethash这种类似集合的数据结构中,一般bigkey就是因为元素数量太多引起的。限定集合中的元素数量,可以解决bigkey问题。
  4. 监控和告警:监控Redis内存使用情况,有bigkey产生的话,就及时处理。

解决已经产生的bigkey

  1. 拆分:bigkey拆分生多个minorKey管理。例如Redis存储的是全国各地的省城市信息,可以按省拆分成多个key,这样bigkey会变成minorKey
  2. 合理的数据结构:对于hash结构,有些人会用hash存储一些Boolean值,对于这些可以用bitmap(位图)来代替,压缩数据空间,从而解决bigkey问题。
  3. 使用异步删除,定期清理bigkey。【unlike代替del命令,可以异步删除bigkey
  4. 压缩数据:对于String字符串数据,可以在放入Redis之前进行压缩,这样就可以解决bigkey问题。

2.3 Redis作为缓存,常用的缓存模式有哪些?【常见的三种缓存读写策略了解吗?】

数据访问流程
image.png

Redis作为缓存,常用的缓存模式主要有三种,分别是:

  • Cache-Aside-Pattern(旁路缓存模式):
    • 写:先更新数据库,再更新缓存
    • 读:先读取缓存,读取不到从数据库读,然后将读取的信息更新到缓存
    • 优点:降低数据负载,易于扩展,提高服务性能
    • 缺点:存在数据一致性问题,因为缓存一般是同步数据库的binLog日志,同步存在时差。
  • Read/Write-Through-Pattern(读写穿透模式):
    • 写:先查缓存是否有该key信息,如果有,先更新缓存,再更新数据库;如果没有,直接更新数据库。
    • 读:先读取缓存,读取不到从数据库读,然后将读取的信息更新到缓存
    • 优点:查询性能最优
    • 缺点:容易丢失数据,因为写的模式,如果缓存写成功了,数据库写失败了,就会丢失数据了
  • Write-Behind-Pattern(异步缓存模式)
    • 写:先更新缓存,将更新信息写入页,然后异步将页信息更新到数据库
    • 读:先读取缓存,读取不到从数据库读,然后将读取的信息更新到缓存
    • 优点:写入性能最优
    • 缺点:容易丢失数据,比Read/Write-Through-Pattern更严重,因为Read/Write-Through-Pattern也是同步更新缓存和数据库的,即使数据库断联,一般可以急时发现。但是此模式,是按页批量写入数据库的,如果数据库挂了,不一定能即使发现,会造成数据丢失。

相比较而言,Cache-Aside-Pattern更适合数据安全性要求较高的场景,一般是业务开发时会用。Write-Behind-Pattern则是对读写响应速率比较高的场景,一般都是中间件在用。Read/Write-Through-Pattern是前面两种场景的折中权衡。

2.4 Redis缓存击穿问题怎么解决?

Redis缓存击穿指——大量请求访问缓存,但缓存上不存在该数据,所以就导致所有的请求最终访问到了数据库。根据这个问题的根因,解决办法一般有如下几种:

  • 缓存全部数据,并且不设置过期时间,例如缓存全国省-城市信息
  • 提前预热,对热点数据提前先放入到缓存中,避免访问时造成击穿问题,例如带货直播、电商秒杀
  • 避免多请求打入数据库,设置分布式锁,在访问同一个数据时,先获取锁,如果获取不到就等待,或者直接失败。【锁一般表示有请求读DB了,读完DB会将数据重新写入到缓存的,下次就快了】

一般我是建议,根据数据量,使用场景来判断怎么解决。

2.5 Redis缓存雪崩问题怎么解决?

Redis缓存雪崩——大量请求访问缓存,但由于缓存中大量数据过期时间已经到达,导致数据删除。所以请求最终会访问到数据库,给数据库造成压力。解决办法也很简单:

  • 不设置过期时间,这种就属于常驻数据,例如缓存全国省-城市信息
  • 设置随机过期时间,例如缓存写入数据时,在基础的时间范围上,加一个随机时长,避免数据同一时间大量过期
  • 合理设置过期时间,例如带货直播场景中,该商品肯定是在直播期是热点数据,可以将该商品缓存时间设置为大于等于带货直播时长。

2.6 Redis缓存穿透问题怎么解决?

Redis缓存穿透——大量请求访问缓存,缓存中没有该数据,数据库中也没有该数据,就导致访问了一个空数据。这种一般是恶意攻击居多,很少发生这种情况,不过如果发生了,根据根因,解决办法有如下几种:

  • 设置分布式锁,降低DB和缓存访问水位,不过一般设置在缓存和DB之间
  • 限流,识别访问的IP信息,直接拉黑,避免该IP多次访问
  • 布隆过滤器,可以利用布隆过滤器判断访问的Key是否在缓存中,DB中也有布隆过滤器索引,如果布隆过滤器判断不存在,就直接fast-fail

2.7 Redis查询阻塞的遇到过吗?怎么解决?

Redis查询阻塞一般指网络IO阻塞,具体的原因有很多种,常见的有两种:

  1. Redis负载不足,例如一个Redis实例机器只能处理1000QPS,但是此刻QPS达到了1000以上,比如2000,那么此刻Redis查询就是被阻塞。针对这种情况,解决办法也很简单,要么扩容机器,要么IO限流。
  2. Redis在持久化的时候,会短暂的阻塞,比如RDB模式下,如果在fork主进程中,就会陷入阻塞。这种解决办法的话,就是配置合理的持久化参数,所有的持久化操作尽量在Slave机器上执行。

至于其他原因,基本上不是什么大问题

2.8 怎么提升Redis查询性能?

这个问题问的很泛,提升Redis查询性能有很多种手段,这个还是建议在回答的时候,仔细和面试官确定场景

提升Redis查询性能,可以从很多方面入手,但常见的两种手段如下:

  • 硬件方面:
    • 提升机器性能,即提升机器硬件,这样可以从物理层面直接提升Redis查询性能。
    • 采用分布式部署,Redis集群可以分摊查询压力,在高并发场景中,可以很好的提升性能。
    • 采用master-Slave模式,分摊查询压力,可以提升查询性能。
  • 软件方面:
    • 合理的设计数据结构,对于一些二象性(0或1,yes或no)数据,可以采用位图bitmap方式存储,提升查询性能。
    • 合理设置持久化策略,避免持久化长期阻塞Redis

2.9 Redis主备库数据怎么同步?

偏移量+写日志,先复制主库全部数据,然后记录一个偏移量。等待主库进行写操作时,监听主库的写日志,将写日志信息同步给从库,从而让从库同步更新数据。

2.10 Redis主备数据延迟会导致什么问题?

主备数据延迟主要值得是master同步Slave数据信息延迟,一般情况下采用master-Slave结构的服务器,主要是解决服务负载问题的。主备数据延迟一般会导致如下几个问题:

  • 数据不一致,这是表象现象,masterSlave的数据会发生不一致现象。
  • 降低系统可用性,由于主要是读取Slave数据,Slave数据可能不一致,或者缺失,导致查询时需要去数据库查询,或者直接失败(fast-fail)。这会降低系统可用性,影响系统整体性能,或者说影响业务。
  • 备份数据丢失,如果是Slave上备份数据,可能会丢失数据。

怎么解决数据延迟?

  • 优化网络环境:确保master和Slave之间的网络连接稳定且带宽充足,以减少网络传输延迟。
  • 合理配置Redis参数:通过调整Redis的复制相关参数(如repl-backlog-sizerepl-timeout等),优化复制过程的性能和稳定性。
  • 增加Slave数量:通过增加Slave的数量来分担单个Slave的复制压力,同时提高系统的读取性能和可用性。但需要注意的是,增加Slave也会增加系统的复杂性和维护成本。
  • 使用哨兵模式或集群模式:通过引入哨兵模式或集群模式来提高系统的高可用性和容错能力。这些模式可以自动检测和处理master的故障,并在必要时进行主从切换或数据重分布。

2.11 Redis怎么实现分布式锁?

分布式锁需要保证全局唯一,要求多个客户端同时加锁时,只有一个成功。一般实现锁的方式,主要有如下几种方式:

  • String+expire:利用Stringexpirek-v设置过期时间,多个客户端加锁时,直接读取k-v健即可。
  • 利用一些Redis框架,比如redlockRedisson等,加分布式锁。

3 Redis特殊数据类型

3.1 Bitmap 怎么存储数据?

位图bitmap不是Redis基本的数据类型,而是字符串数据类型的升级,主要存储的是二进制数据0和1。由于字符串最大可存储512M数据,所以位图bitmap也可以存储相同大小的数据。

3.2 Bitmap 应用场景?

位图主要是存储的一些0和1数据,对于一些数据统计,数据结果判断归档很有用。例如:用户签到、活跃统计等。

3.3 HyperLogLog 怎么存储数据?

HyperLogLog是一种用于估算非常大的集合的基数的概率算法,它不需要存储集合中的所有值,而是使用固定大小的数据结构来近似计算集合中不同元素的数量。HyperLogLog算法通过哈希函数和概率统计的方法来实现数据的概率计算。

3.4 HyperLogLog 应用场景?

主要是概率计算,数据统计等方面有用,常用的场景包括:博文数据统计、热门帖子访问量统计等

3.5 Geospatial 怎么存储数据?

Geospatial存储的是地理位置信息,地理位置可以由三个信息点组成,分别是经纬度+海拔高度,底层是由Sorted Set实现的。

3.6 Geospatial 应用场景?

Geospatial可以存储地理位置,对于一些游戏场景中很有用,可以搜索一定范围内GEO数据,即附近的人。还有其他地理图中,可以探索最近周围数据信息。

;