2022最新redis 面试八股文


  • 1)数据结构

1.1)string->int编码、raw编码、embstr编码

1.1.1)应用场景:计数

1.1.2)SDS

1.1.2.1)获取字符串长度的时间复杂度为O(1)

1.1.2.2)减少修改字符串时带来的内存重分配次数,因为SDS会预留一部分空间,来预防字符串修改

1.2) List ->linkedlist ziplist,

1.2.1)默认linkedlist,ziplist:所有字符串长度都小于64字节,二是元素数量小于512

1.2.2)应用场景:1、最新消息排行等功能(比如朋友圈的时间线) 2、消息队列

1.3)hash ->ziplist,hash

1.3.1)应用场景:购物车

1.4)set ->intset(数组),hashtable

1.4.1)集合对象使用intset编码需要满足两个条件:一是所有元素都是整数值;二是元素个数小于等于512个;

1.4.2)应用场景:

1、共同好友sinter,好友推荐时,根据tag求交集,大于某个阈值就可以推荐

2、利用唯一性,统计访问网站的所有独立ip

3、经常有业务出于安全性方面的考虑,需要设置用户黑名单、ip黑名单、设备黑名单等,set类型适合存储这些黑名单数据,sismember命令可用于判断用户、ip、设备是否处于黑名单之中。

1.5)Zset ->ziplist,skiplist

1.5.1)排行榜,取TopN操作 2、带权重的消息队列,3限流(滑动窗口)

1.5.2)zset包括 dict和zskiplist两个数据结构,其中dict的保存key/value,便于通过key(元素)获取score(分值)。zskiplist保存有序的元素列表,便于执行range之类的命令。

1.6)skiplist:一种有序数据结构,它通过在某个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

1.6.1)有序集合对象使用ziplist编码需要满足两个条件:一是所有元素长度小于64字节;二是元素个数小于128个;不满足任意一条件将使用skiplist编码

https://www.cnblogs.com/MouseDong/p/11134039.html

  • 2)高可用

2.1)持久化

2.1.1)AOF

2.1.1.1)优点

      • 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
          • 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
          • AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)

2.1.1.2) 缺点

    • AOF 文件比 RDB 文件大,且恢复速度慢。
          • 数据集大的时候,比 RDB 启动效率低。

2.1.2)RDB

2.1.2.1)优点

          • 只有一个文件 dump.rdb,方便持久化。
          • 容灾性好,一个文件可以保存到安全的磁盘。
          • 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis的高性能)
          • 相对于数据集大时,比 AOF 的启动效率更高。

2.1.2.2)缺点

数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。

2.1.3)全量复制:

slave初次连接master,master把runid,offset发给slave,并且bgsave生成rdb文件,中间的写命令放入缓冲区

bgsave完事后,把rdb文件发给slave,slave完事后然后发送缓冲区中的写命令

slave接收到rdb文件,然后抛弃原有的数据,加载rdb文件,完事后,执行缓冲区的写命令

此后master每写完一个命令,就会向slave发送一个写命令

2.1.4)增量复制:

如果中间出现闪断或者重连情况,恢复后,slave向master发送runid,offset

主节点检查runid与自身是否一致,如果没问题,判断offset是否在缓冲区,如果在增量,不在执行全量复制

2.1.5)同步故障问题:

rdb太大,导致全量超时,调大repl-timeout

积压缓冲区太多,主节点将直接关闭复制客户端连接,导致全量失败。调整:client-output-buffer-limit slave

slave全量复制时,对于无法容忍不一致的应用场景可以设置slave-server-stale-data no来关闭命令执行

2.2)主从

2.3)哨兵

2.3.1)sentinel状态持久化,可以安全的停止和重启sentinel进程。

2.3.2)sentinel工作方式:

2.3.2.1)sentinel每一秒向master,slave,其他sentinel发送Ping命令

2.3.2.2)如果一个实例在距上次ping回复超过了own-after-milliseconds,则认为该实例主观下线

2.3.2.3)如果是master被标记为主观下线,则监视该master的所有sentinel以每秒一次的频率确认master是否主观下线

2.3.2.4)如果有一定数量的sentinel在一定时间内确认master已经下线,则master被标记为客观下线

2.3.2.5)一般情况下,sentinel每10秒一次向master,slave发送Info命令

2.3.2.6)如果master被标记为客观下线,sentinel改为1秒一次的频率向master的slave实例发送INFO命令

2.3.2.7)如果没有足够的sentinel确认master主观下线或者master重新向sentinel发送有效PING回复,则主观下线被移除

2.3.3)三个定时任务

2.3.3.1)PING命令:心跳检测,是失败判定的依据

2.3.3.2)INFO命令:发现slave节点,确认主从

2.3.3.3)channel交换信息(pub/sub):与master进行信息交换(对节点的"看法"和自身的信息)

2.3.4) 为什么要选领导者?

