Redis面试题

x33g5p2x  于2022-02-16 转载在 Redis  
字(9.4k)|赞(0)|评价(0)|浏览(457)

Redis面试题

1.概述

1.1什么是redis

1、redis是一个高性能的key-value数据库
2、redis支持数据的持久化,可以将内存中的数据保存到磁盘中,重启的时候在再次加载进行使用
3、redis有丰富的数据类型,list,set,zset,hash,String
4、性能高
5、所有的操作都是原子性  
6、丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

1.2redis有哪些优缺点

优点:
- 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。

缺点:
- 数据库容量受到内存的限制,不适合作为海量数据的高性能读写
- redis不具备自动容错和恢复功能,当主机或者从机宕机,前端前端部分读写请求失败,需要手动重启才可以恢复
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。

1.3为什么要用redis/为什么要用缓存

- 高性能:加入用户第一次访问数据的数据,会进行IO操作,这个效率会很慢,当查询出来后,把数据放入缓存,这样第二次查询的时候,直接从缓存中获取,是直接操作内存的,减少了IO次数
- 高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库

1.4为什么要用redis,不用map做缓存

缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂

1.5redis为什么那么快

1、完全基于内存,绝大部分请求是纯粹的内存操作
2、数据结构简单,对数据操作也简单
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
4、使用多路 I/O 复用模型,非阻塞 IO

2.数据类型

2.1redis有哪些数据类型

- String  热点信息,短信验证码
- list    粉丝列表,消息队列
- set     集合性的数据
- hash    存放结构化数据, 商品详情,个人信息详情,新闻详情
- zset    排行榜应用(去重但可以排序,如获取排名前几名的用户)

2.2redis的应用场景

- 计数器:可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量

- 缓存:将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率

- 会话缓存:可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性

- 分布式锁实现:在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现

3.持久化

3.1什么是redis持久化

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失

3.2redis的持久化机制是什么?各自的优缺点

- Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制

- RDB:RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期
 -- 优点:
    1、只有一个文件 dump.rdb,方便持久化。
    2、容灾性好,一个文件可以保存到安全的磁盘。
    3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
    4.相对于数据集大时,比 AOF 的启动效率更高。
 -- 缺点:
    数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失
    
- AOF:AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复 
  -- 优点:
    1、数据安全
    2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题
    3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令
  -- 缺点:
    1、AOF 文件比 RDB 文件大,且恢复速度慢
    2、数据集大的时候,比 rdb 启动效率低

3.3redis持久化数据和缓存怎么做到扩容

如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容

4.过期键的删除策略

4.1Redis的过期键的删除策略

- 我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

- 过期策略通常有以下三种:
   1、定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
   2、惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
   3、定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
   
Redis中同时使用了惰性过期和定期过期两种过期策略

4.2我们知道通过expire来设置key 的过期时间,那么对过期的数据怎么处理呢?

- 除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
    1.定时去清理过期的缓存;

    2.当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

4.3Redis key的过期时间和永久有效分别怎么设置?

expore 和 persist

5.内存相关

5.1MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据

redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略

5.2Redis的内存淘汰策略有哪些

- Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

 -- 全局的键空间选择性移除
    1、noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
    2、allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
    3、allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
    
 -- 设置过期时间的键空间选择性移除
    1、volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
    2、volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
    3、volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

- 总结:Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据

5.3Redis的内存用完了会发生什么?

如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。

6.redis事务

6.1Redis事务的概念

Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令

6.2Redis事务的三个阶段

1、事务开始 MULTI
2、命令入队
3、事务执行 EXEC

事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队

6.3Redis事务相关命令

- Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
- Redis会将一个事务中的所有命令序列化,然后按顺序执行
    1、redis 不支持回滚
    2、如果在一个事务中的命令出现错误,那么所有的命令都不会执行
    3、如果在一个事务中出现运行错误,那么正确的命令会被执行

- WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
- MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
- UNWATCH命令可以取消watch对所有key的监控。

6.4Redis事务支持隔离性吗

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

6.5Redis事务保证原子性吗,支持回滚吗

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行

7.缓存异常

7.1缓存雪崩及解决办法

缓存雪崩:同一时间内大量的缓存过期,所有原本应该访问缓存的都去访问数据库了,对数据库造成很大的压力,从而引起一系列的连锁反应,造成系统崩溃
    
解决方法:
    1、加锁,或者队列的方式保证不会有大量的线程对数据库进行一次性读写
    2、把缓存失效的时间分开
    3、给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存

7.2缓存穿透及解决方法

缓存穿透:缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
    
解决方法:
    1、采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
    2、如果查询返回的数据为空,我们仍然把这个空结果进行缓存,当但它的过期时间很短,通过这个直接设置的默认值存放到缓存,这样第二次缓存中就获取就有值了,不会在访问数据库

7.3缓存击穿及解决方法

缓存击穿:指缓存中没有可是数据库中有的数据,这个时候由于并发用户特别多,同时读缓存没有读到数据,又同时去数据库中查数据,引起数据库压力增大和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方法:
	1、设置热点信息永不过期
	2、加互斥锁

7.4缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据

解决方法:
	1、直接写个缓存刷新页面,上线时手工操作一下;
	2、数据量不大,可以在项目启动的时候自动进行加载;
	3、定时刷新缓存;

