找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2786

积分

0

好友

374

主题
发表于 3 天前 | 查看: 15| 回复: 0

随着互联网时代对网站访问速度的要求越来越高,直接使用关系型数据库的方案在性能上逐渐出现瓶颈。为此,在客户端与数据存储层之间引入一个缓存层来分担请求压力,成为提升系统性能的关键。作为一款优秀的缓存中间件,Redis在企业级架构中占据着举足轻重的地位,也因此成为了技术面试中的必问项。

客户端-缓存层-数据存储层系统架构示意图

为了帮助大家在技术考核中更好地展现自己,本文将通过30个问题,由浅入深地梳理Redis的核心知识体系。

Redis知识结构全景图


问题1:什么是Redis

1、什么是Redis?

Redis(Remote Dictionary Server)是一个开源的、键值对型的内存数据存储系统。它使用C语言编写,遵守BSD协议,既可作为基于内存的数据库,也支持数据持久化。Redis提供了多种语言的API,被广泛用于缓存、数据库和消息中间件等场景。它支持多种数据结构,可以存储不同类型值之间的映射,并具备事务、持久化、LUA脚本及多种集群方案等特性。

问题2:Redis的优缺点

2、Redis的优缺点

优点:

  1. 完全基于内存操作,性能极高,读写速度快,支持超过 100KB/s 的读写速率。
  2. 支持高并发,可达10万级别的并发读写。
  3. 支持主从模式,具备读写分离与分布式能力。
  4. 拥有丰富的数据类型(如String、Hash、List、Set、ZSet)及特性(如发布订阅)。
  5. 支持持久化操作,保证数据不会因进程退出而丢失。

缺点:

  1. 数据库容量受物理内存限制,难以实现海量数据的高性能读写。
  2. 相比关系型数据库,不支持复杂的逻辑查询,存储结构相对简单。
  3. 虽然提供持久化能力,但更多是一种 disk-backed 功能,与传统意义上的持久化有所区别。

问题3:Memcache与Redis的区别

3、Memcache与Redis的区别

Memcache 同样是一个开源、高性能、分布式内存对象缓存系统。所有数据存储在内存中,服务器重启后数据丢失,采用哈希表和LRU算法进行数据管理。

两者的主要区别如下:

  1. 数据类型:Memcache 仅支持字符串类型,而 Redis 支持5种主要数据类型及多种高级数据结构。
  2. 数据持久化:Memcache 不支持持久化;Redis 支持 RDB 快照和 AOF 日志两种持久化策略。
  3. 分布式:Memcache 本身不支持分布式,需在客户端使用一致性哈希实现;Redis 3.0 之后可在服务端构建无中心节点的集群,具备线性可伸缩能力。
  4. 内存管理机制:Memcache 数据量不能超出系统内存;Redis 有 VM 机制,可管理物理内存。两者的底层实现和通信协议不同。
  5. 数据大小限制:Memcache 单个 Value 最大容量为 1MB;Redis 最大容量为 512MB。

问题4:Redis支持哪些数据类型

4、Redis 支持哪些数据类型?

基本数据类型:

  • String(字符串)
  • Hash(哈希)
  • List(列表)
  • Set(集合)
  • ZSet(Sorted Set,有序集合)

高级数据类型:

  • HyperLogLog:用于基数统计的算法。在输入元素数量极大时,计算基数所需空间固定且很小,且不存储元素本身。
  • Geo:用于存储和计算地理位置信息。其原理是通过 Geohash 算法将经纬度转换成一个哈希字符串,距离越近的地点,哈希值的前缀越相似。
  • BitMap:本质上是一种对字符串进行的位操作,常用于统计日活跃用户等场景。

问题5:Redis中String类型的实现原理

5、Redis中String类型的实现原理

Redis 并未直接使用C语言传统的字符串表示,而是自己构建了一种名为 简单动态字符串(Simple Dynamic String,SDS) 的抽象类型。