简单来说,就是因为只能有一个sentinel节点去完成故障转移。

sentinel is-master-down-by-addr这个命令有两个作用,一是确认下线判定,二是进行领导者选举。

选举过程:

1)每个做主观下线的sentinel节点向其他sentinel节点发送上面那条命令,要求将它设置为领导者。

2)收到命令的sentinel节点如果还没有同意过其他的sentinel发送的命令(还未投过票),那么就会同意,否则拒绝。

3)如果该sentinel节点发现自己的票数已经过半且达到了quorum的值,就会成为领导者

4)如果这个过程出现多个sentinel成为领导者,则会等待一段时间重新选举。

2.4)集群

2.4.1)设计:Redis-cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点和其他所有节点连接

2.4.2)结构特点

2.4.2.1)所有节点彼此互联,内部使用二进制协议优化传输熟读和贷款

2.4.2.2)节点的fail通过集群中超过半数的节点检测失效时才生效

2.4.2.3)客户端与redis直连,不需要中间层,也不需要连接所有节点,而是连接任意节点就行

2.4.2.4)redis-cluster把所有的物理节点映射到[0-16383]slot(插槽)上,cluster 负责维护node<->slot<->value

2.4.2.5)redis集群预分配16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。

2.4.3)新增节点分配

2.4.3.1)从其他节点拿出适量的hash槽分配到目标节点

2.4.3.2)在指定节点拿出适量的hash槽分配给目标节点

2.4.4)redis cluster主从模式

redis cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉

  • 3)过期策略 惰性删除+定期删除

3.1)定时删除 消耗内存

3.2)惰性删除 消耗空间

3.3)定期删除 检查删除都是随机的

4)淘汰机制 LRU 最近最少使用

设置过期时间LRU

设置过期时间即将过期

设置过期时间-随机

所有-LRU

所有-随机

淘汰的数据的大小其实是和置换的大小来确定的,如果置换的数据量大,淘汰的肯定也多

  • 5)多路复用
  • 6)分布式锁

6.1)setNX+getset过期时间,并且比较过期时间

缺点:A获取锁,然后redis master宕机,主从切换,可能会导致多个线程获取到同一个锁

6.2)RedLock:多个redis实例同时加锁,只要多数redis节点(一半以上)在使用,client就可以获取和释放锁

缺点:对于性能要求高的业务,实在太重太复杂,

对于完全正确的则不安全(ABCDE,ABC,CDE,C时钟前移,前一个线程锁c上过期,两个线程都能获取锁)

6.3)看门狗:自动续期,每30秒续期一次

  • 7)应用场景

7.1)限流

7.1.1)setNx

7.1.2) zset range 滑动窗口 比较两个时间戳内的请求数量

7.1.3)令牌桶 list

定时push 然后lpop

问题:空轮训--->blpop,需要捕获异常,重试

令牌桶可以用来保护自己,主要用来对调用者频率进行限流,为的是让自己不被打垮,应对流量突发

7.1.4)漏斗算法Redis Cell cl.throttle

如果保证别人的系统不被打垮,用漏桶算法

7.2)Geohash算法

将经纬度编码,将二维变一维,给地址位置分区的一种算法。

7.3)bitmap

7.3.1)用户签到 SETBIT key offset value

7.3.2)统计活跃用户 BITOP operation destkey key

7.3.3)用户在线状态

7.3.4)优势

7.3.4.1)占用空间小,基于bit实现的

7.3.4.2)设置时候时间复杂度O(1)、读取时候时间复杂度O(n),操作是非常快的。

7.3.4.3)二进制数据的存储,进行相关计算的时候非常快

7.3.4.4)方便扩容

7.4)HyperLogLog 基数计数-使用存储少,统计结果并不是完全精确,存在一定误差,采用分桶机制,从局部看全部

7.4.1)统计用户访问量

pfadd key 123 ,pfcount key 统计该key去重后的元素个数

pfmerge key1 key2 //将两个key合并,用于统计两个网站访问量的总大小

7.5)布隆过滤器:巧妙使用hash算法和bitmap位存储的方式,极大的节约空间

可以判断某个数据一定不存在,但是无法判断一定存在。

通过 bitmap 这种数据结构实现 Redisson guava

减少磁盘 IO 或者网络请求

布隆过滤器除了一个位数组,还有 K 个哈希函数。当一个元素加入布隆过滤器中的时候,会进行如下操作: •使用 K 个哈希函数对元素值进行 K 次计算,得到 K 个哈希值。 •根据得到的哈希值,在位数组中把对应下标的值置为 1。 举个例子,假设布隆过滤器有 3 个哈希函数:f1, f2, f3 和一个位数组 arr。现在要把 2333 插入布隆过滤器中: •对值进行三次哈希计算,得到三个值 n1, n2, n3。 •把位数组中三个元素 arr[n1], arr[n2], arr[3] 都置为 1。 当要判断一个值是否在布隆过滤器中,对元素进行三次哈希计算,得到值之后判断位数组中的每个元素是否都为 1, 如果值都为 1,那么说明这个值可能在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

