Interview
Redis

BigKey 问题,多大算 Big?你如何发现?如何删除?如何处理?

  • String 类型,大于 10KB

  • List、Hash、Set 和 ZSet 个数超过 5000

redis-cli —bigkeys
memory usage <key-name>
  • String 一般用 del,过于庞大使用 unlink

  • hash hscan 获取少量 field-value 使用 hdel 删除每个 field

  • list ltrim 渐进式逐步删除

  • set sscan + srem

  • zset zscan + rem

BigKey 生产调优,在 redis.conf 配置文件设置 lazy freeing 相关配置

手机登陆某 App,短信验证码下发功能,如何用 Redis 实现

新浪微博、微信实现共同好友功能,如果使用 Redis,你如何实现,说说思路

Redis 持久化是什么?RDB 和 AOF 相关特性和工作原理分别说下

Redis是内存数据库,宕机后数据会消失,当Redis用作DB时,DB数据要完整,所以一定要有一个完整的数据源文件,在系统启动时,从这个完整的数据源中将数据load到Redis内存中,完成提供持久化机制。

RDB:持久化是以指定的时间间隔执行数据集的时间点快照(全量备份)

  • 触发条件

    • 自动

      • redis.conf 文件修改 save 命令
    • 手动

      • save:在持久化工作完成前,会阻塞当前 redis 服务器,一般线上禁止使用
      • bgsave:会 fork 一个子进程在后台异步进行快照操作
  • 优点

    • 文件紧凑
    • 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,最大化 Redis 性能
    • 和 AOF 相比,在恢复大的数据集的时候,RDB 方式会更快一些
  • 缺点

    • 数据丢失风险大
    • RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级不能响应客户端请求。

AOF:以日志形式记录每个写操作,只允许追加但不可以改写文件。

  • AOF 原理

    • client 命令到达 server 后不会直接写入 aof 文件,会将其这些命令先放入 AOF 缓存中进行保存。当这些命令达到一定量以后再写入磁盘,避免频繁的磁盘 IO 操作。

    • AOF 缓冲会根据 AOF 缓冲区同步文件的三种写回策略将命令写入磁盘上的 AOF 文件。

    • 随着写入 AOF 内容的增加为避免文件膨胀,会根据规则进行命令的合并(又称 AOF 重写),从而起到 AOF 文件压缩的目的。

    • 当 Redis Server 服务器重启的时候会从 AOF 文件载入数据。

  • 优点

    • AOF 文件是一个只进行追加的日志文件;

    • Redis 可以在 AOF 文件体积变得过大时,自动在后台对 AOF 进行重写;

    • AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂,对文件进行分析也很轻松。

  • 缺点

    • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积;

    • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。

Redis 复制功能底层原理和工作流程是什么?

概述

主从复制,Master 以写为主,Slave 以读为主;当 Master 数据变化时,自动将新的数据异步同步到其他 Slave 数据库。

工作流程

  • Slave 启动成功连接到 Master 后会发一个 Sync 命令
    Slave 首次连接 Master 节点,会完成一次全量复制,自身原有的数据会被 Master 数据覆盖

  • Master 节点收到 Sync 命令后会开始在后台保存快照(即 RDB 持久化,主从复制时会触发 RDB),同时收集所有接收到的用于修改数据集命令缓存起来;
    Master 节点执行 RDB 持久化完后,Master 将 RDB 快照文件和所有缓存的命令发送到所有 Slave,已完成一次同步完成;
    Slave 服务接收到数据库文件后,存盘并加载到内容中

  • Master 和 Slave 通过持续的心跳来保持通信

  • Master 将新的修改命令一次传给 Slave 来完成同步

  • 从机下线,重连续传:Master 和 Slave 都会保存一个复制的 offset 还有一个 masterId,Master 会根据 backlog 里面的 offset,将 offset 后面的数据复制给 Slave。

Redis 的事务是什么?怎么操作实现?它和传统的 MySQL 数据库事务有什么不同

  • 概述

    可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。

  • 工作原理

    • 以MULTI开始一个事务

    • 将多个命令入队到事务中,接到这些命令并不会立即执行, 而是放到等待执行的事务队列里面

    • 由EXEC命令触发事务

  • Redis事务 VS 数据库事务

    • 单独的隔离操作

    • 没有隔离级别的概念

    • 不保证原子性

    • 排他性