SDS 相较于C字符串的提升

  1. 避免缓冲区溢出:修改字符串时,可根据SDS的 len 属性检查空间是否满足。
  2. 获取长度复杂度低:直接读取 len 属性即可,复杂度为O(1)。
  3. 减少内存重分配次数:通过空间预分配和惰性空间释放策略优化。
  4. 二进制安全:可以保存文本或任意二进制数据。
  5. 兼容部分C字符串函数:可重用一部分C语言字符串库的函数。

问题6:Redis为何直接以内存存储

6、Redis 为何直接以内存存储?

将数据完全存储在内存中可以实现最快的读写速度。如果开启了持久化功能,数据会以异步方式写入磁盘,从而兼具了高速访问和数据持久化的特性。

内存操作本身就比磁盘I/O快得多,且不受磁盘I/O速度的制约。反之,若数据存于磁盘,I/O速度将成为严重的性能瓶颈。当数据集大小达到内存上限时,便无法插入新值。此时如果开启了虚拟内存功能,不常访问的数据会被换出到磁盘;若未开启,则会使用操作系统的交换内存,但这将导致性能急剧下降。若配置了内存淘汰策略,则会根据策略淘汰旧数据。

问题7:Redis如何进行内存优化

7、Redis 如何进行内存优化?

  1. 优先使用哈希表:对于字段数少于100的Hash结构,Redis的存储效率很高。在不需集合操作或列表的push/pop操作时,应优先考虑使用Hash。
  2. 考虑使用 BitMap:在合适的场景(如布尔状态统计)下,使用BitMap能极大节省内存。
  3. 利用共享对象池:Redis启动时会自动创建0-9999的整数对象池。对于这个范围内的整数值,Redis会直接引用池中的对象,因此尽量使用这些整数可以节省内存。
  4. 合理使用内存回收策略:利用 expire 设置键的过期时间,并配合合适的过期删除与内存淘汰策略。

问题8:Redis如何实现分布式锁

8、Redis 如何实现分布式锁?

可以利用 INCRSETNXSET 命令,并结合 expire 来辅助实现。

方式1:利用 INCR
如果 key 不存在,则初始化为0,然后使用 INCR 加1。后续线程如果获取到的值大于等于1,说明锁已被占用。持有锁的线程执行完任务后,使用 DECR 将值减1以释放锁。

方式2:利用 SETNX
使用 setnx 争抢锁,抢到后再用 expire 设置一个过期时间,防止锁无法释放。setnx 在 key 不存在时设置值并返回1;key已存在时,不做任何操作并返回0。

方式3:利用 SET
set 指令通过参数可以合并 setnxexpire 的功能,其命令格式如下:

set($Key, $value, array('nx', 'ex'=>$ttl))

问题9:Redis高性能的原因

9、Redis 高性能的原因

  1. 完全基于内存操作,读写速度极快。
  2. 数据结构简单高效,针对不同场景有专门优化的数据结构。
  3. 核心网络请求模块采用单线程,避免了不必要的上下文切换和竞争条件,也无需考虑多线程环境下的锁问题(其他模块如持久化仍使用多线程)。
  4. 使用多路I/O复用模型,实现非阻塞I/O。
  5. 自身实现了VM机制,而非使用操作系统的Swap,可实现冷热数据分离,避免因内存不足导致的访问速度下降。

问题10:Redis持久化的方式

10、Redis 持久化的方式

1、RDB(Redis DataBase)持久化
这是Redis默认的持久化方式,按照配置的时间周期,将内存中的数据以快照形式保存到磁盘的 .rdb 文件中。

其核心是 forkcow(Copy-On-Write)机制。执行 bgsave 时,Redis会 fork 出一个子进程共享主进程内存数据来写临时RDB文件,写完后替换旧文件。主进程进行读操作时访问共享内存,进行写操作时则会拷贝一份数据执行(cow)。

优点:

  • 只有一个 dump.rdb 文件,便于备份和灾难恢复。
  • 最大化性能,由子进程负责持久化,主进程继续处理命令,无I/O阻塞。
  • 紧凑的二进制格式,重启时加载效率比AOF高,尤其在数据量大时。