7.5.1)推荐系统给用户推荐新闻,避免重复推送。

  • 8)常见问题

8.1)缓存击穿

8.1.1)存在的key,在过期时刻,大量请求打到DB上,导致DB压力增大

8.1.2)解决方案:1)设置key不过期,2)加互斥锁,同一时间只有一个去请求db,其他在等待,3)get的时候,如果还差30秒过期,设置标志位,开一个线程续期 4)多级缓存,l1时间短,l2时间长,l1过期,一个线程去DB获取,其他线程访问l2

8.2)缓存穿透

8.2.1)不存在的key,大量访问,导致DB压力增大

8.2.2)解决方案:1)布隆过滤器,2)如果取不到,设置缓存为null,较短过期时间

8.3)缓存雪崩

8.3.1)大量Key同时过期,导致db崩掉

8.3.2)解决方案:1)缓存过期时间设置随机,2)热点数据永远不过期,3)如果是分布式,将热点数据均匀分布缓存数据库中

8.4)双写一致性

8.4.1)延时双删 先删除缓存->写数据库->过一秒再删除缓存

8.4.2)订阅binlog,同步到缓存,如果删除缓存失败,消息发送到消息队列,重试操作直到成功

8.5)热key

8.5.1)缓存不失效

8.5.2)多级缓存

8.5.3)布隆过滤器

8.5.4)读写分离

8.6)大key,字符串最大可存512M

8.6.1) bigkey命令(redis-cli --bigkeys),找到就干掉

如何解决大key:Mset 命令用于同时设置一个或多个 key-value 对,分散到各个实例中

8.6.2)Redis 4.0引入了memory usage命令和lazyfree机制,Lazyfree逻辑删除,删除操作交给BIO

8.7)集群(主从)脑裂解决方案:

min-slaves-to-write 3

min-slaves-max-lag 10

第一个参数表示连接master的slave节点数量,第二个参数表示slave连接到master的最大延迟时间

8.8)redis快的原因

8.8.1)基于内存实现

8.8.2)高效的数据结构

8.8.3)合理的编码结构

8.8.4)合适的线程模型

单线程处理读写

IO 多路复用处理IO,因为原来版本的redis瓶颈也在IO上

9)备用方案

9.1)主从+哨兵+cluster

9.2)ecache+Hystrix+降级+熔断+隔离

9.3)持久化

10)redis事务

redis通过MULT,EXEC,WATCH命令来实现事务,事务执行过程中,按照一定的顺序执行,并且事务执行过程中,不会中断,不会执行其他请求

watch机制基于CAS来实现的,被监视的key放在链表中,如果某个key被修改,那么REDIS_DIRTY_CAS标志将会被打开,这时服务器会拒绝执行事务。

11)hash扩容

11.1)redis中的hash扩容渐进式rehash过程

11.2)redis字典(hash表)底层有两个数组,还有一个rehashidx用来控制rehash,每次的增删改查,rehashidx+1,然后执行对应原hash表rehashidx索引位置的rehash


2022最新redis 面试八股文


11.3)单线程渐进式hash过程

在扩容和收缩的时候,如果哈希字典中有很多元素,一次性将这些键全部rehash到ht[1]的话,可能会导致服务器在一段时间内停止服务。所以,采用渐进式rehash的方式,详细步骤如下:

11.3.1)为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表,ht[1]长度为ht[0]的两倍。

11.3.2)将rehashindex的值设置为0,表示rehash工作正式开始

11.3.3)在rehash期间,每次对字典执行增删改查操作是,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashindex索引上的所有键值对rehash到ht[1],当rehash工作完成以后,rehashindex的值+1

11.3.4)随着字典操作的不断执行,最终会 在某一时间段上ht[0]的所有键值对都会被rehash到ht[1],这时将rehashindex的值设置为-1,表示rehash操作结束

将rehash的操作分摊在每一个的访问中,避免集中式rehash而带来的庞大计算量

11.4)

11.4.1)ConcurrentHashMap采用的扩容策略为: “多线程协同式rehash“。

扩容时大致过程如下:

线程A在扩容把数据从oldTable搬到到newTable,这时其他线程

进行get操作:这个线程知道数据存放在oldTable或是newTable中,直接取即可。

进行写操作:如果要写的桶位,已经被线程A搬运到了newTable。

那么这个线程知道正在扩容,它也一起帮着扩容,扩容完成后才进行put操作。

进行删除操作:与写一致。

12)大热Key优化方案

将整存整取的大对象,分拆为多个小对象。可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;

13)Redis事务

13.1)Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现

13.2)

      • redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
      • 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
      • 如果在一个事务中出现运行错误,那么正确的命令会被执行。

13.3)Redis 事务其他实现

基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行,

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章