Redis面试题总结

参考
https://blog.csdn.net/zdp072/article/details/50991116
https://blog.csdn.net/qq_34337272/article/details/80012284#commentBox

坑人无数的Redis面试题(未整理,写的很棒,超出了理论范围,结合实际中的问题,给出不错的解决方案) https://blog.csdn.net/u011405515/article/details/79190652

0 什么是Redis?

  1. Redis 是一个使用 C 语言写成的,开源的,基于内存的 key-value 数据库。
  2. 支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。
  3. 与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。目前,Vmware在资助着redis项目的开发和维护。

1 使用redis有哪些好处?

  1. 速度快:因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1),每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
  2. 支持丰富数据类型:支持string,list,set,sorted set,hash
  3. 单个value的最大限制是1GB,不像 memcached只能保存1MB的数据
  4. 支持事务:操作不是原子性的,即使某些操作失败,也不会影响别的操作的执行
  5. 丰富的特性:可用于缓存,按key设置过期时间,过期后将会自动删除

2 redis相比memcached有哪些优势?

  1. memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  2. redis的速度比memcached快很多
  3. redis可以持久化其数据,也可以做数据备份
  4. 单个value的最大限制是1GB,不像 memcached只能保存1MB的数据

3 Redis与Memcached的区别与比较

Memcached:一款完全开源、高性能的、分布式的内存系统

Redis:一个开源的、Key-Value型、基于内存运行并支持持久化的NoSQL数据库

Memcached追求的高性能的内存服务;而Redis追求的不仅仅是内存运行,还有数据持久化的需求。

  1. 存储方式
    Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
    Redis有部份存在硬盘上,这样能保证数据的持久性(即master-slave模式的数据备份)。

  2. 数据支持类型
    Memcache对数据类型支持相对简单String。
    Redis有复杂的数据类型:String,list,set,zset,hash。

  3. value大小
    redis的value大小最大可以达到1GB,而memcache只有1MB

  4. Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。
    网络IO之间的对比:

    Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程worker子线程,监听线程监听网络连接,接受请求后,将连接描述字pipe传递给worker线程,进行读写IO,网络层使用libevent封装的事件库,多线程模型可以发挥多核作用,但是引入了cache coherency和锁的问题,比如:memcached最常用的stats命令,实际memcached所有操作都要对这个全局变量加锁,进行技术等工作,带来了性能损耗。

    Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll, kqueue和select,对于单存只有IO操作来说,单线程可以将速度优势发挥到最大,但是redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型施加会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞的。

    redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

    基于以上的内容大致可以总结如下:
    在高并发场景的压力下,多线程非阻塞式IO的Memcached表现会更加优异。

  5. 过期删除策略的不同
    memcached只是用了惰性删除,而redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点

    详情见 Redis过期策略 实现原理

4 redis常见性能问题和解决方案:

  1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

  2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次(AOF有3种同步方式:1.每秒同步,2.每修改同步,3.不同步)

  3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

  4. 尽量避免在压力很大的主库上增加从库

  5. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

    这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

5 Redis与Memcached的选择

  1. 如果大家对持久化要求不高,更希望快速高效,建议大家优先考虑Memcached;
  2. 追求多类型支撑,持久化要求相对比较高的情况下,优先使用Redis;
  3. 使用Redis的String类型做的事,都可以用Memcached替换,以此换取更好的性能提升; 除此以外,优先考虑Redis;

6 Redis常见数据结构使用场景

6.1 String

常用命令: set,get,decr,incr,mget 等。
  • String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字
  • 常规key-value缓存应用;
  • 计数功能的缓存:微博数,粉丝数等。

6.2 Hash

常用命令: hget,hset,hgetall 等。

