Redis 常见面试题

felix.shao2025-02-16数据库Redis

Redis 常见面试题

基础概念

什么是 Redis

 Redis 是一个基于内存的高性能 key-value 数据库。

Redis 的特点
  • 基础
    • 语言 ANSI C 编写
    • 存储 基于内存,性能高效
    • 协议 C/S 通讯模型
    • API 接口 多语言支持
  • 特性
    • 原子性 支持
    • 事务 支持
    • 持久化 支持
    • 并发支持 高并发
    • 可拓展性 支持 LUA 脚本
    • 容灾性
      • 将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
      • 支持数据的备份,即 master-slave 模式的数据备份
  • 逻辑结构
    • 存储 key-value 存储系统
    • 支持的数据类型 丰富的数据类型支持
  • 进程线程模型 单线程模型,6.0 版本后加入了多线程模型
  • 分布式
    • 是否支持分布式 是,理论上可以无限扩展
Redis 常用命令
 管理命令
# 返回当前数据库 key 的数量。
dbsize
# 返回当前 redis 服务器状态和一些统计信息。 
info 
# 实时监听并返回 Redis 服务器接收到的所有请求信息。
monitor 
# 把数据同步保存到磁盘上,并关闭 Redis 服务。
shutdown 
# 获取一个 Redis 配置参数信息。(个别参数可能无法获取)
config get parameter 
# 设置一个 Redis 配置参数信息。(个别参数可能无法获取)
config set parameter value 
# 重置 info 命令的统计信息。(重置包括:keyspace 命中数、
config resetstat 
# 错误数、 处理命令数,接收连接数、过期 key 数)
keyspace 
# object key 获取一个 key 的调试信息。
debug
# 制造一次服务器当机。
debug segfault 
# 删除当前数据库中所有 key,此方法不会失败。小心慎用
flushdb 
# 删除全部数据库中所有 key,此方法不会失败。小心慎用
flushall 
 工具命令
# Redis 服务器的 daemon 启动程序
redis-server
# Redis 命令行操作工具。当然,你也可以用 telnet 根据其纯文本协议来操作
redis-cli
# Redis 性能测试工具,测试 Redis 在你的系统及你的配置下的读写性能
redis-benchmark
# 更新日志检查
redis-check-aof
# 本地数据库检查
redis-check-dump
Redis 通讯协议

 RESP 是 Redis 客户端和服务端之前使用的一种通讯协议。
 RESP 的特点:实现简单、快速解析、可读性好。
 其对应的结构等详解参考 Redis 底层协议 RESP 详解open in new window

TIP

 Redis 6.0 新特性----RESP3协议。

为什么使用 Redis,有哪些好处

 了解 Redis 的特点,以及适应的场景,要点如下。

  • 速度极快。
  • 持久化。
  • 支持多种数据结构。分别支持哈希、集合、BitMaps,还有位图(多用于活跃用户数等统计)、HyperLogLog(超小内存唯一值计数,由于只有 12K,是有一定误差范围的)、GEO(地理信息定位)。
  • 支持多种编程语言。
  • 功能丰富。如发布订阅、Lua 脚本、事务、Pipeline(管道,即当指令到达一定数量后,客户端才会执行)。
  • 简单。不依赖外部库、单线程、只有 23000 行 Code。
  • 主从复制。
  • 高可用和分布式。

 详细可参考 为什么我们要使用 Redisopen in new window

Redis 相比 Memcached 有哪些优势
  • Memcached 所有的值均是简单的字符串,Redis 作为其替代者,支持更为丰富的数据类型。
  • Redis 的速度比 Memcached 快很多。
  • Redis 可以持久化其数据。
  • ...。
使用过 Redis 分布式锁么,它是怎么实现的

 Redis 提供了一个命令 setnx 可以来实现分布式锁,该命令只在键 key 不存在的情况下 将键 key 的值设置为 value ,若键 key 已经存在, 则 SETNX 命令不做任何动作。注意一些细节,如下。

  • 锁超时问题。
  • 原子性问题。
  • 锁的误删除问题。
  • Redisson 的 RedLock。

 详细理解可以参考 Redis 实现分布式锁原理open in new window