Redis 复制功能底层原理和工作流程是什么?

  • slave 启动,同步初请

    • slave 启动成功连接到 master 后会发送一个 sync 命令;
    • slave 首次全新连接 master,一次完全同步(全量复制)将被自动执行,slave 自身原有数据会被 master 数据覆盖清除。
  • 首次连接,全量复制

    • master 节点收到 sync 命令后会开始在后台保存快照(即 RDB 持久化,主从复制时会触发 RDB),同时收集所有接收到的用于修改数据集命令缓存起来,master 节点执行 RDB 持久化完后,master 将 RDB 快照文件和所有缓存的命令发送到所有 slave,已完成一次同步完成;
    • slave 服务在收到数据库文件数据后,将其存盘并加载到内存中,从而完成复制初始化。
  • 心跳持续,保持通信

    master 发出 PING 包的周期,默认是 10 秒,repl-ping-replica-period 10

  • 进入平稳,增量复制

    master 继续将新的所有收集到的修改命令自动一次传给 slave,完成同步。

  • 从机下线,重连续传

    master 会检查 backlog 里面的 offset,master 和 slave 都会保存一个复制的 offset 还有一个masterId, offset 是保存在 backlog 中的。master 只会把已经复制的 offset 后面的数据复制给 slave,类似断点续传。

Redis 哨兵功能你使用过吗?谈谈主观下线和客观下线

Redis 哨兵(Sentinel)是Redis的高可用性解决方案。通过监控Redis主服务器和从服务器,它能够实现故障转移,配置提供者,以及服务发现等功能。哨兵通过发送命令,检查Redis实例是否可达以及是否按预期运作,从而决定实例是否在线。在Redis哨兵中,主观下线和客观下线是两个重要概念,它们共同决定了是否会对Redis实例进行故障转移。

主观下线(Subjective Down, SDown)

主观下线是指某个哨兵实例因为某些原因(如无法与Redis服务器正常通信)而认为Redis实例不可用的状态。这是一个“主观”判断,因为这种情况只是从当前哨兵的角度观察到的,并不代表其他哨兵或客户端也认为该Redis实例不可用。主观下线通常是由于网络故障、Redis实例卡顿或响应超时等原因导致的。

客观下线(Objective Down, ODown)

当足够多的哨兵(根据配置的quorum值)都报告同一Redis实例为主观下线状态时,系统会将该实例判定为客观下线。这是一个“客观”决定,因为它是基于多个哨兵的共识而非单个哨兵的观察。只有当实例被判定为客观下线后,哨兵系统才会开始故障转移过程,如自动选举新的主节点以替代下线的主节点。

故障转移过程

  1. 检测故障:当主节点被客观下线后,哨兵会开始故障转移过程。
  2. 选举领导者:多个哨兵中,只有一个哨兵会被选举为领导者来执行故障转移操作。
  3. 选举新的主节点:领导者哨兵会从现有的从节点中选举出一个新的主节点。
  4. 配置更新:新的主节点会被配置好,并通知其他从节点及客户端更新配置。

使用哨兵的好处

  • 高可用性:自动故障转移机制提高了Redis服务的可用性。
  • 系统稳定性:通过客观下线机制,避免了因单个哨兵的网络问题误判节点下线。
  • 服务发现:客户端可以查询哨兵得到当前的主节点地址,简化了客户端的配置。

在使用Redis哨兵时,合理配置quorum值和理解主观下线与客观下线的区别非常重要,这有助于避免不必要的故障转移操作,确保系统的稳定性和可靠性。

Redis 管道命令你怎么理解的?

Redis 客户端执行命令分 4 个过程:发送命令、命令排队、命令执行和返回结果。这个过程称为 RRT,Redis 的原生批量命令有效节约了 RTT,但是大部分命令不支持批量操作,需要消耗 N 次 RTT 产生多次往返。

Pipeline 是为了解决 RTT 往返回时,仅仅是将命令打包一次性发送,对整个 Redis 的执行不选成其它任何影响,是提升性能的利器。