Hash是一个string类型的fieldvalue(的映射表,hash特别适合用于存储对象。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。

  • 在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果

6.3 List

常用命令: lpush,rpush,lpop,rpop,lrange等

list就是链表,Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,最新消息排行等功能都可以用Redis的list结构来实现。

Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销

使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。

6.4 Set

常用命令:
sadd,spop,smembers,sunion 等

set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的(没有重复的数据)。

可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。

另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

6.5 Sorted Set

常用命令: zadd,zrange,zrem,zcard等

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列

举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用Redis中的SortedSet结构进行存储。

可以做排行榜应用取TOP N操作。最后一个应用就是可以做范围查找

7 MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据(redis有哪些数据淘汰策略???)

相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。redis 提供 6种数据淘汰策略:

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(驱逐):禁止驱逐数据

8 Redis的并发竞争问题如何解决?

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:

  1. 客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化(是什么意思?不懂),同时对客户端读写Redis操作采用内部锁synchronized。
  2. 服务器角度,利用setnx实现锁。

注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。
 
 # 9 Redis回收进程如何工作的? Redis回收使用的是什么算法?

Redis内存回收:LRU算法(有源码分析,我还没看,推荐):https://www.cnblogs.com/WJ5888/p/4371647.html

Redis中采用两种算法进行内存回收,引用计数算法以及LRU算法

LRU算法采用的数据结构是HashMap+Double LinkedList,时间复杂度为O(1)。其含义是:当内存容量不足时,为了保证程序的运行,就要淘汰一些数据,选择最近一段时间内,最久未使用的对象将其淘汰,至于为什么要选择最久未使用的,可以想想,最近一段时间内使用的东西,我们是不是可能一会又要用到呢,而很长一段时间内都没有使用过的东西,也许永远都不会再使用~淘汰的不是内存中的对象,而是页选择一页(一般是4KB)将其交换到虚拟内存区(Swap区)。

9 Redis 大量数据插入

使用管道(pipelining)

10 Redis 分区的优势、不足以及分区类型

https://blog.csdn.net/csdnlijingran/article/details/88032979

11 Redis持久化数据和缓存怎么做扩容?

《redis的持久化和缓存机制》 :https://blog.csdn.net/tr1912/article/details/70197085?foxhandler=RssReadRenderProcessHandler
扩容即redis集群:https://blog.csdn.net/csdnlijingran/article/details/88065336

12 Redis与消息队列

不要使用redis去做消息队列,这不是redis的设计目标。做网站过程接触比较多的还是使用redis做缓存,比如秒杀系统首页缓存等等。

13 为什么redis需要把所有数据放到内存中?

Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。

14 redis 最适合的场景

  1. 会话缓存(Session Cache)
    最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?

  2. 全页缓存
    除基本的会话token之外,Redis还提供很简便的全页缓存功能。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降。

    再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。

    此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

  3. 队列
    Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。

  4. 排行榜/计数器
    Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:

    当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:

    ZRANGE user_scores 0 10 WITHSCORES

  5. 发布/订阅
    最后是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。用Redis的发布/订阅功能来建立聊天系统!

    Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
Redis面试题总结_第1张图片

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
Redis面试题总结_第2张图片

15 高可用分布式集群

15.1 高可用

高可用(High Availability),是当一台服务器停止服务后,对于业务及用户毫无影响。 停止服务的原因可能由于网卡、路由器、机房、CPU负载过高、内存溢出、自然灾害等不可预期的原因导致,在很多时候也称单点问题

15.1.1 解决单点问题主要有2种方式

  1. 主备方式
    这种通常是一台主机、一台或多台备机,在正常情况下主机对外提供服务,并把数据同步到备机,当主机宕机后,备机立刻开始服务。
    Redis HA中使用比较多的是keepalived,它使主机备机对外提供同一个虚拟IP,客户端通过虚拟IP进行数据操作,正常期间主机一直对外提供服务,宕机后VIP自动漂移到备机上。

    优点是对客户端毫无影响,仍然通过VIP操作。

    缺点是在绝大多数时间内备机是一直没使用,被浪费着的。

  2. 主从方式
    这种采取一主多从的办法,主从之间进行数据同步。 当Master宕机后,通过选举算法(Paxos、Raft)从slave中选举出新Master继续对外提供服务,主机恢复后以slave的身份重新加入。

    主从另一个目的是进行读写分离,这是当单机读写压力过高的一种通用型解决方案。 其主机的角色只提供写操作或少量的读,把多余读请求通过负载均衡算法分流到单个或多个slave服务器上。

    缺点是主机宕机后,Slave虽然被选举成新Master了,但对外提供的IP服务地址却发生变化了,意味着会影响到客户端。 解决这种情况需要一些额外的工作,在当主机地址发生变化后及时通知到客户端,客户端收到新地址后,使用新地址继续发送新请求。

15.1.2 数据同步方式2种

无论是主备还是主从都牵扯到数据同步的问题,这也分2种情况:

  1. 同步方式:当主机收到客户端写操作后,以同步方式把数据同步到从机上,当从机也成功写入后,主机才返回给客户端成功,优点也称数据强一致性。缺点 很显然这种方式性能会降低不少。解决办法当从机很多时,可以不用每台都同步,主机同步某一台从机后,从机再把数据分发同步到其他从机上,这样提高主机性能分担同步压力。 在redis中是支持这种配置的,一台master,一台slave,同时这台slave又作为其他slave的master。

  2. 异步方式:主机接收到写操作后,直接返回成功,然后在后台用异步方式把数据同步到从机上。 优点这种同步性能比较好,缺点 但无法保证数据的完整性,比如在异步同步过程中主机突然宕机了,也称这种方式为数据弱一致性

Redis主从同步采用的是异步方式,因此会有少量丢数据的危险。

15.1.3 方案选择

主备方式keepalived方案配置简单、人力成本小,在数据量少、压力小的情况下推荐使用。 如果数据量比较大,不希望过多浪费机器,还希望在宕机后,做一些自定义的措施,比如报警、记日志、数据迁移等操作,推荐使用主从方式,因为和主从搭配的一般还有个管理监控中心。

宕机通知这块,可以集成到客户端组件上,也可单独抽离出来。 Redis官方Sentinel支持故障自动转移、通知等,详情见低成本高可用方案设计(四)。

15.2 分布式

分布式(distributed), 是当业务量、数据量增加时,可以通过任意增加减少服务器数量来解决问题。

集群时代,至少部署两台Redis服务器构成一个小的集群,主要有2个目的:

  1. 高可用性:在主机挂掉后,自动故障转移,使前端服务对用户无影响。
  2. 读写分离:将主机读压力分流到从机上。可在客户端组件上实现负载均衡,根据不同服务器的运行情况,分担不同比例的读请求压力。

15.3 分布式集群时代

当缓存数据量不断增加时,单机内存不够使用,需要把数据切分不同部分,分布到多台服务器上。
可在客户端对数据进行分片,数据分片算法一致性Hash。
Redis面试题总结_第3张图片大规模分布式集群时代

当数据量持续增加时,应用可根据不同场景下的业务申请对应的分布式集群。 这块最关键的是缓存治理这块,其中最重要的部分是加入了代理服务。 应用通过代理访问真实的Redis服务器进行读写,这样做的好处是:

  1. 避免越来越多的客户端直接访问Redis服务器难以管理,而造成风险。
  2. 在代理这一层可以做对应的安全措施,比如限流、授权、分片。
  3. 避免客户端越来越多的逻辑代码,不但臃肿升级还比较麻烦。
  4. 代理这层无状态的,可任意扩展节点,对于客户端来说,访问代理跟访问单机Redis一样。

目前楼主公司使用的是客户端组件和代理两种方案并存,因为通过代理会影响一定的性能。 代理这块对应的方案实现有Twitter的Twemproxy和豌豆荚的codis。
Redis面试题总结_第4张图片

16 reids内存管理

最大内存设置
默认情况下,在32位OS中,Redis最大使用3GB的内存,在64位OS中则没有限制。

在使用Redis时,应该对数据占用的最大空间有一个基本准确的预估并为Redis设定最大使用的内存。否则在64位OS中Redis会无限制地占用内存(当物理内存被占满后会使用swap空间),容易引发各种各样的问题。

通过如下配置控制Redis使用的最大内存:

maxmemory 100mb

在内存占用达到了maxmemory后,再向Redis写入数据时,Redis会:

  • 根据配置的数据淘汰策略尝试淘汰数据,释放空间

  • 如果没有数据可以淘汰或者没有配置数据淘汰策略,那么Redis会对所有写请求返回错误但读请求仍然可以正常执行

在为Redis设置maxmemory时,需要注意:
如果采用了Redis的主从同步,主节点向从节点同步数据时会占用掉一部分内存空间如果maxmemory过于接近主机的可用内存,就会导致数据同步时内存不足。所以设置的maxmemory不要过于接近主机可用的内存,留出一部分预留用作主从同步。

17 redis事务

没有整理
https://redisbook.readthedocs.io/en/latest/feature/transaction.html

Redis 的事务保证了 ACID 中的一致性(C)和隔离性(I),但并不保证原子性(A)和持久性(D)。

1 单个 Redis 命令的执行是原子性的但是 Redis 事务的执行并不是原子性的

  • 如果一个事务队列中的所有命令都被成功地执行,那么称这个事务执行成功

  • 另一方面,如果 Redis 服务器进程在执行事务的过程中被停止 —— 比如接到 KILL 信号、宿主机器停机,等等,那么事务执行失败
    当事务失败时,Redis 也不会进行任何的重试或者回滚动作

2 一致性(Consistency)

Redis 的一致性问题可以分为三部分来讨论:入队错误执行错误Redis 进程被终结

(1)入队错误
在命令入队的过程中,如果客户端向服务器发送了错误的命令,当客户端执行 EXEC 命令时, Redis 会拒绝执行, 并返回失败信息。因此,带有不正确入队命令的事务不会被执行,也不会影响数据库的一致性

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command

redis 127.0.0.1:6379> EXISTS key
QUEUED

redis 127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

(2)执行错误
如果命令在事务执行的过程中发生错误,比如说,对一个不同类型的 key 执行了错误的操作, 那么 Redis 只会将错误包含在事务的结果中, 这不会引起事务中断或整个失败,不会影响已执行事务命令的结果,也不会影响后面要执行的事务命令, 所以它对事务的一致性也没有影响。

(3)Redis 进程被终结
如果 Redis 服务器进程在执行事务的过程中被其他进程终结,或者被管理员强制杀死,那么根据 Redis 所使用的持久化模式,可能有以下情况出现:

★ 内存模式:如果 Redis 没有采取任何持久化机制,那么重启之后的数据库总是空白的,所以数据总是一致的。

★ RDB 模式:Redis 只有在事务执行之后,保存 RDB 的工作才有可能开始。所以当 RDB 模式下的 Redis 服务器进程在事务中途被杀死时,事务内执行的命令,不管成功了多少,都不会被保存到 RDB 文件里。恢复数据库需要使用现有的 RDB 文件,而这个 RDB 文件的数据保存的是最近一次的数据库快照(snapshot),所以它的数据可能不是最新的,但只要 RDB 文件本身没有因为其他问题而出错,那么还原后的数据库就是一致的。

★ AOF 模式:因为保存 AOF 文件的工作在后台线程进行所以即使是在事务执行的中途,保存 AOF 文件的工作也可以继续进行,因此,根据事务语句是否被写入并保存到 AOF 文件,有以下两种情况发生

1)如果事务语句未写入到 AOF 文件,或 AOF 未被 SYNC 调用保存到磁盘,那么当进程被杀死之后,Redis 可以根据最近一次成功保存到磁盘的 AOF 文件来还原数据库,只要 AOF 文件本身没有因为其他问题而出错,那么还原后的数据库总是一致的,但其中的数据不一定是最新的

