Redis常见面试题详解

文章目录

1. Redis

1.1 Redis可以用来做什么?

参考答案

  1. Redis最常用来做缓存,是实现分布式缓存的首先中间件;
  2. Redis可以作为数据库,实现诸如点赞、关注、排行等对性能要求极高的互联网需求;
  3. Redis可以作为计算工具,能用很小的代价,统计诸如PV/UV、用户在线天数等数据;
  4. Redis还有很多其他的使用场景,例如:可以实现分布式锁,可以作为消息队列使用。
1.2 Redis和传统的关系型数据库有什么不同?

参考答案

Redis是一种基于键值对的NoSQL数据库,而键值对的值是由多种数据结构和算法组成的。Redis的数据都存储于内存中,因此它的速度惊人,读写性能可达10万/秒,远超关系型数据库。

关系型数据库是基于二维数据表来存储数据的,它的数据格式更为严谨,并支持关系查询。关系型数据库的数据存储于磁盘上,可以存放海量的数据,但性能远不如Redis。

1.3 Redis有哪些数据类型?

参考答案

  1. Redis支持5种核心的数据类型,分别是字符串、哈希、列表、集合、有序集合;
  2. Redis还提供了Bitmap、HyperLogLog、Geo类型,但这些类型都是基于上述核心数据类型实现的;
  3. Redis在5.0新增加了Streams数据类型,它是一个功能强大的、支持多播的、可持久化的消息队列。
1.4 Redis是单线程的,为什么还能这么快?

参考答案

  1. 对服务端程序来说,线程切换和锁通常是性能杀手,而单线程避免了线程切换和竞争所产生的消耗;
  2. Redis的大部分操作是在内存上完成的,这是它实现高性能的一个重要原因;
  3. Redis采用了IO多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。

关于Redis的单线程架构实现,如下图:

img

1.5 Redis在持久化时fork出一个子进程,这时已经有两个进程了,怎么能说是单线程呢?

参考答案

Redis是单线程的,主要是指Redis的网络IO和键值对读写是由一个线程来完成的。而Redis的其他功能,如持久化、异步删除、集群数据同步等,则是依赖其他线程来执行的。所以,说Redis是单线程的只是一种习惯的说法,事实上它的底层不是单线程的。

1.6 set和zset有什么区别?

参考答案

set:

  • 集合中的元素是无序、不可重复的,一个集合最多能存储232-1个元素;
  • 集合除了支持对元素的增删改查之外,还支持对多个集合取交集、并集、差集。

zset:

  • 有序集合保留了集合元素不能重复的特点;
  • 有序集合会给每个元素设置一个分数,并以此作为排序的依据;
  • 有序集合不能包含相同的元素,但是不同元素的分数可以相同。
1.7 说一下Redis中的watch命令

参考答案

很多时候,要确保事务中的数据没有被其他客户端修改才执行该事务。Redis提供了watch命令来解决这类问题,这是一种乐观锁的机制。客户端通过watch命令,要求服务器对一个或多个key进行监视,如果在客户端执行事务之前,这些key发生了变化,则服务器将拒绝执行客户端提交的事务,并向它返回一个空值。

1.8 说说Redis中List结构的相关操作

参考答案

列表是线性有序的数据结构,它内部的元素是可以重复的,并且一个列表最多能存储2^32-1个元素。列表包含如下的常用命令:

  • lpush/rpush:从列表的左侧/右侧添加数据;
  • lrange:指定索引范围,并返回这个范围内的数据;
  • lindex:返回指定索引处的数据;
  • lpop/rpop:从列表的左侧/右侧弹出一个数据;
  • blpop/brpop:从列表的左侧/右侧弹出一个数据,若列表为空则进入阻塞状态。
1.9 你要如何设计Redis的过期时间?

参考答案

  1. 热点数据不设置过期时间,使其达到“物理”上的永不过期,可以避免缓存击穿问题;
  2. 在设置过期时间时,可以附加一个随机数,避免大量的key同时过期,导致缓存雪崩。
1.10 Redis中,sexnx命令的返回值是什么,如何使用该命令实现分布式锁?

参考答案

setnx命令返回整数值,当返回1时表示设置值成果,当返回0时表示设置值失败(key已存在)。

一般我们不建议直接使用setnx命令来实现分布式锁,因为为了避免出现死锁,我们要给锁设置一个自动过期时间。而setnx命令和设置过期时间的命令不是原子的,可能加锁成果而设置过期时间失败,依然存在死锁的隐患。对于这种情况,Redis改进了set命令,给它增加了nx选项,启用该选项时set命令的效果就会setnx一样了。

