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 详解。
TIP
Redis 6.0 新特性----RESP3协议。
为什么使用 Redis,有哪些好处
了解 Redis 的特点,以及适应的场景,要点如下。
- 速度极快。
- 持久化。
- 支持多种数据结构。分别支持哈希、集合、BitMaps,还有位图(多用于活跃用户数等统计)、HyperLogLog(超小内存唯一值计数,由于只有 12K,是有一定误差范围的)、GEO(地理信息定位)。
- 支持多种编程语言。
- 功能丰富。如发布订阅、Lua 脚本、事务、Pipeline(管道,即当指令到达一定数量后,客户端才会执行)。
- 简单。不依赖外部库、单线程、只有 23000 行 Code。
- 主从复制。
- 高可用和分布式。
详细可参考 为什么我们要使用 Redis。
Redis 相比 Memcached 有哪些优势
- Memcached 所有的值均是简单的字符串,Redis 作为其替代者,支持更为丰富的数据类型。
- Redis 的速度比 Memcached 快很多。
- Redis 可以持久化其数据。
- ...。
使用过 Redis 分布式锁么,它是怎么实现的
Redis 提供了一个命令 setnx 可以来实现分布式锁,该命令只在键 key 不存在的情况下 将键 key 的值设置为 value ,若键 key 已经存在, 则 SETNX 命令不做任何动作。注意一些细节,如下。
- 锁超时问题。
- 原子性问题。
- 锁的误删除问题。
- Redisson 的 RedLock。
详细理解可以参考 Redis 实现分布式锁原理。
会话缓存(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 为什么是单线程的
主要有以下几点原因。
- Redis 是基于内存的操作。
- CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。
注意 Redis4.0 之后,有些后台线程处理一些缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等等。
Redis6.0 引入的多线程特性,参考 Redis 6.0 新特性-多线程连环13问!。
为什么 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) 。
读写分离模型
通过增加 Slave DB 的数量,读的性能可以线性增长。为了避免 Master DB 的单点故障,集群一般都会采用两台 Master DB 做双机热备,所以整个集群的读和写的可用性都非常高。
读写分离架构的缺陷在于,不管是 Master 还是 Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于 Write-intensive 类型的应用,读写分离架构并不适合。
数据分片模型
为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。可以将每个节点看成都是独立的 master,然后通过业务实现数据分片。结合上面两种模型,可以将每个 master 设计成由一个 master 和多个 slave 组成的模型。
Redis 有哪些架构模式,讲讲各自的特点
有下面一些架构模式:
- Redis 架构模式
- 单机版
- 主从复制
- 哨兵
- 集群(proxy 型)
- 集群(直连型)
- 单机。
特点:
- 简单。
问题:
- 内存容量有限。
- 处理能力有限。
- 无法高可用。
- 主从复制。
Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。
只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
特点:
- master/slave 角色
- master/slave 数据相同
- 降低 master 读压力在转交从库
缺点:
- 没有解决 master 写的压力
- 哨兵。
Redis sentinel 是一个分布式系统中监控 Redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
- 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。
特点:
- 保证高可用。
- 监控各个节点。
- 自动故障迁移。
缺点:
- 主从模式,切换需要时间丢数据。
- 没有解决 master 写的压力。
- 集群(proxy 型)。
Twemproxy 是一个 Twitter 开源的一个 Redis 和 Memcache 快速/轻量级代理服务器;Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。
特点:
- 多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins。
- 支持失败节点自动删除。
- 后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致。
缺点:
- 增加了新的 proxy,需要维护其高可用。
- failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预。
- 集群(直连型)。
从 Redis 3.0 之后版本支持 redis-cluster 集群,Redis-Cluster 采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
特点:
- 无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
- 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
- 可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
- 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本。
- 实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
缺点:
- 资源隔离性较差,容易出现相互影响的情况。
- 数据通过异步复制,不保证数据的强一致性。
- 高可用 Redis 架构分析搭建,可以参考:高可用Redis服务架构分析与搭建
sharding 机制的限制
因为每个 shard 都是一个 master,因此使用 sharding 机制会产生一些限制:不能在 sharding 中直接使用 jedis 的 transactions、pipelining、pub/sub 这些 API,基本的原则是不能跨越 shard。
另外一个限制是正在使用的 shards 是不能被改变的,因为所有的 sharding 都是预分片的。
命令无法跨节点使用:如 mget,keys,scan,flush,sinter 等。
Lua 和事务无法跨节点使用。
如何解决 Redis 并发竞争 Key 问题
方案简介如下,详见Redis 并发竞争 key 问题如何解决。
- 乐观锁,注意不要在分片集群中使用。
- 分布式锁,适合分布式系统环境。
- 时间戳,适合有序场景。
- 消息队列,串行化处理。
- 分段锁。
为什么 Redis 需要把所有数据放到内存
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 Redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 Redis 的性能。在内存越来越便宜的今天,Redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
为什么 Redis 的操作是原子性的,怎么保证原子性的
对于 Redis 而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。Redis 的操作之所以是原子性的,是因为 Redis 是单线程的。
Redis 本身提供的所有 API 都是原子操作,Redis 中的事务其实是要保证批量操作的原子性。
多个命令在并发中也是原子性的吗?
不一定,将 get 和 set 改成单命令操作,incr。使用 Redis 的事务,或者使用 Redis + Lua ==
的方式可以保证操作原子性。
发布/订阅原理
Redis 线程模型
参考 彻底搞懂 Redis 的线程模型。
RedLock
已废弃,参考 面试官:说一下红锁RedLock的实现原理?。
实战
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 设计原则。
Redis 线上操作最佳实践有哪些
详细参考 Redis 线上操作最佳实践。
热门面试题
Redis 和缓存一致性问题
根源就是并发场景下,更新 Redis 和更新数据库是非原子性的操作,会出现缓存和数据不一致的场景。
解决方案如下。
- 延迟双删。该方案关注下延迟多久,一般是读操作耗时+几百毫秒,但是实际也会出现一定的一致性问题。
- 队列加重试机制。也会出现一致性的问题,可以多删几次等。
- 异步更新缓存(基于 binlog 的同步机制,如 阿里的 canal 框架)。可以在同步时结合队列+重试机制解决一致性问题。
还有其他的一些方案,可以参考相关资源理解下。
TIP
拓展 MySQL binlog。有时间整理下,且归类到 MySQL 中。
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 哨兵机制原理详解,关注下以下要点。
- 监控,关键字心跳、主观下线、客观下线。
- 主从切换,关键字筛选、评估。
- 信息通知,关键字 重新 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 的文件名和文件位置。
需要关注一些要点如下。
- 流程。
- Redis 客户端执行 bgsave 命令或者自动触发 bgsave 命令。
- 主进程判断当前是否已经存在正在执行的子进程 ,如果存在,那么主进程直接返回。
- 如果不存在,正在执行的子进程 ,那么就 fork 一个新的子进程进行持久化数据,fork 过程是阻塞的,fork 操作完成后主进程即可执行其它操作。
- 子进程先将数据写入到 临时的 rdb 文件中 ,待快照数据写入完成后,再原子替换旧的 rdb 文件。
- 同时发送信号给主进程,通知主进程 rdb 持久化完成,主进程更新相关的统计信息。
- 触发。
- 手动触发。
- 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 种方案,及正确使用姿势。
Redis 中 SETNX 与 SET NX 的区别及应用
参考文章理解,Redis 中 SETNX 与 SET NX 的区别及应用。
Redis 事务(类似问题 Redis 到底支不支持事务?)
面试点可以参考这篇文章答,以下是一些要点,Redis 执行 Lua,能保证原子性吗。
- 需要解释这里的原子性是什么?它和关系数据事务 ACID中的一致性的差异是什么?消除原子性在具体载体(RDBMS/NoSQL)上概念的差异。
- ACID 的原子性是指:事务中的命令要么全执行,要么全部不执行;
- Redis 中执行 Lua脚本原子性是指:Lua 脚本会作为一个整体执行且不被其他客户端打断,至于 Lua 脚本里面的命令是否必须全部成功,或者全部失败,并不要求。
- 需要解释 Redis的事务,说明 RDBMS/NoSQL 在事务上的差异点;
- Redis 事务允许执行一批命令,通过执行 MULTI 命令开启事务,执行 EXEC 命令结束事务,WATCH 和 DISCARD 配合事务一起使用,提供了一种 CAS(check-and-set) 乐观锁的机制。WATCH 用于监听 Key,如果被监听的 Key有任何一个发生变化,则中止事务(被动关闭事务),而 DISCARD 用于主动中止事务。
- Redis 不支持回滚。
- 需要解释 Redis 在不同部署方式下原子性能否保证。Redis 部署方式有 3 种:单机部署,主从部署,Cluster 集群部署,需要说明在哪些部署方式下能保证原子性,哪些不能保证原子性;
- 单机部署:不管 Lua 脚本中操作的 key 是不是同一个,都能保证原子性;
- 主从部署:都是写操作,都在主节点上,是可以保证原子性,如果有读操作,读可能在从库,可能读到稍有延迟的数据。
- 集群部署:同一个 key 能保证原子性,不同的 key 则不能保证原子性。
- 解释 Redis 执行 Lua 脚本是如何保证原子性;
- 即 redis.call(),redis.pcall() 执行步骤和差异。
- 分析下 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)。
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快照:COW(Copy-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测试:
单实例:4核8G云服务器,SET/GET操作 >12万QPS
集群:3主3从,线性扩展至60万QPS
故障演练:
主节点宕机后,哨兵在8秒内完成切换
数据恢复:RDB+AOF混合恢复,20GB数据加载时间<2分钟
可以问 deepseek 问题 “你怎么开发一个 类似 Redis 的中间件”。
可以通过场景来说,如设置、删除一个 key 时,Redis 的完整流程。需要考虑如下要点。