2)如果事务的部分语句被写入到 AOF 文件,并且 AOF 文件被成功保存,那么不完整的事务执行信息就会遗留在 AOF 文件里,当重启 Redis 时,程序会检测到 AOF 文件并不完整,Redis 会退出,并报告错误。需要使用 redis-check-aof 工具将部分成功的事务命令移除之后,才能再次启动服务器。还原之后的数据总是一致的,而且数据也是最新的(直到事务执行之前为止)。

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

4 持久性(Durability)
因为事务不过是用队列包裹起了一组 Redis 命令并没有提供任何额外的持久性功能所以事务的持久性由 Redis 所使用的持久化模式决定:

(1)在单纯的内存模式下,事务肯定是不持久的。

(2)RDB 模式下的 Redis 事务也是不持久的。

(3)在 AOF 的“总是 SYNC ”模式下,事务的每条命令在执行成功之后,都会立即调用 fsync 或 fdatasync 将事务数据写入到 AOF 文件。但是,这种保存是由后台线程进行的主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。

其他 AOF 模式也和“总是 SYNC ”模式类似,所以它们都是不持久的。

18 Redis的rehash过程

过程是:RedisServer结构体中(Redis使用C语言完成)有一个Dictionary数组,默认容量大小是16, 每个 Dictionary结构体中有三个Dict结构体指针,Dict其实就是一个KV结构类似于HashMap其中两个作为数据存储的库,一个作为当前使用,一个作为备用rehash;另外一个Dict存储键的过期时间Redis的rehash过程其实就是将上述的16个Dictionary进行rehash,Redis内部支持一个周期循环运行的ctro函数,这个函数定期检查上述的16个Dictionary是否需要rehash,如果有需要,ctro会完成Dictionary的rehash过程,周期结束后并且记录当前循环到Dictionary数组中的哪一个角标方便再次循环时从上一次循环停止的地方开始

其中还有一点,Redis的rehash是延迟性的每次put会将新元素加入到新的表中,每次查询时,首先会在新的表中查找如果没有在旧的表中查找查找到的话将这个元素插入到新的表中(这个过程也会涉及到检查元素是否过期的问题)

你可能感兴趣的:(缓存服务器(redis))