采用Redis实现分布式锁,就是在Redis里存一份代表锁的数据,通常用字符串即可。采用改进后的setnx命令(即set...nx...命令)实现分布式锁的思路,以及优化的过程如下:

加锁:

第一版,这种方式的缺点是容易产生死锁,因为客户端有可能忘记解锁,或者解锁失败。

setnx key value

第二版,给锁增加了过期时间,避免出现死锁。但这两个命令不是原子的,第二步可能会失败,依然无法避免死锁问题。

setnx key value``expire key seconds

第三版,通过“set…nx…”命令,将加锁、过期命令编排到一起,它们是原子操作了,可以避免死锁。

set key value nx ex seconds

解锁:

解锁就是删除代表锁的那份数据。

del key

问题:

看起来已经很完美了,但实际上还有隐患,如下图。进程A在任务没有执行完毕时,锁已经到期被释放了。等进程A的任务执行结束后,它依然会尝试释放锁,因为它的代码逻辑就是任务结束后释放锁。但是,它的锁早已自动释放过了,它此时释放的可能是其他线程的锁。

img

想要解决这个问题,我们需要解决两件事情:

  1. 在加锁时就要给锁设置一个标识,进程要记住这个标识。当进程解锁的时候,要进行判断,是自己持有的锁才能释放,否则不能释放。可以为key赋一个随机值,来充当进程的标识。
  2. 解锁时要先判断、再释放,这两步需要保证原子性,否则第二步失败的话,就会出现死锁。而获取和删除命令不是原子的,这就需要采用Lua脚本,通过Lua脚本将两个命令编排在一起,而整个Lua脚本的执行是原子的。

按照以上思路,优化后的命令如下:

# 加锁
set key random-value nx ex seconds 

# 解锁
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
1.11 说一说Redis的持久化策略

参考答案

Redis支持RDB持久化、AOF持久化、RDB-AOF混合持久化这三种持久化方式。

RDB:

RDB(Redis Database)是Redis默认采用的持久化方式,它以快照的形式将进程数据持久化到硬盘中。RDB会创建一个经过压缩的二进制文件,文件以“.rdb”结尾,内部存储了各个数据库的键值对数据等信息。RDB持久化的触发方式有两种:

  • 手动触发:通过SAVE或BGSAVE命令触发RDB持久化操作,创建“.rdb”文件;
  • 自动触发:通过配置选项,让服务器在满足指定条件时自动执行BGSAVE命令。

其中,SAVE命令执行期间,Redis服务器将阻塞,直到“.rdb”文件创建完毕为止。而BGSAVE命令是异步版本的SAVE命令,它会使用Redis服务器进程的子进程,创建“.rdb”文件。BGSAVE命令在创建子进程时会存在短暂的阻塞,之后服务器便可以继续处理其他客户端的请求。总之,BGSAVE命令是针对SAVE阻塞问题做的优化,Redis内部所有涉及RDB的操作都采用BGSAVE的方式,而SAVE命令已经废弃!

BGSAVE命令的执行流程,如下图:

img

BGSAVE命令的原理,如下图:

img

RDB持久化的优缺点如下:

  • 优点:RDB生成紧凑压缩的二进制文件,体积小,使用该文件恢复数据的速度非常快;

  • 缺点:BGSAVE每次运行都要执行fork操作创建子进程,属于重量级操作,不宜频繁执行,

    所以RDB持久化没办法做到实时的持久化。

AOF:

AOF(Append Only File),解决了数据持久化的实时性,是目前Redis持久化的主流方式。AOF以独立日志的方式,记录了每次写入命令,重启时再重新执行AOF文件中的命令来恢复数据。AOF的工作流程包括:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load),如下图:

img

AOF默认不开启,需要修改配置项来启用它:

appendonly yes                           # 启用AOF
appendfilename "appendonly.aof"      # 设置文件名

AOF以文本协议格式写入命令,如:

*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n

文本协议格式具有如下的优点:

  1. 文本协议具有很好的兼容性;
  2. 直接采用文本协议格式,可以避免二次处理的开销;
  3. 文本协议具有可读性,方便直接修改和处理。

AOF持久化的文件同步机制:

为了提高程序的写入性能,现代操作系统会把针对硬盘的多次写操作优化为一次写操作。

  1. 当程序调用write对文件写入时,系统不会直接把书记写入硬盘,而是先将数据写入内存的缓冲区中;
  2. 当达到特定的时间周期或缓冲区写满时,系统才会执行flush操作,将缓冲区中的数据冲洗至硬盘中;

这种优化机制虽然提高了性能,但也给程序的写入操作带来了不确定性。

  1. 对于AOF这样的持久化功能来说,冲洗机制将直接影响AOF持久化的安全性;
  2. 为了消除上述机制的不确定性,Redis向用户提供了appendfsync选项,来控制系统冲洗AOF的频率;
  3. Linux的glibc提供了fsync函数,可以将指定文件强制从缓冲区刷到硬盘,上述选项正是基于此函数。