会话缓存(Session Cache)

 最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。

全页缓存(FPC)

 即页面静态化技术,除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。再次以 Magento 为例,Magento 提供一个插件来使用 Redis 作为全页缓存后端。此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

数据结构与对象

Redis 的数据类型,以及每种数据类型的使用场景
  • String:一般做一些复杂的计数功能的缓存。
  • Hash:存储二维数据或对象。
  • List:可实现队列,栈及有序的数据存储。
  • Set:利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能,也常用于黑名单,微信抽奖等功能,应用场景多变。
  • SortedSet:做排行榜应用,取 TOPN 操作;延时任务;做范围查找。
队列

 Redis 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。如果你快速的在 Google 中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用 Redis 作为 broker,你可以从这里去查看。

使用过 Redis 做异步队列么,你是怎么用的,有什么缺点

 一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。
 缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq 等。能不能生产一次消费多次呢?使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。

排行榜/计数器

 Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10 个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:ZRANGE user_scores 0 10 WITHSCORES Agora Games 就是一个很好的例子,用 Ruby 实现的,它的排行榜就是使用 Redis 来存储数据的。

有序集合除了跳表还有什么结构实现

 这里一个是 B 树,类似 MySQL。其他的几种数据结构问题类似。

TIP

 Why 为啥 MySQL 用 B+ 树,而 Redis 用跳表。

  • 主要是扫描磁盘开销不一样。MySQL 数据是存磁盘的,跳表层级太多,每层都要读取磁盘,因此不适合,而 Redis 是内存的,没有这个问题。
  • 范围查找需要,MySQL B+ 树叶子节点间有指针,可以扫描叶子节点就能方便查询,而跳表和 B 树都需要找父节点扫描。

架构

Redis 为什么是单线程的

 主要有以下几点原因。

  1. Redis 是基于内存的操作。
  2. CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。

 注意 Redis4.0 之后,有些后台线程处理一些缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等等。
 Redis6.0 引入的多线程特性,参考 Redis 6.0 新特性-多线程连环13问!open in new window

为什么 Redis 服务使用单线程模型处理绝大多数的网络请求?

 类似问题也即为什么 Redis 那么快。主要原因如下。

  • 纯内存操作:读取不需要进行磁盘 IO,所以比传统数据库要快。但不要有误区说磁盘即一定慢,例如 Kafka 就是使用磁盘顺序读取但仍然较快)。
  • 单线程,无锁竞争:保证了没有线程的上下文切换,不会因为多线程的一些操作而降低性能。
  • 多路 IO 复用模型,非阻塞 IO:采用多路 IO 复用技术可以让单个线程高效的处理多个网络连接请求(尽量减少网络 IO 的时间消耗)。
  • 数据结构的设计,查询时间复杂度都是常数。
  • 数据编码的设计,不同类型底层存储采取了不同编码优化。
为什么 Redis 服务增加了多个非阻塞的删除操作,例如:UNLINK、FLUSHALL ASYNC 和 FLUSHDB ASYNC

 对于 Redis 中的一些超大键值对,几十 MB 或者几百 MB 的数据并不能在几毫秒的时间内处理完,Redis 可能会需要在释放内存空间上消耗较多的时间,这些操作就会阻塞待处理的任务,影响 Redis 服务处理请求的 PCT99 和可用性,然而释放内存空间的工作其实可以由后台线程异步进行处理,这也就是 UNLINK 命令的实现原理,它只会将键从元数据中删除,真正的删除操作会在后台异步执行。

虚拟内存

 虚拟内存功能在 2.6 版本后已经去除,详细参考 Redis 虚拟内存(virtual memory)open in new window