7.5缓存降级

- 当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

- 服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

8.分布式问题

8.1redis实现分布式锁

redis是单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对redis的连接不存在竞争关系,redis中可以使用setnx命令实现分布式锁。

当且仅当key不存在,将setnx的值设置为value,若给定的key已经存在,则setnx不做任何动作。SETNX是(SET IF NOT EXISTS)的简写,如果设置成功返回1,设置失败返回0

使用SETNX完成同步锁的流程
	1.使用SETNX命令获取锁,若返回为0(key 已经存在,锁已经存在),获取锁失败,反之成功
	2.为了防止获取锁后程序出现问题,导致其他线程调用SETNX命令一直放回0而进入死锁状态,需要给这个值设置一个“合理”的过期时间
	3.释放锁,使用DEL命令将锁数据删除

8.2如何解决 Redis 的并发竞争 Key 问题

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同

方案:
	1.分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
	2.基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁

8.3什么是 RedLock

Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

	1.安全特性:互斥访问,即永远只有一个 client 能拿到锁
	2.避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
	3.容错性:只要大部分 Redis 节点存活就可以正常提供服务

9.集群方案

9.1主从模式

单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
9.1.1主从复制原理
1.当从库(slave)和主库(master)建立MS关系后,会向主数据库发送SYNC命令
2.主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
3.当快照完成后,主Redis(master)会将快照文件和所有缓存的写命令发送给从Redis(slave)
4.从Redis(slave)接收到后,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据
5.之后,主Redis(master)每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致

slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。(第一次连接是全量复制,再次连接是部分数据复制)

所有的slave节点数据的复制和同步都由master节点来处理,会照成master节点压力太大,使用主从从结构来解决

9.2 Sentinel模式(哨兵模式)

集群监控:负责监控 redis master 和 slave 进程是否正常工作。 
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。 
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。 
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

哨兵的核心知识:
	1.哨兵至少需要 3 个实例,来保证自己的健壮性
	2.哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性
9.2.1一致性hash算法

场景描述

场景描述:
 假设,公司有三台缓存服务器,用于缓存图片,这三台缓存服务器的编号为0号,1号,2号,现在有三万张图片需要缓存,希望这么多的缓存数据能均匀的缓存到3台服务请求上去(也就是说每台机器上平均分配1万左右的数据),以便能够分摊缓存的压力。不好的做法:
 hash(图片名称)% N (取模)--->对服务器数量进行取模因为图片名称是唯一的,所以hash运算结果也是唯一的;
 弊端:
 1.如果增加一台服务器,那么所有的缓存位置都将发生改变,也意味着一定时间内,所有缓存失效,造成缓存雪崩;
 2.如果一台服务器宕机,效果一样;

一致性hash算法

一致性hash算法:
一致性hash是对2^32取模;—-> hash(服务器的IP地址) % 2^32

 为什么对2^32取模?
     对2^32取余是有依据的,IPV4最大数量就是2^32,所以这样就能保证所有的ip的地址进行取余时不会重复—对应hash环上面的正数。
通过一致性hash算法,判断一个对象应该被缓存到哪台服务器上,同时将缓存服务器与被缓存对象都映射到hash环上以后,从被缓存对象的位置出发,沿着顺时针方向遇到的第一个服务器,就是当前对象将要缓存的服务器,由于被缓存对象与服务器hash后的值都是固定的,所以服务器不变的情况下,一张图片必定会被缓存到固定的服务器上,那么,当下次访问这张图片时,只要再次使用相同的算法进行计算,即可算出这张图片被缓存在那个服务器上,直接去对应的服务器上查找即可;

这是理想情况,存在hash环偏斜;
为了解决hash环偏斜 , 引入虚拟节点;
“虚拟节点”是“实际节点”(实际的物理服务器)在hash环上的复制品,一个实际节点可以对应多个虚拟节点。

9.3 Cluster 模式

映射

Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内计算公式:slot = CRC16(key) & 16383,每一个节点负责维护一部分槽以及槽所映射的键值数据。(默认16384个槽点位)

架构

1.所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽. 
2.节点的fail是通过集群中超过半数的节点检测失效时才生效. 
3.客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可 
4.redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value之间的关系,解耦了节点和数据;

选举容错

选举过程是集群中所有master参与,如果半数以上master节点与某个master节点通信超时(cluster-node-timeout),认为该master节点挂掉.什么时候整个集群不可用(cluster_state:fail):

1) 如果集群任意master挂掉,且当前master没有slave,集群进入fail状态
2) 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态

当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误。

9.4为什么redis集群至少需要三个节点

因为一个节点挂了,有一半以上的节点通过ping-pong方式的投票机制认为它挂了,那么这个集群就挂了,如果只有俩个节点,有一个挂了,另一个确定他挂了但是没有超过百分之五十,这个集群也不会挂。

9.5为什么每一个集群需要至少6台服务器

因为redis集群至少需要三个节点,要保证集群的高可用,每一个节点都要有一个备份机,理论上也需要6台虚拟机

10.其他问题

10.1一个字符串类型的值能存储最大容量是多少?

512M

10.2使用Redis做过异步队列吗,是如何实现的

使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

10.3Redis如何实现延时队列

使用sortedset,使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理

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

读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求

先更新数据库,然后再更新缓存。

相关文章

最新文章

更多

目录