appendfsync选项的取值和含义如下:

img

AOF持久化的优缺点如下:

  • 优点:与RDB持久化可能丢失大量的数据相比,AOF持久化的安全性要高很多。通过使用everysec选项,用户可以将数据丢失的时间窗口限制在1秒之内。
  • 缺点:AOF文件存储的是协议文本,它的体积要比二进制格式的”.rdb”文件大很多。AOF需要通过执行AOF文件中的命令来恢复数据库,其恢复速度比RDB慢很多。AOF在进行重写时也需要创建子进程,在数据库体积较大时将占用大量资源,会导致服务器的短暂阻塞。

RDB-AOF混合持久化:

Redis从4.0开始引入RDB-AOF混合持久化模式,这种模式是基于AOF持久化构建而来的。用户可以通过配置文件中的“aof-use-rdb-preamble yes”配置项开启AOF混合持久化。Redis服务器在执行AOF重写操作时,会按照如下原则处理数据:

  • 像执行BGSAVE命令一样,根据数据库当前的状态生成相应的RDB数据,并将其写入AOF文件中;
  • 对于重写之后执行的Redis命令,则以协议文本的方式追加到AOF文件的末尾,即RDB数据之后。

通过使用RDB-AOF混合持久化,用户可以同时获得RDB持久化和AOF持久化的优点,服务器既可以通过AOF文件包含的RDB数据来实现快速的数据恢复操作,又可以通过AOF文件包含的AOF数据来将丢失数据的时间窗口限制在1s之内。

1.12 如何实现Redis的高可用?

参考答案

实现Redis的高可用,主要有哨兵和集群两种方式。

哨兵:

Redis Sentinel(哨兵)是一个分布式架构,它包含若干个哨兵节点和数据节点。每个哨兵节点会对数据节点和其余的哨兵节点进行监控,当发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它就会与其他的哨兵节点进行协商,当多数哨兵节点都认为主节点不可达时,它们便会选举出一个哨兵节点来完成自动故障转移的工作,同时还会将这个变化实时地通知给应用方。整个过程是自动的,不需要人工介入,有效地解决了Redis的高可用问题!

一组哨兵可以监控一个主节点,也可以同时监控多个主节点,两种情况的拓扑结构如下图:

img

哨兵节点包含如下的特征:

  1. 哨兵节点会定期监控数据节点,其他哨兵节点是否可达;
  2. 哨兵节点会将故障转移的结果通知给应用方;
  3. 哨兵节点可以将从节点晋升为主节点,并维护后续正确的主从关系;
  4. 哨兵模式下,客户端连接的是哨兵节点集合,从中获取主节点信息;
  5. 节点的故障判断是由多个哨兵节点共同完成的,可有效地防止误判;
  6. 哨兵节点集合是由多个哨兵节点组成的,即使个别哨兵节点不可用,整个集合依然是健壮的;
  7. 哨兵节点也是独立的Redis节点,是特殊的Redis节点,它们不存储数据,只支持部分命令。

集群:

Redis集群采用虚拟槽分区来实现数据分片,它把所有的键根据哈希函数映射到0-16383整数槽内,计算公式为slot=CRC16(key)&16383,每一个节点负责维护一部分槽以及槽所映射的键值数据。虚拟槽分区具有如下特点:

  1. 解耦数据和节点之间的关系,简化了节点扩容和收缩的难度;
  2. 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据;
  3. 支持节点、槽、键之间的映射查询,用于数据路由,在线伸缩等场景。

Redis集群中数据的分片逻辑如下图:

img

1.13 Redis的主从同步是如何实现的?

参考