缺点:

  • 可能丢失最后一次快照之后的数据。
  • fork 过程在数据集很大时可能导致服务短暂停顿。

2、AOF(Append Only File)持久化
将每一个写命令追加到日志文件中。需要配置同步选项(appendfsync):

  • always:同步刷盘,可靠性最高,性能影响大。
  • everysec:每秒刷盘(默认),性能与可靠性折中,最多丢失1秒数据。
  • no:由操作系统控制,性能最好,可靠性最差。

AOF文件会不断增长,可通过 bgrewriteaof 命令或配置自动触发重写以压缩文件体积。

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

优点:

  • 数据安全性更高,配置为 always 时最多丢失一次命令。
  • rewrite 机制可压缩文件体积。

缺点:

  • AOF文件通常比RDB文件大。
  • 重启时加载效率可能低于RDB。
  • 在高并发下,AOF文件体积增长较快,需定期重写。

问题11:Redis过期删除策略

11、Redis 过期删除策略

方式1:定时删除
为每个设置过期时间的key创建一个定时器,到期立即删除。
特点: 对内存友好,对CPU不友好,会占用大量CPU时间处理过期键。

方式2:惰性删除
只有在获取key时才检查其是否过期,过期则删除。
特点: 对CPU友好,对内存不友好,可能导致大量过期key占用内存不被释放。

方式3:定期删除
每隔一段时间,主动检查并删除一批过期key(检查的数据库和数量由算法决定)。
特点: 上述两种策略的折衷,通过控制执行的时长和频率来平衡CPU和内存。

Redis实际采用的是 惰性删除 + 定期删除 的组合策略。

问题12:Redis的同步机制

12、Redis 的同步机制

Redis主从同步分为全量同步增量同步。从节点会先尝试增量同步,失败则进行全量同步。

增量同步:
从节点初始化完成后,主服务器执行的写命令会实时同步给从服务器。

全量同步:
从节点初次连接主节点时,会发送 psync 命令。主节点执行一次 bgsave 生成RDB文件并发送给从节点,同时将后续的写命令记录到内存缓冲区。从节点加载RDB文件后,主节点再将缓冲区中的命令发送给从节点重放,完成同步。

问题13:Redis的内存淘汰策略

13、Redis 的内存淘汰策略

当内存使用达到 maxmemory 限制时,Redis会根据配置的策略淘汰数据。

对设置了过期时间的key:

  • volatile-lru:淘汰最近最少使用的。
  • volatile-random:随机淘汰。
  • volatile-ttl:淘汰存活时间更短的。

对所有key:

  • allkeys-lru:淘汰最近最少使用的。
  • allkeys-random:随机淘汰。
  • noeviction:不淘汰,新写入操作会报错(默认)。

策略选择参考:

  • 数据访问呈幂律分布(部分高频,部分低频):用 allkeys-lru
  • 数据访问呈均匀分布:用 allkeys-random

注意: Redis的LRU并非精确算法,而是随机采样一定数量(默认为5,通过 maxmemory-samples 配置)的key,淘汰其中最近最少使用的。

问题14:Redis常见的问题

14、Redis 常见的问题

问题1:缓存穿透
指查询一个缓存和数据库中都不存在的数据,导致所有请求直达数据库。
解决:

  1. 布隆过滤器:将所有可能查询的参数哈希到足够大的位图中,查询前先校验位图,不存在则拦截。
  2. 缓存空对象:数据库查询为空时,仍将空结果缓存并设置较短过期时间。需注意数据库有数据后更新缓存。

问题2:缓存击穿
指某个热点key在缓存过期瞬间,大量请求同时击穿缓存直达数据库。
解决:

  1. 永不过期:对极热点数据设置逻辑上的永不过期。
  2. 互斥锁:缓存失效时,只允许一个线程去查询数据库并回填缓存,其他线程等待。
  3. 随机退避:缓存失效时,线程随机sleep很短时间后再重试缓存。