读写分离模型

 通过增加 Slave DB 的数量,读的性能可以线性增长。为了避免 Master DB 的单点故障,集群一般都会采用两台 Master DB 做双机热备,所以整个集群的读和写的可用性都非常高。
 读写分离架构的缺陷在于,不管是 Master 还是 Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于 Write-intensive 类型的应用,读写分离架构并不适合。

数据分片模型

 为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。可以将每个节点看成都是独立的 master,然后通过业务实现数据分片。结合上面两种模型,可以将每个 master 设计成由一个 master 和多个 slave 组成的模型。

Redis 有哪些架构模式,讲讲各自的特点

 有下面一些架构模式:

  • Redis 架构模式
    • 单机版
    • 主从复制
    • 哨兵
    • 集群(proxy 型)
    • 集群(直连型)
  1. 单机。

 特点:

  • 简单。

 问题:

  • 内存容量有限。
  • 处理能力有限。
  • 无法高可用。
  1. 主从复制。

 Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。
 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。

 特点:

  • master/slave 角色
  • master/slave 数据相同
  • 降低 master 读压力在转交从库

 缺点:

  • 没有解决 master 写的压力
  1. 哨兵。

 Redis sentinel 是一个分布式系统中监控 Redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:

  • 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

 特点:

  • 保证高可用。
  • 监控各个节点。
  • 自动故障迁移。

 缺点:

  • 主从模式,切换需要时间丢数据。
  • 没有解决 master 写的压力。
  1. 集群(proxy 型)。

 Twemproxy 是一个 Twitter 开源的一个 Redis 和 Memcache 快速/轻量级代理服务器;Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。

 特点:

  • 多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins。
  • 支持失败节点自动删除。
  • 后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致。

 缺点:

  • 增加了新的 proxy,需要维护其高可用。
  • failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预。
  1. 集群(直连型)。

 从 Redis 3.0 之后版本支持 redis-cluster 集群,Redis-Cluster 采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
 特点:

  • 无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
  • 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
  • 可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
  • 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本。
  • 实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。

 缺点:

sharding 机制的限制

 因为每个 shard 都是一个 master,因此使用 sharding 机制会产生一些限制:不能在 sharding 中直接使用 jedis 的 transactions、pipelining、pub/sub 这些 API,基本的原则是不能跨越 shard。
 另外一个限制是正在使用的 shards 是不能被改变的,因为所有的 sharding 都是预分片的。
 命令无法跨节点使用:如 mget,keys,scan,flush,sinter 等。
 Lua 和事务无法跨节点使用。

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

 方案简介如下,详见Redis 并发竞争 key 问题如何解决open in new window

  • 乐观锁,注意不要在分片集群中使用。
  • 分布式锁,适合分布式系统环境。
  • 时间戳,适合有序场景。
  • 消息队列,串行化处理。
  • 分段锁。
为什么 Redis 需要把所有数据放到内存

 Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 Redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 Redis 的性能。在内存越来越便宜的今天,Redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

为什么 Redis 的操作是原子性的,怎么保证原子性的

 对于 Redis 而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。Redis 的操作之所以是原子性的,是因为 Redis 是单线程的。
 Redis 本身提供的所有 API 都是原子操作,Redis 中的事务其实是要保证批量操作的原子性。
多个命令在并发中也是原子性的吗?
 不一定,将 get 和 set 改成单命令操作,incr。使用 Redis 的事务,或者使用 Redis + Lua == 的方式可以保证操作原子性。

发布/订阅原理

 见 Redis 的发布与订阅的底层原理open in new window

Redis 线程模型

 参考 彻底搞懂 Redis 的线程模型open in new window

RedLock

 已废弃,参考 面试官:说一下红锁RedLock的实现原理?open in new window

实战

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

 相关知识:Redis 内存数据集大小上升到一定大小的时候,就会执行数据淘汰策略。

Redis 常见性能问题和解决方案
  • Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以 Master 最好不要写内存快照。
  • Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
  • Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。
  • 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内。
  • 尽量避免在压力很大的主库上增加从库。
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3... 这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。
Redis 的 Key 和 Value 设计原则有哪些

 详细参考 Redis 的 Key 和 Value 设计原则open in new window