管道与原生批命令对比

特征原生批命令管道命令
原子性原子性(所有 Redis 单命令都是原子性的)Pipeline 是非原子性的,中途异常退出,不回滚
命令一命令 + 多个 key多个命令
实现方式服务端实现需要服务端与客户端共同完成

谈谈 Redis 中的主要命令数据类型和使用场景

Redis 数据类型讲解

集群中 Slot 槽位映射,会采用 Hash 算法,你知道几种?你用哪一种

哈希取余分区、一致性哈希算法分区、哈希槽分区

优点:简单粗暴,直接有效,只需要预估好数据,规划好节点,例如 3 台,8 台,10 台,就能保证一段时间的数据支持。使用 Hash 算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡和分而治之的作用。

缺点:原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3 会变成 Hash(key)/? 。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。某个 redis 机器宕机了,由于台数数量变化,会导致 hash 取余全部数据重新洗牌。

优点:加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。

缺点:数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果,出现数据倾斜问题。

哈希槽分区解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用 Key 的哈希值来计算所在的槽,便于数据分配。

为什么 Redis 集群的最大槽数是 16384 个?

  • 消息大小考虑:尽管 CRC16 能得到 65535 个值,但 redis 选择 16384 个 slot,是因为 16384 的消息头只占用了 2k,而 65535 则需要 8k。
  • 集群规模设计考虑:集群设计最多支持 1000 个分片,16384 是相对比较好的选择,需要保证在最大集群规模下,slot 均匀分布式场景下,每个分片平均分到的 slot 不至于太小。

你们公司怎么保证 Redis 和 MySQL 的双写一致性,更新策略你说说

双写一致性 (opens in a new tab)

请统计淘宝首页每天的点击率是多少?要求支持亿级 UV 的统计方案

HyperLogLog,相对于 Redis 的 Hash 存储结构,按照 ipv4 的结构来说明,每个 ipv4 的地址最多是 15 个字节,一天 1.5 亿 * 15 个字节 = 2G,一个月 60G,占用巨大

Redis 使用了 2142^{14} = 16384 个桶,按照上面的标准差,误差为 0.81% ,精度相当高。Redis 使用一个 long 类型哈希值的前 14 个比特用来确定桶编号,剩下的 50 个比特用来做基数估计。而 262^{6} = 64,所以只需要用 6 个比特表示下标值。在一般情况下,一个 HpyterLogLog 数据结构占用内存的大小为 16384 * 6 / 8 = 12KB,Redis 将这种情况称为密集(dense)存储。

美团附近的酒店、单车,类似附近的 XX 功能如何实现

请简述布隆过滤器 BloomFilter 原理和使用场景

  1. 初始化:布隆过滤器在初始时是一个包含m位的位数组,所有位都设为0。同时选定k个哈希函数,这些哈希函数能将任何输入映射到m位中的某一位。
  2. 添加元素:将要添加的元素通过k个哈希函数处理,得到k个在m位数组范围内的位置,并将这些位置的位都设置为1。
  3. 元素查询:要判断一个元素是否存在于集合中,同样通过这k个哈希函数得到k个位置。如果所有这些位置的位都是1,那么元素可能存在于集合中;如果任何一个位置的位是0,则元素绝对不在集合中。
  4. 误判:由于多个元素可能被映射到相同的位上,因此存在误判的可能,即某个元素并未添加到集合中,但测试结果却错误地表明它存在。

解决缓存穿透、黑名单校验,识别垃圾邮件

缓存雪崩是什么,如何避免

缓存雪崩 (opens in a new tab)

缓存击穿是什么?有哪些危害?如何避免

缓存击穿 (opens in a new tab)

缓存穿透是什么?如何避免

缓存穿透 (opens in a new tab)

缓存在使用中,你们公司有编码和配置规范吗?

请谈谈实现一个 Redis 分布式锁要达到哪些要求和条件

  • 独占性:任何时刻只能有且仅有一个线程持有
  • 高可用
    • 若 Redis 集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况
    • 高并发请求下,依旧性能好使
  • 防死锁:杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案
  • 不乱抢:防止张冠李戴,不能私下解别人的锁,自己加锁只能自己释放
  • 重入性:同一个节点的同一个线程如果获得锁之后,他也可以再次获取这个锁