问题3:缓存雪崩
指大量缓存key在同一时间大面积失效,导致所有请求落向数据库。
解决:

  1. 差异化过期时间:给缓存失效时间加上一个随机值,让失效时间点分散。
  2. 二级缓存:使用本地缓存(如Ehcache)作为一级缓存,Redis作为二级缓存。
  3. 高可用架构:采用Redis集群,避免单点故障。
  4. 限流降级:在缓存失效后,通过加锁或队列控制访问数据库的线程数,或对非核心服务进行降级。

问题15:Redis如何进行缓存降级

15、Redis 如何进行缓存降级?

缓存降级本质上是服务降级。在系统面临巨大流量压力或部分服务出现问题时,为了保证核心业务链路的可用性,有策略地暂时降低非核心服务的质量或直接关闭。

降级需要事先规划,明确哪些服务可降级。策略包括:

  • 服务方式:直接拒绝服务、延迟服务、返回兜底数据。
  • 服务范围:关闭非核心功能、只读服务、简化流程。

降级的核心目标是:有损服务,但保障核心

问题16:Redis如何进行缓存更新

16、Redis 如何进行缓存更新?

  1. 数据实时同步更新:数据库更新后,同步或异步通知缓存更新。保证强一致性,但对数据库有压力。
  2. 异步消息队列更新:数据库更新后,发送消息到队列,由消费者异步更新缓存。保证最终一致性。
  3. 定时任务更新:通过定时任务按频率全量或增量刷新缓存。一致性最弱,可能有延迟。
  4. 读时更新(Cache Aside Pattern):读缓存,没有则读数据库并写入缓存;写时直接更新数据库,并删除缓存。这是最常用的模式。

问题17:Redis如何缓存热点Key

17、Redis 如何缓存热点Key?

热点Key指访问频率极高的key。
解决思路:

  1. 本地缓存:在应用层使用本地缓存(如Guava Cache),缓存极热点数据,减少对Redis的访问。
  2. Key拆分:将一个热点Key的数据拆分成多个子Key,分布到不同节点,减轻单点压力。
  3. 永不过期:对热点Key设置合理的逻辑永不过期,通过后台任务异步更新。
  4. 限流与熔断:对热点Key的访问实施限流,保护下游系统。

问题18:Redis的哨兵模式

18、Redis 的哨兵模式

Sentinel(哨兵)是Redis官方提供的高可用性解决方案,用于监控主从节点状态。

主要作用:

  1. 监控:持续检查主节点和从节点是否正常运行。
  2. 通知:当被监控的Redis实例出现故障时,可以通过API通知管理员。
  3. 自动故障转移:主节点故障时,自动将一个从节点提升为新主节点,并让其他从节点复制新主节点。同时,通知客户端连接新的主节点。

问题19:Redis的Pipeline管道

19、Redis 的Pipeline管道

问题背景:
Redis基于TCP请求/响应模型,每条命令都需经历网络往返。频繁的请求会带来巨大的网络延迟开销。

Pipeline解决:
Pipeline允许客户端一次性发送多个命令,而无需等待每个命令的响应,服务器会按顺序处理并一次性返回所有结果。这能将多次网络往返时间缩减为一次,极大提升批量操作的性能。

注意:

  • Pipeline基于队列实现,保证命令顺序性。
  • 一次性打包过多命令会占用大量客户端内存。Redis客户端通常有默认提交大小(如53条)。
  • Pipeline不保证原子性,只是批量发送。在Redis集群模式下无法直接使用跨slot的Pipeline。

问题20:Redis如何进行性能优化

20、Redis 如何进行性能优化?

  1. 持久化优化:主节点避免做耗时长的 bgsave,可在从节点开启AOF并配置为 everysec
  2. 网络优化:主从节点尽量部署在同一局域网,保证复制速度和连接稳定性。
  3. 架构优化:主从采用链式结构(Master -> Slave1 -> Slave2),而非星型,便于故障切换。避免在压力大的主节点上直接添加过多从节点。
  4. 命令优化:使用批量操作命令(如 mgetmset)、Pipeline,避免大量循环单条命令。
  5. 键名优化:在保证可读性前提下,使用简短的键名。
  6. 内存优化:合理选择数据结构,使用整数对象池,设置过期时间。