Redis 线上操作最佳实践有哪些

 详细参考 Redis 线上操作最佳实践open in new window

大促压力暴增导致分布式锁串行争用问题
  • 读写锁

 详细参考 一线大厂 Redis 高并发缓存架构实战与性能优化open in new window

热门面试题

Redis 和缓存一致性问题

 根源就是并发场景下,更新 Redis 和更新数据库是非原子性的操作,会出现缓存和数据不一致的场景。
 解决方案如下。

  1. 延迟双删。该方案关注下延迟多久,一般是读操作耗时+几百毫秒,但是实际也会出现一定的一致性问题。
  2. 队列加重试机制。也会出现一致性的问题,可以多删几次等。
  3. 异步更新缓存(基于 binlog 的同步机制,如 阿里的 canal 框架)。可以在同步时结合队列+重试机制解决一致性问题。

 还有其他的一些方案,可以参考相关资源理解下。

Redis 过期策略

 Redis 采用的是定期删除 + 惰性删除策略。
 为什么不用定时删除策略?
 定时删除,用一个定时器来负责监视 key,过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 key,因此没有采用这一策略。
 定期删除 + 惰性删除是如何工作的呢?
 定期删除,Redis 默认每隔 100ms 检查,是否有过期的 key,有过期 key 则删除。需要说明的是,Redis 不是每个 100ms 将所有的 key 检查一次,而是随机抽取进行检查(如果每隔 100ms,全部 key 进行检查,Redis 岂不是卡死)。因此,如果只采用定期删除策略,会导致很多 key 到时间没有删除。于是,惰性删除派上用场。也就是说在你获取某个 key 的时候,Redis 会检查一下,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
 采用定期删除 + 惰性删除就没其他问题了么?
 不是的,如果定期删除没删除 key。然后你也没即时去请求 key,也就是说惰性删除也没生效。这样,Redis 的内存会越来越高,可能会内存溢出,Redis 引入内存淘汰机制解决这个问题。

Redis 内存淘汰机制

 在 redis.conf 中有一行配置 maxmemory-policy volatile-lru 是配内存淘汰策略的。
 如果没有设置 expire 的 key,不满足先决条件(prerequisites); 那么 volatile-lru、volatile-random 和 volatile-ttl 策略的行为,和 no-eviction(不删除) 基本上一致。
 注意 lfu 算法是 4.0 版本开始支持的。

  • no-enviction(驱逐):禁止驱逐数据。
  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
  • allkeys-lru: 从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。
  • volatile-lfu:只淘汰设置了过期时间的键中最少使用的键。
  • allkeys-lfu:淘汰最不常使用的键(基于 LFU 算法)。
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。

&emsp;默认是 no-enviction 策略,与版本有关。

聊聊 Redis 的哨兵机制

 一种高可用方案,其主要工作任务如下。

  • 监控:哨兵会不断地检查你的 Master 和 Slave 是否运作正常。
  • 提醒:当被监控的某个 Redis 节点出现问题时,哨兵可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移:当一个 Master 不能正常工作时,哨兵会进行自动故障迁移操作,将失效 Master 的其中一个 Slave 升级为新的 Master,并让失效 Master 的其他 Slave 改为复制新的 Master;当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使用新 Master 代替失效 Master。

 核心要点工作原理可以参考文章理解 Redis 哨兵机制原理详解open in new window,关注下以下要点。

  • 监控,关键字心跳、主观下线、客观下线。
  • 主从切换,关键字筛选、评估。
  • 信息通知,关键字 重新 replacaof、runID 和 slave_repl_offset。
  • 等,如后续已挂的主节点恢复后处理流程