请谈谈手写 Redis 分布式锁思路和步骤

手写分布式锁 (opens in a new tab)

Redis 分布式锁 RedLock 红锁算法你了解吗?

Redisson 实现分布式锁和底层原理

Redis 的缓存过期淘汰策略有哪些?你用哪一种?

Redis 的缓存过期淘汰策略 (opens in a new tab)

Redis 源码读过吗?请说一下 RedisObject 对象

RedisObject 是 Redis 的核心,因为几乎所有的 Redis 操作都涉及对这种类型对象的操作。通过使用这样一个通用的对象模型,Redis 能够统一处理不同类型的数据结构,同时保持代码的整洁和效率。它还允许 Redis 在运行时根据数据的使用模式动态调整编码方式,从而优化内存使用和性能。

Redis 常用的 String 类型,为什么重新设计一个 SDS 数据结构

原因 (opens in a new tab)

跳表你了解吗

Redis 单线程如何处理那么多并发客户端连接,为什么单线程还这么快

1. 内存访问:Redis 是一个完全基于内存的数据存储。访问内存比访问磁盘要快得多,因为内存访问的延迟非常低。这使得数据操作非常快速,因为没有磁盘I/O的瓶颈。

2. 非阻塞 I/O:Redis 使用非阻塞 I/O 操作,这意味着它可以在等待I/O操作完成时继续执行其他操作。Redis 使用I/O多路复用技术(如 epoll、kqueue),允许单个线程监控多个网络连接的状态变化,从而可以在一个连接等待数据时服务其他连接的请求。

3. 事件驱动模型:Redis 采用事件驱动模型,这意味着所有操作(如网络请求、数据处理等)都是响应某些事件(如数据到达、客户端连接)而进行的。这种模型非常适合于处理高并发,因为它允许系统在一个事件循环内快速切换处理不同的任务。

4. 管道化:Redis 支持客户端请求的管道化,允许客户端在等待响应之前发送多个请求。这减少了网络延时的影响,并允许Redis在单个操作中处理多个命令,从而更有效地利用网络和处理能力。

5. 简单的数据结构:Redis 使用简单而高效的数据结构来存储数据,例如字符串、列表、集合等。这些数据结构为特定类型的操作提供了高效的支持,从而优化了性能。

6. 单线程避免锁竞争:由于Redis是单线程的,它天然避免了多线程环境中的锁竞争和线程切换成本。没有多线程的复杂性,Redis 可以连续处理请求,而无需担心线程安全和上下文切换的开销。

总结

虽然单线程可能听起来会限制性能,但在Redis的案例中,由于其高效的内存操作、事件驱动架构、非阻塞I/O和数据结构的优化,这使得单线程能够高效地处理大量并发请求。Redis的这种设计展示了在特定场景下,单线程可以非常高效地执行多任务,特别是在网络和内存访问操作占主导地位的场景中。

IO 多路复用是什么,它是 Redis 的底层实现?请你谈谈

I/O 多路复用是一种编程技术,它使得单个进程或线程可以同时监控多个I/O流的事件。这允许程序在一个或多个I/O流准备好进行读写操作时,得到通知并进行相应的处理,而不必为每个I/O流阻塞等待,从而提高了程序在处理多个并发I/O操作时的效率。

Redis 使用 I/O 多路复用

Redis 作为一个高性能的键值存储,使用 I/O 多路复用是其能够高效处理多个并发连接的关键。Redis 的网络I/O操作,包括接收新的连接请求、读取数据、发送响应等,都是通过I/O多路复用机制实现的。Redis 支持多种I/O多路复用库(如select, epoll, kqueue等),它会根据运行平台选择最合适的实现。

在Redis的事件循环中,I/O多路复用库用于监控和通知哪些socket准备好进行读写操作。这允许Redis的单线程服务器以非阻塞方式高效地处理成千上万的并发客户端连接。

签到系统怎么实现

使用 Bitmap。在签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型

Redis 基础结构?谈谈 List Set 区别,底层区别