问题21:Redis的数据一致性问题

21、Redis 的数据一致性问题

指缓存与数据库之间的数据不一致。常用更新策略:

方式1:先更新数据库,再更新缓存
问题:并发下可能因更新顺序导致缓存脏数据;且更新缓存可能失败。

方式2:先删除缓存,再更新数据库
问题:在“删缓存”后、“更新数据库”前,若有读请求将旧数据再次加载到缓存,会导致不一致。(可通过“延时双删”缓解)

方式3:延时双删策略

  1. 删除缓存。
  2. 更新数据库。
  3. 休眠一段时间(如几百毫秒)。
  4. 再次删除缓存。
    目的:清除在第二步期间可能被读请求加载的旧缓存。

方式4:异步订阅Binlog
通过 canal 等工具订阅数据库的Binlog变化,异步更新或删除缓存。对业务代码无侵入,最终一致性。

问题22:Redis如何实现事务

22、Redis 如何实现事务?

Redis事务并非严格意义上的ACID事务(如执行EXEC时宕机,部分命令可能未执行),但提供了命令打包顺序执行的能力。

事务相关四个原语:

  1. multi:开启事务,后续命令放入队列。
  2. exec:执行事务队列中的所有命令。
  3. discard:取消事务,清空队列。
  4. watch:监视一个或多个key,如果在 exec 前被其他客户端修改,则事务失败(乐观锁)。

特性:

  • 事务中的命令按顺序串行执行,不会被其他客户端命令打断。
  • Redis事务不支持回滚。如果某条命令失败,后续命令仍会继续执行。

问题23:Redis如何实现集群

23、Redis 如何实现集群?

方式1:Redis Cluster(官方方案,>=3.0)
采用去中心化架构,数据分片存储在多个节点上。分布式算法是哈希槽,而非一致性哈希。整个集群有16384个槽,每个节点负责一部分。支持主从复制和自动故障转移。

方式2:Twemproxy(代理方案)
Twitter开源的一个轻量级代理,可管理后端多个Redis实例。客户端连接Twemproxy,由它通过一致性哈希等算法将请求转发到具体节点。具备节点故障自动剔除与重试机制。

方式3:Codis
一个分布式Redis解决方案,在客户端与Redis服务器之间引入了Codis Proxy和Codis Dashboard等组件,支持平滑扩缩容和数据迁移。

问题24:Redis的脑裂问题

24、Redis 的脑裂问题

什么是脑裂?
当主节点与从节点、哨兵因网络分区被隔离时,哨兵可能在新分区中选举出新的主节点。此时集群中出现两个“主节点”,客户端可能向旧主节点写入数据,导致数据丢失和不一致。

如何解决?
通过配置以下参数,要求主节点必须有足够多、延迟足够低的从节点连接时才能接收写请求。

min-slaves-to-write 3    # 主节点至少需要3个从节点连接 (新版本参数名:min-replicas-to-write)
min-slaves-max-lag 10    # 从节点最长滞后时间10秒 (新版本参数名:min-replicas-max-lag)

配置后,如果发生脑裂,原主节点因连接从节点数不足会拒绝写请求,从而减少数据丢失。

问题25:Redis如何实现异步队列

25、Redis 如何实现异步队列?

  1. 使用 List 结构

    • 生产者:RPUSH key value
    • 消费者:LPOP keyBLPOP key timeout (阻塞版本,无消息时等待)
      使用 BLPOP 可避免消费者轮询带来的CPU消耗。
  2. 使用 Pub/Sub 模式
    实现一对多的消息广播。但它是非持久化的,如果消费者中途下线,将丢失其离线期间的消息。

问题26:Redis如何实现延迟队列

26、Redis 如何实现延迟队列?