RDB 和 AOF 持久化机制
持久化以及持久化方式

 持久化:即把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
 Redis 提供了三种持久化方式。

  • RDB,即 Redis Data Base,内存开启,默认。
  • AOF,即 Append Only File,增量日志,6.2.9 默认配置为 appendonly no,即不启用。
  • 混合持久化(4.0 版本开始支持),详细参考如下内容,6.2.9 默认配置为 aof-use-rdb-preamble yes,即启用,不过需要 AOF 启用才生效。
 1. RDB

 RDB 持久化会生成 RDB 文件,该文件是一个压缩过的二进制文件,可以通过该文件还原快照时的数据库状态,即生成该 RDB 文件时的服务器数据。RDB 文件默认为当前工作目录下的 dump.rdb,可以根据配置文件中的 dbfilename 和 dir 设置 RDB 的文件名和文件位置。
 需要关注一些要点如下。

    1. 流程。
    • Redis 客户端执行 bgsave 命令或者自动触发 bgsave 命令。
    • 主进程判断当前是否已经存在正在执行的子进程 ,如果存在,那么主进程直接返回。
    • 如果不存在,正在执行的子进程 ,那么就 fork 一个新的子进程进行持久化数据,fork 过程是阻塞的,fork 操作完成后主进程即可执行其它操作。
    • 子进程先将数据写入到 临时的 rdb 文件中 ,待快照数据写入完成后,再原子替换旧的 rdb 文件。
    • 同时发送信号给主进程,通知主进程 rdb 持久化完成,主进程更新相关的统计信息。
    1. 触发。
    • 手动触发。
      • save(同步保存)。
      • bgsave(主进程 fork 创建子进程,由子进程异步持久化)。
    • 自动触发。
      • 配置,如 save 60 10000, 服务器在 60 秒之内,对数据库进行了至少 10000 次修改。PS: save 配置全注释即关闭了 RDB 。
      • shutdown。
      • flushall。
  • 优点
    • 性能高。RDB 持久化是通过生成一个快照文件来保存数据,因此在恢复数据时非常快。
    • 文件紧凑。RDB 文件是二进制格式的数据库文件,相对于 AOF 来说,文件体积较小。
  • 缺点
    • 在备份周期在一定间隔时间做一次备份,所以如果 Redis 意外 down 掉 的话(如果正常关闭 Redis仍然会进行 RDB 备份,不会丢失数据),就会丢失最后一次快照后的所有修改。
 2. AOF

 AOF 存储内容是 Redis 通讯协议(RESP)格式的命令文本数据。
 每当 Redis 接受到会修改数据集的命令时,就会把命令追加到 AOF 文件里,当你重启 Redis 时,AOF 文件里的命令会被重新执行一次,重建数据。
 需要关注一些要点如下。

  • 1 流程。
    • 客户端的请求写命令会被 append 追加到 AOF 缓冲区内。
    • AOF 缓冲区根据 AOF 持久化策略将操作 sync 同步到磁盘的 AOF 文件中。
    • AOF 文件大小超过重写策略或手动重写时,会对 AOF 文件 rewrite 重写,压缩 AOF 文件容量。
    • Redis 服务重启时,会重新 load 加载 AOF 文件中的写操作达到数据恢复的目的。
  • 2 同步策略 6.2.8 默认配置 appendfsync everysec
    • always,即每次。
    • everysec,即每秒。
    • no,即交给操作系统自动判断处理。
  • 3 AOF 日志重写,下面两个条件同时满足时,会触发 AOF 重写。
    • 自动重写。
      • auto-aof-rewrite-percentage 100,也是6.2.8 默认配置,AOF 体积达到上次重写之后的 100% 时,会触发 AOF 重写。
      • auto-aof-rewrite-min-size 64mb,也是6.2.8 默认配置,当 AOF 文件体积超过这个阈值时,会触发 AOF 重写。
    • 手动重写。
      • 执行 bgrewriteaof 命令。
  • 4 AOF 修复。
    • 略。
  • 优点
    • 数据更加可靠。AOF 持久化记录了每个写命令的操作,因此在出现故障时,可以通过重写执行 AOF 文件来保证数据的完整性。
    • 可以保留写命令历史。AOF 文件是一个追加日志文件,可以用于回放过去的写操作。
  • 缺点
    • 文件较大。由于记录了每个写命令,AOF 文件体积通常比 RDB 文件要大。
    • 恢复速度较慢。当 AOF 文件较大时,Redis 重启时需要重写执行整个 AOF 文件,恢复速度相对较慢。

 可参考如下文档理解。