<think>好的,用户现在需要一套详细的Redis常见面试题。首先,我得回顾一下Redis的核心知识点,确保覆盖各个方面。用户之前提到要作为Java专家回答问题,但这次是Redis面试题,可能需要注意结合Java应用场景。 首先,Redis的基本概念,比如它是什么,数据结构,持久化机制,这些是基础问题。然后是高可用方案,主从复制、哨兵、集群的区别和应用场景。接着是内存管理、淘汰策略,以及与Java相关的客户端,比如Jedis和Lettuce的区别。 缓存相关问题也是重点,比如缓存穿透、雪崩、击穿,以及解决方案。事务和管道机制,Redis的线程模型,单线程为什么高效。还有分布式锁的实现,可能出现的问题,比如死锁、锁续期。发布订阅、Lua脚本的应用,性能优化,慢查询处理,数据备份方式,Redis的适用场景,以及与其他数据库的对比。可能需要提到Redis6的新特性,如多线程IO。 在组织问题时,要确保每个问题都有深度,不仅问定义,还要问原理和解决策略。例如,缓存穿透要解释现象、原因,再给出布隆过滤器、空值缓存等方法。同时,结合Java中的实际应用,比如Redisson框架的使用。最后,检查是否有遗漏的重要知识点,比如Redis的持久化机制RDB和AOF的优缺点,哨兵模式的选举过程,集群的数据分片方式等。确保问题覆盖全面,层次分明,由浅入深,适合不同层次的面试准备。</think>以下是 Redis 常见面试题详解: --- ### 1. Redis 是什么?主要特点是什么? **答案**:Redis(Remote Dictionary Server)是一个基于内存的高性能键值数据库,支持多种数据结构(字符串、哈希、列表、集合等)。特点包括: - 单线程模型(Redis 6+ 支持多线程IO) - 持久化机制(RDB/AOF) - 支持事务和 Lua 脚本 - 发布订阅功能 - 高可用方案(主从复制、哨兵、集群) --- ### 2. Redis 的持久化机制有哪些?区别是什么? **答案**: 1. **RDB(Redis Database)**:通过快照保存数据,二进制格式。 - 优点:恢复速度快,文件紧凑 - 缺点:可能丢失最后一次快照后的数据 2. **AOF(Append-Only File)**:记录写操作命令。 - 优点:数据丢失少(可配置同步频率) - 缺点:文件较大,恢复速度慢 **配置示例**: ```properties # 启用 RDB save 900 1 # 900秒内至少1次修改触发保存 save 300 10 # 300秒内至少10次修改 # 启用 AOF appendonly yes appendfsync everysec # 每秒同步一次 ``` --- ### 3. 缓存穿透、缓存雪崩、缓存击穿的区别及解决方案? - **缓存穿透**:查询不存在的数据(绕过缓存直接查数据库)。 **解决方案**:布隆过滤器、缓存空值。 - **缓存雪崩**:大量缓存同时过期导致请求打到数据库。 **解决方案**:随机过期时间、集群部署、永不过期+后台更新。 - **缓存击穿**:热点数据过期后高并发请求直接访问数据库。 **解决方案**:互斥锁(如 Redis 的 `SETNX`)、永不过期策略。 --- ### 4. Redis 如何实现分布式锁? **答案**:使用 `SET key value NX EX timeout` 命令: - `NX`:仅当 key 不存在时设置 - `EX`:设置过期时间(防止死锁) **Java 示例(Jedis)**: ```java public boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime); return "OK".equals(result); } public void unlock(Jedis jedis, String lockKey, String requestId) { if (requestId.equals(jedis.get(lockKey))) { jedis.del(lockKey); } } ``` --- ### 5. Redis 主从复制原理? **答案**: 1. 从节点发送 `SYNC` 命令给主节点。 2. 主节点执行 `BGSAVE` 生成 RDB 文件,期间新写入命令缓存到缓冲区。 3. 主节点发送 RDB 文件给从节点,从节点加载数据。 4. 主节点将缓冲区中的命令发送给从节点,保持同步。 --- ### 6. Redis 集群如何分片? **答案**:采用哈希槽(Hash Slot)机制,共 16384 个槽。 - 每个键通过 `CRC16(key) % 16384` 计算槽位。 - 节点负责部分槽位,可通过 `CLUSTER ADDSLOTS` 分配。 --- ### 7. Redis 为什么快? **答案**: - 内存操作 - 单线程避免上下文切换(网络IO和键操作) - 高效数据结构(如跳跃表、压缩列表) - IO多路复用(epoll/kqueue) --- ### 8. Redis 事务与 MySQL 事务的区别? **答案**: - Redis 事务通过 `MULTI/EXEC` 实现,不支持回滚(命令错误时继续执行)。 - MySQL 事务支持 ACID 特性,错误时会回滚。 --- ### 9. 如何保证 Redis数据库的数据一致性? **答案**: - 删除缓存而非更新缓存(避免并发写导致脏数据)。 - 延迟双删:更新数据库后删除缓存,延迟后再删一次。 - 使用 Canal 监听数据库 binlog 同步更新缓存。 --- ### 10. Redis 的过期策略和内存淘汰策略? **答案**: - **过期策略**: 1. 定期删除(随机检查部分 key) 2. 惰性删除(访问时检查是否过期) - **内存淘汰策略**(`maxmemory-policy`): - `volatile-lru`:从已过期的 key 中淘汰最近最少使用的 - `allkeys-lru`:全体 key 中淘汰 LRU - `volatile-ttl`:淘汰剩余生存时间最短的 key - `noeviction`:不淘汰,返回错误(默认) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值