使用 ZSet 实现。将消息内容作为 member,投递的时间戳作为 score。

  • 生产者ZADD delay_queue <timestamp> message
  • 消费者:轮询调用 ZRANGEBYSCORE delay_queue 0 <current_timestamp> WITHSCORES 获取到期的消息进行处理,处理完后用 ZREM 移除。

问题27:Redis中哈希槽的概念

27、Redis 中哈希槽的概念

Redis Cluster 采用哈希槽进行数据分片。整个键空间被划分为 16384 个槽。每个键通过 CRC16 校验后,对 16384 取模来决定归属于哪个槽。集群中的每个节点负责处理一部分哈希槽,从而实现了数据的自动分片和定位。

问题28:Redis的应用场景

28、Redis 的应用场景

  1. 数据缓存:最经典场景,加速数据访问。
  2. 排行榜:利用ZSet的有序特性实现。
  3. 计数器:利用 INCR 命令的原子性实现文章阅读数、点赞数等。
  4. 分布式Session:在集群环境中集中管理用户Session。
  5. 分布式锁:利用 SETNX 或 Redlock 算法实现。
  6. 最新列表:使用 List 的 LPUSHLTRIM 命令维护固定长度的最新动态。
  7. 位统计:使用 BitMap 统计用户签到、活跃状态等。
  8. 消息队列:使用 List 或 Stream 类型实现简单的消息队列。
  9. 社交关系:使用 Set 求交集、并集实现共同关注、好友推荐。
  10. 地理位置:使用 Geo 类型存储和计算附近的人、店铺。

问题29:Redis实现签到表功能

29、Redis 实现签到表功能

方式1:使用 Set 结构
以日期为key,用户ID集合为value。

  • 签到:SADD sign:20240517 user_id
  • 查询是否签到:SISMEMBER sign:20240517 user_id
  • 统计签到人数:SCARD sign:20240517

方式2:使用 BitMap 结构
u:sign:user_id:yyyyMM 为key,每月一个位图,位的位置代表日期(0-30)。

# 用户ID 1000 在 2月17日签到(偏移量从0开始,故为16)
SETBIT u:sign:1000:201902 16 1

# 检查2月17日是否签到
GETBIT u:sign:1000:201902 16

# 统计2月份总签到次数
BITCOUNT u:sign:1000:201902

# 获取首次签到的日期(返回偏移量,+1即日期)
BITPOS u:sign:1000:201902 1

两者对比:

  • Set:内存占用与签到用户数成正比。
  • BitMap:内存占用与最大用户ID有关(最坏情况)。它非常节省空间,但仅适合存储布尔状态,且最大可存储2^32位(512MB)数据。

问题30:Redis中ZSet的底层实现

30、Redis 中 ZSet 的底层实现

Redis的ZSet内部使用跳表(Skip List) 而非红黑树实现。

什么是跳表?
跳表是一种随机化的数据结构,本质上是可以支持二分查找的有序链表。它在原有链表的基础上,构建了多级索引,通过索引实现快速查找、插入和删除,时间复杂度为O(log n)。

跳表数据结构示意图

跳表特点总结:

  1. 可进行二分查找的有序链表。
  2. 插入元素时,随机生成其索引层数。
  3. 最底层(L0)包含所有元素。
  4. 时间复杂度接近平衡二叉树。

为何选择跳表而非红黑树?
红黑树能高效完成ZSet的大部分操作(插入、删除、查找、有序遍历),时间复杂度也是O(log n)。然而,对于 “查找区间内所有元素” 这个操作,在红黑树上需要进行中序遍历,效率不如跳表。在跳表中,只需定位到区间两端在底层的位置,然后顺序遍历即可,非常高效。此外,跳表的实现相对更简单。


掌握这些核心知识点,能让你在技术面试中更加游刃有余。技术的深度源于持续的积累与思考。如果你对更多系统设计、分布式系统或数据库相关话题感兴趣,欢迎在云栈社区与我们进一步交流探讨。




上一篇:RAG系统生产部署的五大工程挑战:从数据处理到安全合规
下一篇:Redis核心原理与实践:从数据结构到高可用架构深度解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 20:20 , Processed in 0.753348 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表