Redis 实现分布式锁的 7 种方案,及正确使用姿势!

 参考文章理解,Redis 实现分布式锁的 7 种方案,及正确使用姿势open in new window

Redis 中 SETNX 与 SET NX 的区别及应用

 参考文章理解,Redis 中 SETNX 与 SET NX 的区别及应用open in new window

Redis 事务(类似问题 Redis 到底支不支持事务?)

 面试点可以参考这篇文章答,以下是一些要点,Redis 执行 Lua,能保证原子性吗open in new window

    1. 需要解释这里的原子性是什么?它和关系数据事务 ACID中的一致性的差异是什么?消除原子性在具体载体(RDBMS/NoSQL)上概念的差异。
    • ACID 的原子性是指:事务中的命令要么全执行,要么全部不执行;
    • Redis 中执行 Lua脚本原子性是指:Lua 脚本会作为一个整体执行且不被其他客户端打断,至于 Lua 脚本里面的命令是否必须全部成功,或者全部失败,并不要求。
    1. 需要解释 Redis的事务,说明 RDBMS/NoSQL 在事务上的差异点;
    • Redis 事务允许执行一批命令,通过执行 MULTI 命令开启事务,执行 EXEC 命令结束事务,WATCH 和 DISCARD 配合事务一起使用,提供了一种 CAS(check-and-set) 乐观锁的机制。WATCH 用于监听 Key,如果被监听的 Key有任何一个发生变化,则中止事务(被动关闭事务),而 DISCARD 用于主动中止事务。
    • Redis 不支持回滚。
    1. 需要解释 Redis 在不同部署方式下原子性能否保证。Redis 部署方式有 3 种:单机部署,主从部署,Cluster 集群部署,需要说明在哪些部署方式下能保证原子性,哪些不能保证原子性;
    • 单机部署:不管 Lua 脚本中操作的 key 是不是同一个,都能保证原子性;
    • 主从部署:都是写操作,都在主节点上,是可以保证原子性,如果有读操作,读可能在从库,可能读到稍有延迟的数据。
    • 集群部署:同一个 key 能保证原子性,不同的 key 则不能保证原子性。
    1. 解释 Redis 执行 Lua 脚本是如何保证原子性;
    • 即 redis.call(),redis.pcall() 执行步骤和差异。
    1. 分析下 Redis 的单线程模型 和 IO 多路复用模型(加分项),这步是可选项;
    • 略。

 相关命令解释如下:

  • MULTI:用于开启一个事务,它总是返回 OK。MULTI 执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当 EXEC 命令被调用时,所有队列中的命令才会被执行。
  • EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil。
  • DISCARD:客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出。
  • WATCH:可以为 Redis 事务提供 check-and-set(CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
redis Big key (俗称大key)
  • 对大 Key 进行拆分。
  • 对大 Key 进行清理。
  • 监控 Redis 的内存、网络带宽、超时等指标。
  • 定期清理失效数据。
  • 压缩 value。

 参考文章理解 redis Big key (俗称大key)open in new window

Redis 热 Key 问题如何解决
  • Redis 集群扩容:增加分片副本,分摊客户端发过来的读请求;
  • 使用二级缓存,即 JVM 本地缓存,减少 Redis 的读请求;
  • 分批加载:将大 key 按照一定的数量或者大小进行分批加载,避免一次性加载过大的数据到内存中;
  • 数据拆分:将大 key 拆分成多个小 key,重新设计数据结构以降低单个 key 的内存占用;
  • 优化数据结构:选择适合场景的数据结构,如使用 Hash 数据结构代替 String,使用 List 代替 Set 等,减少内存占用。
Redis 的看门狗
## 1. 核心作用
- **自动续期**:解决分布式锁的**锁过期问题**,避免业务未完成时锁被自动释放
- **防止死锁**:通过定期续期保证锁的活性

## 2. 实现原理
```plaintext
1. 客户端获取锁后启动后台线程(看门狗)
2. 定期(如锁TTL的1/3间隔)发送EXPIRE命令续期
3. 业务完成时主动释放锁并终止线程
4. 客户端崩溃时停止续期→锁自动过期

## 3. 典型应用(Redisson示例)
// 默认启用看门狗(锁超时30秒,每10秒续期)
RLock lock = redisson.getLock("myLock");
lock.lock();  // 自动续期开始
try {
    // 业务逻辑...
} finally {
    lock.unlock();  // 释放锁+停止看门狗
}

## 4. 加分项
关键点 说明
对比手动续期	避免开发者自行维护续期逻辑
线程泄漏风险	忘记unlock()会导致看门狗线程长期运行
原生Redis的限制	需自行实现类似机制(如Lua脚本+定时任务)

 面试一句话:Redis 的看门狗是一种后台线程自动续期分布式锁的机制,确保长任务执行期间锁不会意外失效,核心思想是 “心跳续命 + 超时释放”。

你怎么开发一个 类似 Redis 的中间件
一、明确核心目标(30秒)
"开发类似Redis的中间件,核心需解决三大问题:

单机百万QPS:通过内存计算与非阻塞I/O实现低延迟

数据可靠性:在内存易失性前提下保障持久化与故障恢复

横向扩展性:突破单机内存限制,支持分布式集群

围绕这三点,我的设计将聚焦内存架构、网络模型、持久化、集群化四个维度。"

二、逐层拆解核心目标(90秒)
目标1:极致的单机性能
内存数据结构

使用SDS动态字符串(预分配+惰性释放)降低内存重分配次数

跳表代替平衡树实现有序集合(平均O(logN)查询,更易实现)

渐进式Rehash避免哈希表扩容时的服务中断

单线程模型

选择单线程而非多线程:避免锁竞争,保证原子性,适合KV场景

实测对比:单线程吞吐量可达多线程方案的70%以上(CPU亲和性更好)

网络层优化

c
// 关键配置示例(Linux环境)
setsockopt(fd, SOL_TCP, TCP_NODELAY, &flag);  // 禁用Nagle算法
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &flag); // 多实例绑定同端口
目标2:可靠的数据持久化
RDB快照:COWCopy-on-Write)技术生成内存快照,牺牲最新数据换恢复速度

AOF日志:

Always:每条命令刷盘(安全但性能差)

Everysec:每秒批量刷盘(吞吐量可达5+/s)

No:依赖OS刷盘(风险高但吞吐量超10/s)

目标3:无缝的集群扩展
数据分片:采用CRC16哈希分片(16384 slots),分片迁移时仅阻塞当前Key

故障转移:

哨兵模式:通过Raft协议选举新主节点(故障恢复时间<10s)

Gossip协议:集群节点每秒交换1次元数据(带宽消耗<1MB/s)

三、技术选型对比(30秒)
方案选项	选择原因	权衡代价
单线程 vs 多线程	避免锁开销,原子性天然保障	无法利用多核CPU
跳表 vs 红黑树	实现简单,范围查询高效	空间占用略高
AOF vs WAL	可读性强,便于故障调试	文件体积较大
四、落地验证(30秒)
Benchmark测试:

单实例:48G云服务器,SET/GET操作 >12QPS

集群:33从,线性扩展至60QPS

故障演练:

主节点宕机后,哨兵在8秒内完成切换

数据恢复:RDB+AOF混合恢复,20GB数据加载时间<2分钟

 可以问 deepseek 问题 “你怎么开发一个 类似 Redis 的中间件”。
 可以通过场景来说,如设置、删除一个 key 时,Redis 的完整流程。需要考虑如下要点。

附录一、参考文献

参考文献
Last Updated 5/3/2025, 9:40:00 PM