Redis面试题,57道Redis八股文(4.6万字286张手绘图),面渣逆袭必看👍
前言
4.6 万字 286 张手绘图,详解 57 道 Redis 面试高频题(让天下没有难背的八股),面渣背会这些 Redis 八股文,这次吊打面试官,我觉得稳了(手动 dog)。整理:沉默王二,戳转载链接,作者:三分恶,戳原文链接。
亮白版本更适合拿出来打印,这也是很多学生党喜欢的方式,打印出来背诵的效率会更高。
2025 年 04 月 27 日开始着手第二版更新。
- 对于高频题,会标注在《Java 面试指南(付费)》中出现的位置,哪家公司,原题是什么,并且会加🌟,目录一目了然;如果你想节省时间的话,可以优先背诵这些题目,尽快做到知彼知己,百战不殆。
- 区分八股精华回答版本和原理底层解释,让大家知其然知其所以然,同时又能做到面试时的高效回答。
- 结合项目(技术派、pmhub)来组织语言,让面试官最大程度感受到你的诚意,而不是机械化的背诵。
- 修复第一版中出现的问题,包括球友们的私信反馈,网站留言区的评论,以及 GitHub 仓库中的 issue,让这份面试指南更加完善。
- 增加二哥编程星球的球友们拿到的一些 offer,对面渣逆袭的感谢,以及对简历修改的一些认可,以此来激励大家,给大家更多信心。
- 优化排版,增加手绘图,重新组织答案,使其更加口语化,从而更贴近面试官的预期。
由于 PDF 没办法自我更新,所以需要最新版的小伙伴,可以微信搜【沉默王二】,或者扫描/长按识别下面的二维码,关注二哥的公众号,回复【222】即可拉取最新版本。
当然了,请允许我的一点点私心,那就是星球的 PDF 版本会比公众号早一个月时间,毕竟星球用户都付费过了,我有必要让他们先享受到一点点福利。相信大家也都能理解,毕竟在线版是免费的,CDN、服务器、域名、OSS 等等都是需要成本的。
更别说我付出的时间和精力了,大家觉得有帮助还请给个口碑,让你身边的同事、同学都能受益到。
我把二哥的 Java 进阶之路、JVM 进阶之路、并发编程进阶之路,以及所有面渣逆袭的版本都放进来了,涵盖 Java基础、Java集合、Java并发、JVM、Spring、MyBatis、计算机网络、操作系统、MySQL、Redis、RocketMQ、分布式、微服务、设计模式、Linux 等 16 个大的主题,共有 40 多万字,2000+张手绘图,可以说是诚意满满。
展示一下暗黑版本的 PDF 吧,排版清晰,字体优雅,更加适合夜服,晚上看会更舒服一点。
基础
1.🌟说说什么是 Redis?
Redis 是一种基于键值对的 NoSQL 数据库。
它主要的特点是把数据放在内存当中,相比直接访问磁盘的关系型数据库,读写速度会快很多,基本上能达到微秒级的响应。
所以在一些对性能要求很高的场景,比如缓存热点数据、防止接口爆刷,都会用到 Redis。
不仅如此,Redis 还支持持久化,可以将内存中的数据异步落盘,以便服务宕机重启后能恢复数据。
Redis 和 MySQL 的区别?
Redis 属于非关系型数据库,数据是通过键值对的形式放在内存当中的;MySQL 属于关系型数据库,数据以行和列的形式存储在磁盘当中。
实际开发中,会将 MySQL 作为主存储,Redis 作为缓存,通过先查 Redis,未命中再查 MySQL 并写回Redis 的方式来提高系统的整体性能。
项目里哪里用到了 Redis?
在技术派实战项目当中,有很多地方都用到了 Redis,比如说用户活跃排行榜用到了 zset,作者白名单用到了 set。
还有用户登录后的 Session、站点地图 SiteMap,分别用到了 Redis 的字符串和哈希表两种数据类型。
其中比较有挑战性的一个应用是,通过 Lua 脚本封装 Redis 的 setnex 命令来实现分布式锁,以保证在高并发场景下,热点文章在短时间内的高频访问不会击穿 MySQL。
部署过 Redis 吗?
第一种回答版本:
我只在本地部署过单机版,下载 Redis 的安装包,解压后运行 redis-server 命令即可。
第二种回答版本:
我有在生产环境中部署单机版 Redis,从官网下载源码包解压后执行 make && make install 编译安装。然后编辑 redis.conf 文件,开启远程访问、设置密码、限制内存、设置内存过期淘汰策略、开启 AOF 持久化等:
bind 0.0.0.0 # 允许远程访问
requirepass your_password # 设置密码
maxmemory 4gb # 限制内存,避免 OOM
maxmemory-policy allkeys-lru # 内存淘汰策略
appendonly yes # 开启 AOF 持久化
第三种回答版本:
我有使用 Docker 拉取 Redis 镜像后进行容器化部署。
docker run -d --name redis -p 6379:6379 redis:7.0-alpine
Redis 的高可用方案有部署过吗?
有部署过哨兵机制,这是一个相对成熟的高可用解决方案,我们生产环境部署的是一主两从的 Redis 实例,再加上三个 Sentinel 节点监控它们。Sentinel 的配置相对简单,主要设置了故障转移的判定条件和超时阈值。
主节点配置:
port 6379
appendonly yes
从节点配置:
replicaof 192.168.1.10 6379
哨兵节点配置:
sentinel monitor mymaster 192.168.1.10 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
当主节点发生故障时,Sentinel 能够自动检测并协商选出新的主节点,这个过程大概需要 10-15 秒。
另一个大型项目中,我们使用了 Redis Cluster 集群方案。该项目数据量大且增长快,需要水平扩展能力。我们部署了 6 个主节点,每个主节点配备一个从节点,形成了一个 3主3从 的初始集群。Redis Cluster 的设置比 Sentinel 复杂一些,需要正确配置集群节点间通信、分片映射等。
redis-server redis-7000.conf
redis-server redis-7001.conf
...
# 使用 redis-cli 创建集群
# Redis 会自动将 key 哈希到 16384 个槽位
# 主节点均分槽位,从节点自动跟随
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
Redis Cluster 最大的优势是数据自动分片,我们可以通过简单地增加节点来扩展集群容量。此外,它的故障转移也很快,通常在几秒内就能完成。
对于一些轻量级应用,我也使用过主从复制加手动故障转移的方案。主节点负责读写操作,从节点负责读操作。手动故障转移时,我们会先将从节点提升为主节点,然后重新配置其他从节点。
# 1. 取消从节点身份
redis-cli -h slaveof no one
# 2. 将其他从节点指向新的主节点
redis-cli -h slaveof
用过哪些缓存数据库,除redis以外?
技术派实战项目中还用到了 Guava Cache 和 Caffeine 作为本地缓存,Guava Cache 适合小规模缓存,Caffeine 性能更好,支持更多高级特性。
Caffeine 通常用来作为二级缓存来使用,主要用于存储一些不经常变动的数据,以减轻 Redis 的压力。
- Java 面试指南(付费)收录的华为一面原题:说下 Redis 和 HashMap 的区别
- Java 面试指南(付费)收录的字节跳动商业化一面的原题:Redis 和 MySQL 的区别
- Java 面试指南(付费)收录的农业银行面经同学 7 Java 后端面试原题:Redis 相关的基础知识
- Java 面试指南(付费)收录的华为 OD 面经同学 1 一面面试原题:Redis 的了解, 部署方案?
- Java 面试指南(付费)收录的农业银行面经同学 3 Java 后端面试原题:项目里哪里用到了 Redis
- Java 面试指南(付费)收录的 360 面经同学 3 Java 后端技术一面面试原题:用过 redis 吗 用来干什么
- Java 面试指南(付费)收录的招商银行面经同学 6 招银网络科技面试原题:了解 MySQL、Redis 吗?
- Java 面试指南(付费)收录的百度面经同学 1 文心一言 25 实习 Java 后端面试原题:项目中什么地方使用了 redis 缓存,redis 为什么快?
- Java 面试指南(付费)收录的国企零碎面经同学 9 面试原题:数据库用什么多(说了 Mysql 和 Redis)
- Java 面试指南(付费)收录的荣耀面经同学 4 面试原题:Redis和MySQL的区别?
- Java 面试指南(付费)收录的海康威视同学 4面试原题:Redis部署
- Java 面试指南(付费)收录的华为 OD 面经同学 1 一面面试原题:Redis 的了解, 部署方案?
- Java 面试指南(付费)收录的同学 30 腾讯音乐面试原题:redis的部署方式都有哪些呢,各自有什么优缺点?
memo:2025 年 9 月 23 日修改至此,今天帮球友修改简历的时候,收到一位球友的反馈说,从 8.16 加入星球以来,每天都在星球里充电学习,学到了很多东西。对于这种正反馈我是非常开心的。
2.Redis 可以用来干什么?
Redis 可以用来做缓存,比如说把高频访问的文章详情、商品信息、用户信息放入 Redis 当中,并通过设置过期时间来保证数据一致性,这样就可以减轻数据库的访问压力。
Redis 的 Zset 还可以用来实现积分榜、热搜榜,通过 score 字段进行排序,然后取前 N 个元素,就能实现 TOPN 的榜单功能。
利用 Redis 的 SETNX 命令或者 Redisson 还可以实现分布式锁,确保同一时间只有一个节点可以持有锁;为了防止出现死锁,可以给锁设置一个超时时间,到期后自动释放;并且最好开启一个监听线程,当任务尚未完成时给锁自动续期。
如果是秒杀接口,还可以使用 Lua 脚本来实现令牌桶算法,限制每秒只能处理 N 个请求。
-- KEYS[1]: 令牌桶的key
-- ARGV[1]: 桶容量
-- ARGV[2]: 令牌生成速率(每秒)
-- ARGV[3]: 当前时间戳(秒)
local bucket = redis.call('HMGET', KEYS[1], 'tokens', 'timestamp')
local tokens = tonumber(bucket[1]) or ARGV[1]
local last_time = tonumber(bucket[2]) or ARGV[3]
local rate = tonumber(ARGV[2])
local capacity = tonumber(ARGV[1])
local now = tonumber(ARGV[3])
-- 计算新令牌数
local delta = math.max(0, now - last_time)
local add_tokens = delta * rate
tokens = math.min(capacity, tokens + add_tokens)
last_time = now
local allowed = 0
if tokens >= 1 then
tokens = tokens - 1
allowed = 1
end
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'timestamp', last_time)
redis.call('EXPIRE', KEYS[1], 3600) -- 过期时间可自定义
return allowed
在 Java 中调用 Lua 脚本:
// 令牌桶参数
int capacity = 10; // 桶容量
int rate = 2; // 每秒2个令牌
long now = System.currentTimeMillis() / 1000;
String key = "token_bucket:user:123";
// 调用 Lua 脚本,返回 1 表示通过,0 表示被限流
Long allowed = (Long) redis.eval(luaScript, 1, key, String.valueOf(capacity), String.valueOf(rate), String.valueOf(now));
redis做缓存要考虑哪些问题,在业务方面呢
一类是经典的缓存系统设计问题(穿透、击穿、雪崩),另一类是与业务逻辑紧密相关的业务缓存问题(数据一致性、缓存粒度等)。
当修改了数据库的数据后,如何保证缓存里的数据也同步更新?如果处理不好,用户就会看到“脏数据”。
另外就是我们应该缓存一个完整的、包含各种关联信息的复杂对象,还是只缓存那些最常用的基础字段?
- Java 面试指南(付费)收录的农业银行面经同学 7 Java 后端面试原题:Redis 相关的基础知识
- Java 面试指南(付费)收录的字节跳动同学 7 Java 后端实习一面的原题:讲一下为什么要用 Redis 去存权限列表?
- Java 面试指南(付费)收录的字节跳动同学 20 测开一面的原题:redis 有什么好处,为什么用 redis
memo:2025 年 4 月 28 日修改至此,今天帮球友修改简历的时候,碰到一位东南大学本硕连读的球友,星球能来这么多优秀的球友,真的很开心啊。
3.🌟Redis有哪些数据类型?
Redis 支持五种基本数据类型,分别是字符串、列表、哈希、集合和有序集合。
还有三种扩展数据类型,分别是用于位级操作的 Bitmap、用于基数估算的 HyperLogLog、支持存储和查询地理坐标的 GEO。
详细介绍下字符串?
字符串是最基本的数据类型,可以存储文本、数字或者二进制数据,最大容量是 512 MB。
适合缓存单个对象,比如验证码、token、计数器等。
详细介绍下列表?
列表是一个有序的元素集合,支持从头部或尾部插入/删除元素,常用于消息队列或任务列表。
详细介绍下哈希?
哈希是一个键值对集合,适合存储对象,如商品信息、用户信息等。比如说 value = {name: '沉默王二', age: 18}。
详细介绍下集合?
集合是无序且不重复的,支持交集、并集操作,查询效率能达到 O(1) 级别,主要用于去重、标签、共同好友等场景。
详细介绍下有序集合?
有序集合的元素按分数进行排序,支持范围查询,适用于排行榜或优先级队列。
详细介绍下Bitmap?
Bitmap 可以把一组二进制位紧凑地存储在一块连续内存中,每一位代表一个对象的状态,比如是否签到、是否活跃等。
比如用户 0 的已签到 1、用户 1 未签到 0、用户 2 已签到,Redis 就会把这些状态放进一个连续的二进制串 101,1 亿用户签到仅需 100,000,000 / 8 / 1024 ≈ 12MB 的空间,真的省到离谱。
详细介绍下HyperLogLog?
HyperLogLog 是一种用于基数统计的概率性数据结构,可以在仅有 12KB 的内存空间下,统计海量数据集中不重复元素的个数,误差率仅 0.81%。
底层基于 LogLog 算法改进,先把每个元素哈希成一个二进制串,然后取前 14 位进行分组,放到 16384 个桶中,记录每组最大的前导零数量,最后用一个近似公式推算出总体的基数。
$2^{14}$个桶,每个桶 6 Bit,刚好
16384 * 6 /8 / 1024 K = 12KB,8 bit = 1 byte。
举个超简单的例子,假设有一个神奇的哈希函数,可以把元素散列成一个二进制数,比如:
| 元素 | 哈希值 | 前导零个数 |
|---|---|---|
| userA | 000100101… | 3 |
| userB | 001010011… | 2 |
| userC | 000000101… | 6 |
可以发现,哈希值越长前导零越多,也就说明集合里的元素越多。
大型网站 UV 统计系统示例:
public class UVCounter {
private Jedis jedis;
public void recordVisit(String date, String userId) {
String key = "uv:" + date;
jedis.pfadd(key, userId);
}
public long getUV(String date) {
return jedis.pfcount("uv:" + date);
}
public long getUVBetween(String startDate, String endDate) {
List keys = getDateKeys(startDate, endDate);
return jedis.pfcount(keys.toArray(new String[0]));
}
}
详细介绍下GEO?
GEO 用于存储和查询地理位置信息,可以用来计算两点之间的距离,查找某位置半径内的其他元素。
常见的应用场景包括:附近的人或者商家、计算外卖员和商家的距离、判断用户是否进入某个区域等。
底层基于 ZSet 实现,通过 Geohash 算法把经纬度编码成 score。
比如说查询附近的商家时,Redis 会根据中心点经纬度反推可能的 Geohash 范围, 在 ZSet 上做范围查询,拿到候选点后,用 Haversine 公式精确计算球面距离,筛选出最终符合要求的位置。
public class NearbyShopService {
private Jedis jedis;
private static final String SHOP_KEY = "shops:geo";
// 添加商铺
public void addShop(String shopId, double longitude, double latitude) {
jedis.geoadd(SHOP_KEY, longitude, latitude, shopId);
}
// 查询附近的商铺
public List getNearbyShops(
double longitude,
double latitude,
double radiusKm) {
return jedis.georadius(SHOP_KEY,
longitude,
latitude,
radiusKm,
GeoUnit.KM,
GeoRadiusParam.geoRadiusParam()
.withCoord()
.withDist()
.sortAscending()
.count(20));
}
// 计算两个商铺之间的距离
public double getShopDistance(String shop1Id, String shop2Id) {
return jedis.geodist(SHOP_KEY,
shop1Id,
shop2Id,
GeoUnit.KILOMETERS);
}
}
为什么使用 hash 类型而不使用 string 类型序列化存储?
Hash 可以只读取或者修改某一个字段,而 String 需要一次性把整个对象取出来。
比如说有一个用户对象 user = {name: '沉默王二', age: 18},如果使用 Hash 存储,可以直接修改 age 字段:
redis.hset("user:1", "age", 19);
如果使用 String 存储,需要先取出整个对象,修改后再存回去:
String userJson = redis.get("user:1");
User user = JSON.parseObject(userJson, User.class);
user.setAge(19);
redis.set("user:1", JSON.toJSONString(user));
- Java 面试指南(付费)收录的字节跳动商业化一面的原题:说说 Redis 的 zset,什么是跳表,插入一个节点要构建几层索引
- Java 面试指南(付费)收录的字节跳动面经同学 9 飞书后端技术一面面试原题:Redis 的数据类型,ZSet 的实现
- Java 面试指南(付费)收录的小米暑期实习同学 E 一面面试原题:你对 Redis 了解多少,说说常见的数据结构和应用场景
- Java 面试指南(付费)收录的腾讯面经同学 23 QQ 后台技术一面面试原题:Redis 的数据类型
- Java 面试指南(付费)收录的快手面经同学 7 Java 后端技术一面面试原题:说一下 Redis 常用的数据结构
- Java 面试指南(付费)收录的农业银行面经同学 7 Java 后端面试原题:Redis 相关的基础知识
- Java 面试指南(付费)收录的华为面经同学 11 面试原题:项目中使用了 redis,redis 有哪些数据类型?分别使用的场景是什么?什么使用 hash 类型而不使用 string 类型序列化存储?
- Java 面试指南(付费)收录的 OPPO 面经同学 1 面试原题:Redis常见数据结构
- Java 面试指南(付费)收录的美团同学 9 一面面试原题:redis的数据结构类型?
- Java 面试指南(付费)收录的阿里云面经同学 22 面经:redis高级数据结构的使用场景
- Java 面试指南(付费)收录的腾讯面经同学 29 Java 后端一面原题:Redis保证incr命令原子性的原理是什么?
memo:2025 年 4 月 29 日修改至此,今天有球友发信息说拿到了亚马逊的 offer,工资还给的很高,问我要不要选? 真的恭喜了🎉。
4.🌟Redis 为什么快呢?
第一,Redis 的所有数据都放在内存中,而内存的读写速度本身就比磁盘快几个数量级。
第二,Redis 采用了基于 IO 多路复用技术的事件驱动模型来处理客户端请求和执行 Redis 命令。
其中的 IO 多路复用技术可以在只有一个线程的情况下,同时监听成千上万个客户端连接,解决传统 IO 模型中每个连接都需要一个独立线程带来的性能开销。
IO 多路复用会持续监听请求,然后把准备好的请求压入到一个队列当中,并将其有序地传递给文件事件分派器,最后由事件处理器来执行对应的 accept、read 和 write 请求。
Redis 会根据操作系统选择最优的 IO 多路复用技术,比如 Linux 下使用 epoll,macOS 下使用 kqueue 等。
// epoll 的创建和使用
int epfd = epoll_create(1024); // 创建 epoll 实例
struct epoll_event ev, events[MAX_EVENTS];
// 添加监听事件
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);
// 等待事件发生
while (1) {
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; i++) {
// 处理就绪的文件描述符
}
}
在 Redis 6.0 之前,包括连接建立、请求读取、响应发送,以及命令执行都是在主线程中顺序执行的,这样可以避免多线程环境下的锁竞争和上下文切换,因为 Redis 的绝大部分操作都是在内存中进行的,性能瓶颈主要是内存操作和网络通信,而不是 CPU。
为了进一步解决网络 IO 的性能瓶颈,Redis 6.0 引入了多线程机制,把网络 IO 和命令执行分开,网络 IO 交给线程池来处理,而命令执行仍然在主线程中进行,这样就可以充分利用多核 CPU 的性能。
主线程专注于命令执行,网络IO 由其他线程分担,在多核 CPU 环境下,Redis 的性能可以得到显著提升。
第三,Redis 对底层数据结构做了极致的优化,比如说 String 的底层数据结构动态字符串支持动态扩容、预分配冗余空间,能够减少内存碎片和内存分配的开销。
总结:
- Java 面试指南(付费)收录的腾讯 Java 后端实习一面原题:Redis 为什么读写性能高?
- Java 面试指南(付费)收录的小米春招同学 K 一面面试原题:为什么 redis 快,淘汰策略 持久化
- Java 面试指南(付费)收录的字节跳动面经同学 1 Java 后端技术一面面试原题:单线程的 Redis 为什么这么快?
- Java 面试指南(付费)收录的微众银行同学 1 Java 后端一面的原题:Redis 为什么这么快?
- Java 面试指南(付费)收录的百度面经同学 1 文心一言 25 实习 Java 后端面试原题:项目中什么地方使用了 redis 缓存,redis 为什么快?
- Java 面试指南(付费)收录的得物面经同学 8 一面面试原题:Redis 为什么快
- Java 面试指南(付费)收录的字节跳动面经同学 21 抖音商城一面面试原题:redis为什么能处理高并发
memo:2025 年 4 月 30 日修改至此,今天有球友发信息说拿到了滴滴的实习 offer,真的恭喜了🎉。
5.能详细说一下IO多路复用吗?
IO 多路复用是一种允许单个进程同时监视多个文件描述符的技术,使得程序能够高效处理多个并发连接而无需创建大量线程。
IO 多路复用的核心思想是:让单个线程可以等待多个文件描述符就绪,然后对就绪的描述符进行操作。这样可以在不使用多线程或多进程的情况下处理并发连接。
主要的实现机制包括 select、poll、epoll、kqueue 和 IOCP 等。
请说说 select、poll、epoll、kqueue 和 IOCP 的区别?
select 的缺点是单个进程能监视的文件描述符数量有限,一般为 1024 个,且每次调用都需要将文件描述符集合从用户态复制到内核态,然后遍历找出就绪的描述符,性能较差。
// select 的基本使用
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
// 示例代码
fd_set readfds;
FD_ZERO(&readfds); // 清空集合
FD_SET(sockfd, &readfds); // 添加监听套接字
select(sockfd + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &readfds)) { // 检查是否就绪
// 处理读事件
}
poll 的优点是没有最大文件描述符数量的限制,但是每次调用仍然需要将文件描述符集合从用户态复制到内核态,依然需要遍历,性能仍然较差。
// poll 的基本使用
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// 示例代码
struct pollfd fds[MAX_EVENTS];
fds[0].fd = sockfd;
fds[0].events = POLLIN; // 监听读事件
poll(fds, 1, -1);
if (fds[0].revents & POLLIN) {
// 处理读事件
}
epoll 是 Linux 特有的 IO 多路复用机制,支持大规模并发连接,使用事件驱动模型,性能更高。其工作原理是将文件描述符注册到内核中,然后通过事件通知机制来处理就绪的文件描述符,不需要轮询,也不需要数据拷贝,更没有数量限制,所以性能非常高。
// epoll 的基本使用
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// 示例代码
int epfd = epoll_create(1);
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
while (1) {
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == sockfd) {
// 处理读事件
}
}
}
kqueue 是 BSD/macOS 系统下的 IO 多路复用机制,类似于 epoll,支持大规模并发连接,使用事件驱动模型。
int kqueue(void);
int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout);
IOCP 是 Windows 系统下的 IO 多路复用机制,使用使用完成端口模型而非事件通知。
HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);
举个例子说一下 IO 多路复用?
比如说我是一名数学老师,上课时提出了一个问题:“今天谁来证明一下勾股定律?”
同学小王举手,我就让小王回答;小李举手,我就让小李回答;小张举手,我就让小张回答。
这种模式就是 IO 多路复用,我只需要在讲台上等,谁举手谁回答,不需要一个一个去问。
Redis 就是使用 epoll 这样的 IO 多路复用机制,在单线程模型下实现高效的网络 IO,从而支持高并发的请求处理。
举例子说一下阻塞 IO和 IO 多路复用的差别?
假设我是一名老师,让学生解答一道题目。
我的第一种选择:按顺序逐个检查,先检查 A同学,然后是 B,之后是 C、D。。。这中间如果有一个学生卡住,全班都会被耽误。
这种就是阻塞 IO,不具有并发能力。
我的第二种选择,我站在讲台上等,谁举手我去检查谁。C、D 举手,我去检查 C、D 的答案,然后继续回到讲台上等。此时 E、A 又举手,然后去处理 E 和 A。
select、poll 和 epoll 的实现原理?
select 和 poll 都是通过把所有文件描述符传递给内核,由内核遍历判断哪些就绪。
select 将文件描述符 FD 通过 BitsMap 传入内核,轮询所有的 FD,通过调用 file->poll 函数查询是否有对应事件,没有就将 task 加入 FD 对应 file 的待唤醒队列,等待事件来临被唤醒。
poll 改进了连接数上限问题,不再用 BitsMap 来传入 FD,取而代之的是动态数组 pollfd,但本质上仍是线性遍历,性能没有提升太多。
select和poll的模式都是,一次将参数拷贝到内核空间,等有结果了再一次拷贝出去。
epoll 将监听的 FD 注册进内核的红黑树,由内核在事件触发时将就绪的 FD 放入 ready list。应用程序通过 epoll_wait 获取就绪的 FD,从而避免遍历所有连接的开销。
epoll 最大的优点是:支持事件驱动 + 边缘触发,ADD 时拷贝一次,epoll_wait 时利用 MMAP 和用户共享空间,直接拷贝数据到用户空间,因此在高并发场景下性能远高于 select 和 poll。
- Java 面试指南(付费)收录的字节跳动面经同学 21 抖音商城一面面试原题:io多路复用了解吗?
- Java 面试指南(付费)收录的快手同学 4 一面原题:IO多路复用中select/poll/epoll各自的实现原理和区别?
- Java 面试指南(付费)收录的字节跳动面经同学19番茄小说一面面试原题:Linux中的IO多路复用
memo:2025 年 5 月 1 日修改至此,今天帮球友修改简历时 时,碰到一名北京交通大学的同学,又一所 211 院校,星球真的是人才济济,大家一起加油吧(骄傲)。
6.Redis为什么早期选择单线程?
第一,单线程模型不需要考虑复杂的锁机制,不存在多线程环境下的死锁、竞态条件等问题,开发起来更快,也更容易维护。
第二,Redis 是IO 密集型而非 CPU 密集型,主要受内存和网络 IO 限制,而非 CPU 的计算能力,单线程可以避免线程上下文切换的开销。
哪怕我们在一个普通的 Linux 服务器上启动 Redis 服务,它也能在 1s 内处理 1000000 个用户请求。
第三,单线程可以保证命令执行的原子性,无需额外的同步机制。
Redis 虽然最初采用了单线程设计,但后续的版本中也在特定方面引入了多线程,比如说 Redis 4.0 就引异步多线程,用于清理脏数据、释放无用连接、删除大 Key 等。
/* 从数据库中删除一个键、值以及相关的过期条目(如果有的话)。
* 如果释放值对象需要大量的内存分配操作,该对象可能会被放入
* 延迟释放列表中,而不是同步释放。延迟释放列表将在
* bio.c 的另一个线程中进行回收。 */
#define LAZYFREE_THRESHOLD 64
int dbAsyncDelete(redisDb *db, robj *key) {
/* 从过期字典中删除条目不会释放键的 sds,
* 因为它与主字典共享。 */
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
/* 如果值对象只包含少量的内存分配,使用延迟释放方式
* 实际上会更慢... 所以在一定阈值以下,我们就直接
* 同步释放对象。 */
dictEntry *de = dictUnlink(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de);
// 计算value的回收收益
size_t free_effort = lazyfreeGetFreeEffort(val);
/* 如果释放对象的工作量太大,就通过将对象添加到延迟释放列表
* 在后台进行处理。
* 注意,如果对象是共享的,现在就回收它是不可能的。这种情况
* 很少发生,但是有时 Redis 核心的某些实现部分可能会调用
* incrRefCount() 来保护对象,然后调用 dbDelete()。在这种
* 情况下,我们会继续执行并到达 dictFreeUnlinkedEntry()
* 调用,这相当于仅仅调用 decrRefCount()。 */
// 只有回收收益超过一定值,才会执行异步删除,否则还是会退化到同步删除
if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
atomicIncr(lazyfree_objects,1);
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
dictSetVal(db->dict,de,NULL);
}
}
/* 释放键值对,如果我们将 val 字段设置为 NULL 以便稍后
* 延迟释放,那么就只释放键。 */
if (de) {
dictFreeUnlinkedEntry(db->dict,de);
if (server.cluster_enabled) slotToKeyDel(key->ptr);
return 1;
} else {
return 0;
}
}
官方解释:https://redis.io/topics/faq
memo:2025 年 5 月 2 日修改至此,今天帮球友修改简历时 时,碰到一名同济大学的同学,让感觉自己的付出正在越来越多被更多人看到,真的很开心。
7.Redis 6.0 使用多线程是怎么回事?
Redis 6.0 的多线程仅用于处理网络 IO,包括网络数据的读取、写入,以及请求解析。
│ 单线程执行命令 │
│ ↑ ↓ │
┌─────────┐ ┌─┴────────────┴──┐
│ I/O线程1 │ ←→ │ │
├─────────┤ │ │
│ I/O线程2 │ ←→ │ 主线程 │
├─────────┤ │ │
│ I/O线程3 │ ←→ │ │
└─────────┘ └─────────────────┘
而命令的执行依然是单线程,这种设计被称为“IO 线程化”,能够在高负载的情况下,最大限度地提升 Redis 的响应速度。
---- 这部分面试中可以不背,方便大家理解 start ----
这一变化主要是因为随着网络带宽和服务器性能的提升,Redis 的瓶颈从 CPU 逐渐转移到了网络 IO:
- 带宽从 10Gbps 提升到 100Gbps,甚至更高。
- 请求的并发数从几千到几万,甚至几十万。
单线程在高负载场景下处理网络 IO 出现了明显的性能瓶颈,Redis 的开发团队通过研究发现,在处理大数据包时,单线程 Redis 有超过 80% 的 CPU 时间花在网络 IO 上,而实际命令执行仅占 20% 左右。
Redis 6.0 的多线程 IO 模型主要包含三个核心步骤:
- 仍然由主线程负责接收客户端的连接请求。
- 主线程将连接请求分发给多个 IO 线程进行处理,主线程负责解析和执行命令。
- 命令执行完毕后,由多个 IO 线程将结果返回给客户端。
// Redis 主事件循环(简化版)
void beforeSleep(struct aeEventLoop *eventLoop) {
// 1. 主线程分派读任务给 I/O 线程
handleClientsWithPendingReadsUsingThreads();
// 2. 等待 I/O 线程完成读取
waitForIOThreads();
// 3. 主线程处理命令
processInputBuffer();
// 4. 主线程分派写任务给 I/O 线程
handleClientsWithPendingWritesUsingThreads();
}
Redis 6.0 默认仍然使用单线程模式,但可以通过配置文件或命令行参数启用多线程模式。
# 启用多线程模式
io-threads 4
# 启用多线程写入(Redis 6.0 默认只开启多线程读取)
io-threads-do-reads yes
建议将 IO 线程数设置为 CPU 核心数的一半,一般不建议超过 8 个。
经过多次测试,Redis 6.0 在处理 1-200 字节的小数据包时,性能提升 1.5-2 倍;在处理 1KB 以上的大数据包时提升约 3-5 倍。
----这部分面试中可以不背,方便大家理解 end ----
- Java 面试指南(付费)收录的同学 30 腾讯音乐面试原题:redis6.0引入的多线程用作什么地方
8.说说 Redis 的常用命令(补充)
2024 年 04 月 11 日增补
一句话回答(也不用全部都背,挑三个就行):
Redis 支持多种数据结构,常用的命令也比较多,比如说操作字符串可以用 SET/GET/INCR,操作哈希可以用 HSET/HGET/HGETALL,操作列表可以用 LPUSH/LPOP/LRANGE,操作集合可以用 SADD/SISMEMBER,操作有序集合可以用 ZADD/ZRANGE/ZINCRBY等,通用命令有 EXPIRE/DEL/KEYS 等。
----这部分面试中可以不背,方便大家理解 start----
①、操作字符串的命令有:
| 命令 | 作用 | 示例 |
|---|---|---|
SET key value |
设置字符串键值 | SET name jack |
GET key |
获取字符串值 | GET name |
INCR key |
数值自增 1 | INCR count |
DECR key |
数值自减 1 | DECR stock |
INCRBY key N |
增加 N | INCRBY views 10 |
APPEND key value |
追加字符串 | APPEND log "done" |
GETRANGE key start end |
获取子串 | GETRANGE name 0 3 |
MSET k1 v1 k2 v2 |
批量设置多个键值 | MSET a 1 b 2 |
②、操作列表的命令有:
LPUSH key value:将一个值插入到列表 key 的头部。RPUSH key value:将一个值插入到列表 key 的尾部。LPOP key:移除并返回列表 key 的头元素。RPOP key:移除并返回列表 key 的尾元素。LRANGE key start stop:获取列表 key 中指定范围内的元素。
③、操作集合的命令有:
SADD key member:向集合 key 添加一个元素。SREM key member:从集合 key 中移除一个元素。SMEMBERS key:返回集合 key 中的所有元素。
④、操作有序集合的命令有:
ZADD key score member:向有序集合 key 添加一个成员,或更新其分数。ZRANGE key start stop [WITHSCORES]:按照索引区间返回有序集合 key 中的成员,可选 WITHSCORES 参数返回分数。ZREVRANGE key start stop [WITHSCORES]:返回有序集合 key 中,指定区间内的成员,按分数递减。ZREM key member:移除有序集合 key 中的一个或多个成员。
⑤、操作哈希的命令有:
HSET key field value:向键为 key 的哈希表中设置字段 field 的值为 value。HGET key field:获取键为 key 的哈希表中字段 field 的值。HGETALL key:获取键为 key 的哈希表中所有的字段和值。HDEL key field:删除键为 key 的哈希表中的一个或多个字段。
详细说说 set 命令?
SET 命令用于设置字符串的 key,支持过期时间和条件写入,常用于设置缓存、实现分布式锁、延长 Session 等场景。
SET key value [EX seconds | PX milliseconds | EXAT timestamp | PXAT timestamp-milliseconds | KEEPTTL] [NX | XX] [GET]
默认情况下,SET 会覆盖键已有的值。
支持多种设置过期时间的方式,比如说 EX 设置秒级过期时间,PX 设置毫秒过期时间。
支持条件写入,使其可以实现原子性操作,比如说 NX 仅在键不存在时设置值,XX 仅在键存在时设置值。
缓存实现:
SET user:profile:{userid} {JSON数据} EX 3600 # 存储用户资料,并设置1小时过期
实现分布式锁:
SET lock:resource_name {random_value} EX 10 NX # 获取锁,10秒后自动释放
存储 Session:
SET session:{sessionid} {session_data} EX 1800 # 存储用户会话,30分钟过期
sadd 命令的时间复杂度是多少?
SADD 支持一次添加多个元素,返回值为实际添加成功的元素数量,时间复杂度为 O(N)。
redis-cli SADD myset "apple" "banana" "orange"
incr命令了解吗?
INCR 是一个原子命令,可以将指定键的值加 1,如果 key 不存在,会先将其设置为 0,再执行加 1 操作。
常用于网站访问量、文章点赞数等计数器的实现;结合过期时间实现限流器;生成分布式唯一 ID;库存扣减等。
# 限制用户每分钟最多访问10次
FUNCTION limit_api_call(user_id)
current = INCR("rate:"+user_id)
IF current == 1 THEN
EXPIRE("rate:"+user_id, 60)
END
IF current > 10 THEN
RETURN false # 超出限制
ELSE
RETURN true # 允许访问
END
END
- Java 面试指南(付费)收录的京东面经同学 1 Java 技术一面面试原题:说说 Redis 常用命令
- Java 面试指南(付费)收录的农业银行面经同学 3 Java 后端面试原题:说的那么好,Redis 设置 key value 的函数是啥
- Java 面试指南(付费)收录的快手面经同学 1 部门主站技术部面试原题:Redis 的 sadd 命令时间复杂度是多少?
memo:2025 年 5 月 3 日修改至此,今天有球友发信息说拿到了美的的软开暑期实习 offer,虽然他自己不满意,但暂时没有其他更好的,我建议他先去试一下🎉。
9.单线程的Redis QPS 能到多少?(补充)
2024 年 4 月 14 日增补
根据官方的基准测试,一个普通服务器的 Redis 实例通常可以达到每秒十万左右的 QPS。
----这部分面试中可以不背,方便大家理解 start ----
Redis 的 QPS(每秒请求数)性能取决于多种因素,包括硬件配置、网络延迟、数据结构、命令类型等。
可以通过 redis-benchmark 命令进行基准测试:
redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000
-h:指定 Redis 服务器的地址,默认是 127.0.0.1。-p:指定 Redis 服务器的端口,默认是 6379。-c:并发连接数,即同时有多少个客户端在进行测试。-n:请求总数,即测试过程中总共要执行多少个请求。
2023 年前,我用的是一台 macOS,4 GHz 四核 Intel Core i7,32 GB 1867 MHz DDR3,测试结果如下:
可以看得出,每秒能处理超过 10 万次请求。
QPS = 总请求数 / 总耗时 = 10000 / 0.09 ≈ 111111 QPS
延迟也非常低,99% 的请求都在 0.3ms 以内完成了。
----这部分面试中可以不背,方便大家理解 end ----
- Java 面试指南(付费)收录的字节跳动面经同学 1 Java 后端技术一面面试原题:单线程 Redis 的 QPS 是多少?
持久化
10.🌟Redis的持久化方式有哪些?
主要有两种,RDB 和 AOF。RDB 通过创建时间点快照来实现持久化,AOF 通过记录每个写操作命令来实现持久化。
这两种方式可以单独使用,也可以同时使用。这样就可以保证 Redis 服务器在重启后不丢失数据,通过 RDB 和 AOF 文件来恢复内存中原有的数据。
详细说一下 RDB?
RDB 持久化机制可以在指定的时间间隔内将 Redis 某一时刻的数据保存到磁盘上的 RDB 文件中,当 Redis 重启时,可以通过加载这个 RDB 文件来恢复数据。
RDB 持久化可以通过 save 和 bgsave 命令手动触发,也可以通过配置文件中的 save 指令自动触发。
save 命令会阻塞 Redis 进程,直到 RDB 文件创建完成。
bgsave 命令会在后台 fork 一个子进程来执行 RDB 持久化操作,主进程不会被阻塞。
什么情况下会自动触发 RDB 持久化?
第一种,在 Redis 配置文件中设置 RDB 持久化参数 save ,表示在指定时间间隔内,如果有指定数量的键发生变化,就会自动触发 RDB 持久化。
save 900 1 # 900 秒(15 分钟)内有 1 个 key 发生变化,触发快照
save 300 10 # 300 秒(5 分钟)内有 10 个 key 发生变化,触发快照
save 60 10000 # 60 秒内有 10000 个 key 发生变化,触发快照
第二种,主从复制时,当从节点第一次连接到主节点时,主节点会自动执行 bgsave 生成 RDB 文件,并将其发送给从节点。
第三种,如果没有开启 AOF,执行 shutdown 命令时,Redis 会自动保存一次 RDB 文件,以确保数据不会丢失。
详细说一下 AOF?
AOF 通过记录每个写操作命令,并将其追加到 AOF 文件来实现持久化,Redis 服务器宕机后可以通过重新执行这些命令来恢复数据。
当 Redis 执行写操作时,会将写命令追加到 AOF 缓冲区;Redis 会根据同步策略将缓冲区的数据写入到 AOF 文件。
当 AOF 文件过大时,Redis 会自动进行 AOF 重写,剔除多余的命令,比如说多次对同一个 key 的 set 和 del,生成一个新的 AOF 文件;当 Redis 重启时,读取 AOF 文件中的命令并重新执行,以恢复数据。
AOF 的刷盘策略了解吗?
Redis 将 AOF 缓冲区的数据写入到 AOF 文件时,涉及两个系统调用:write 将数据写入到操作系统的缓冲区,fsync 将 OS 缓冲区的数据刷新到磁盘。
这里的刷盘涉及到三种策略:always、everysec 和 no。
- always:每次写命令执行完,立即调用 fsync 同步到磁盘,这样可以保证数据不丢失,但性能较差。
- everysec:每秒调用一次 fsync,将多条命令一次性同步到磁盘,性能较好,数据丢失的时间窗口为 1 秒。
- no:不主动调用 fsync,由操作系统决定,性能最好,但数据丢失的时间窗口不确定,依赖于操作系统的缓存策略,可能会丢失大量数据。
可以通过配置文件中的 appendfsync 参数进行设置。
appendfsync everysec # 每秒 fsync 一次
说说AOF的重写机制?
由于 AOF 文件会随着写操作的增加而不断增长,为了解决这个问题, Redis 提供了重写机制来对 AOF 文件进行压缩和优化。
AOF 重写可以通过两种方式触发,第一种是手动执行 BGREWRITEAOF 命令,适用于需要立即减小AOF文件大小的场景。
第二种是在 Redis 配置文件中设置自动重写参数,比如说 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size,表示当 AOF 文件大小超过指定值时,自动触发重写。
auto-aof-rewrite-percentage 100 # 默认值100,表示当前AOF文件大小相比上次重写后大小增长了多少百分比时触发重写
auto-aof-rewrite-min-size 64mb # 默认值64MB,表示AOF文件至少要达到这个大小才会考虑重写
AOF 重写的具体过程是怎样的?
Redis 在收到重写指令后,会创建一个子进程,并 fork 一份与父进程完全相同的数据副本,然后遍历内存中的所有键值对,生成重建它们所需的最少命令。
比如说多个 RPUSH 命令可以合并为一个带有多个参数的 RPUSH;
比如说一个键被设置后又被删除,这个键的所有操作都不会被写入新 AOF。
比如说使用 SADD key member1 member2 member3 代替多个单独的 SADD key memberX。
子进程在执行 AOF 重写的同时,主进程可以继续处理来自客户端的命令。
为了保证数据一致性,Redis 使用了 AOF 重写缓冲区机制,主进程在执行写操作时,会将命令同时写入旧的 AOF 文件和重写缓冲区。
等子进程完成重写后,会向主进程发送一个信号,主进程收到后将重写缓冲区中的命令追加到新的 AOF 文件中,然后调用操作系统的 rename,将旧的 AOF 文件替换为新的 AOF 文件。
主进程(fork)
│
├─→ 子进程(生成新的 AOF 文件)
│ │
│ ├─→ 内存快照
│ ├─→ 写入临时 AOF 文件
│ ├─→ 通知主进程完成
│
├─→ 主进程(追加缓冲区到新 AOF 文件)
├─→ 替换旧 AOF 文件
├─→ 重写完成
AOF 重写期间,Redis 服务器会处于特殊状态:
- aof_child_pid 不为 0,表示有子进程在执行 AOF 重写
- aof_rewrite_buf_blocks 链表不为空,存储 AOF 重写缓冲区内容
如果在配置文件中设置 no-appendfsync-on-rewrite 为 yes,那么重写期间可能会暂停 AOF 文件的 fsync 操作。
appendonly yes # 开启AOF
appendfilename "appendonly.aof" # AOF文件名
appendfsync everysec # 写入磁盘策略
no-appendfsync-on-rewrite no # 重写期间是否临时关闭fsync
auto-aof-rewrite-percentage 100 # AOF文件增长到原来多少百分比时触发重写
auto-aof-rewrite-min-size 64mb # AOF文件最小多大时才允许重写
AOF 文件存储的是什么类型的数据?
AOF 文件存储的是 Redis 服务器接收到的写命令数据,以 Redis 协议格式保存。
这种格式的特点是,每个命令以*开头,后跟参数的数量,每个参数前用$符号,后跟参数字节长度,然后是参数的实际内容。
AOF重写期间命令可能会写入两次,会造成什么影响?
AOF 重写期间命令会同时写入现有AOF文件和重写缓冲区,这种机制是有意设计的,并不会导致数据重复或不一致问题。
因为新旧文件是分离的,现有命令写入当前 AOF 文件,重写缓冲区的命令最终写入新的 AOF 文件,完成后,新文件通过原子性的 rename 操作替换旧文件。两个文件是完全分离的,不会导致同一个 AOF 文件中出现重复命令。
- Java 面试指南(付费)收录的小米春招同学 K 一面面试原题:为什么 redis 快,淘汰策略 持久化
- Java 面试指南(付费)收录的快手面经同学 7 Java 后端技术一面面试原题:说一下 Redis 的持久化方式
- Java 面试指南(付费)收录的小公司面经合集同学 1 Java 后端面试原题:Redis 的持久化方式?RDB 和 AOF 的区别?Redis 宕机哪种恢复的比较快?
- Java 面试指南(付费)收录的美团面经同学 18 成都到家面试原题:redis 持久化
- Java 面试指南(付费)收录的作业帮面经同学 1 Java 后端一面面试原题:redis持久化机制
- Java 面试指南(付费)收录的 OPPO 面经同学 1 面试原题:Redis持久化方案
- Java 面试指南(付费)收录的得物面经同学 9 面试题目原题:Redis的基本数据类型?Redis的持久化呢?有何优缺点?
- Java 面试指南(付费)收录的滴滴面经同学 3 网约车后端开发一面原题:Redis持久化
- Java 面试指南(付费)收录的快手面经同学 1 部门主站技术部面试原题:Redis数据的可靠性怎么保证?AOF重写期间命令可能会写入两次,会造成什么影响?
memo:2025 年 5 月 4 日修改至此,今天有球友发信息说把并发编程和 JVM 的面渣逆袭都打印成纸质版了,说实话,这个封面的颜值我也很喜欢,哈哈。
11.RDB 和 AOF 各自有什么优缺点?
RDB 通过 fork 子进程在特定时间点对内存数据进行全量备份,生成二进制格式的快照文件。其最大优势在于备份恢复效率高,文件紧凑,恢复速度快,适合大规模数据的备份和迁移场景。
缺点是可能丢失两次快照期间的所有数据变更。
AOF 会记录每一条修改数据的写命令。这种日志追加的方式让 AOF 能够提供接近实时的数据备份,数据丢失风险可以控制在 1 秒内甚至完全避免。
缺点是文件体积较大,恢复速度慢。
来个表格对比一下:
| 对比项 | RDB(快照) | AOF(命令日志) |
|---|---|---|
| 数据完整性 | ❌ 可能丢失几分钟数据 | ✅ 最多丢 1 秒数据 |
| 恢复速度 | ✅ 快(直接加载二进制快照) | ❌ 慢(逐条 replay) |
| 文件大小 | ✅ 小(压缩后) | ❌ 大(命令追加) |
| 性能影响 | ✅ 低(fork 后保存) | ❌ 较高(每次写都记录) |
| 写入方式 | 定期全量写 | 每次写命令就记录 |
| 适用场景 | 冷备份,灾难恢复 | 实时持久化,数据安全 |
| 默认状态 | 默认启用 | Redis 7 默认也启用 |
| 重写机制 | 无 | 有(BGREWRITEAOF) |
| 混合支持 | Redis 4.0 后支持结合使用(aof-use-rdb-preamble) |
- Java 面试指南(付费)收录的小公司面经合集同学 1 Java 后端面试原题:Redis 的持久化方式?RDB 和 AOF 的区别?Redis 宕机哪种恢复的比较快?
12.RDB 和 AOF 如何选择?
在选择 Redis 持久化方案时,我会从业务需求和技术特性两个维度来考虑。
如果是缓存场景,可以接受一定程度的数据丢失,我会倾向于选择 RDB 或者完全不使用持久化。RDB 的快照方式对性能影响小,而且恢复速度快,非常适合这类场景。
但如果是处理订单或者支付这样的核心业务,数据丢失将造成严重后果,那么 AOF 就成为必然选择。通过配置每秒同步一次,可以将潜在的数据丢失风险限制在可接受范围内。
在实际的项目当中,我更偏向于使用 RDB + AOF 的混合模式。
appendonly yes # 开启 AOF
appendfsync everysec # 每秒刷盘一次
aof-use-rdb-preamble yes # 开启混合持久化,重启时优先加载 RDB,RDB 作为冷备,AOF 作为实时同步
- Java 面试指南(付费)收录的美团面经同学 18 成都到家面试原题:什么时候用 rdb 什么时候用 aof
13.Redis如何恢复数据?
当 Redis 服务重启时,它会优先查找 AOF 文件,如果存在就通过重放其中的命令来恢复数据;如果不存在或未启用 AOF,则会尝试加载 RDB 文件,直接将二进制数据载入内存来恢复。
如果 AOF 文件损坏的话,Redis 会尝试通过 redis-check-aof 工具来修复 AOF 文件,或者直接使用 --repair 参数来修复。
redis-check-aof --repair appendonly.aof
虽然 Redis 还提供了 redis-check-rdb 工具来检查 RDB 文件的完整性,但它并不支持修复 RDB 文件,只能用来验证文件的完整性。
redis-check-rdb dump.rdb
- Java 面试指南(付费)收录的美团面经同学 4 一面面试原题:Redis 内存中数据丢失怎么解决
memo:2025 年 5 月 5 日修改至此,今天给球友修改简历时,碰到一个华科本硕的球友,985 高校又+1,目前国内的 985 高校有 39 所,希望不久的将来,能全部集齐。😄
14.🌟Redis 4.0 的混合持久化了解吗?
是的。
混合持久化结合了 RDB 和 AOF 两种方式的优点,解决了它们各自的不足。在 Redis 4.0 之前,我们要么面临 RDB 可能丢失数据的风险,要么承受 AOF 恢复慢的问题,很难两全其美。
混合持久化的工作原理非常巧妙:在 AOF 重写期间,先以 RDB 格式将内存中的数据快照保存到 AOF 文件的开头,再将重写期间的命令以 AOF 格式追加到文件末尾。
这样,当需要恢复数据时,Redis 先加载 RDB 格式的数据来快速恢复大部分的数据,然后通过重放命令恢复最近的数据,这样就能在保证数据完整性的同时,提升恢复速度。
如何设置持久化模式?
启用混合持久化的方式非常简单,只需要在配置文件中设置 aof-use-rdb-preamble yes 就可以了。
aof-use-rdb-preamble yes
你在开发中是怎么配置 RDB 和 AOF 的?
对于大多数生产环境,我倾向于使用混合持久化方式,结合 RDB 和 AOF 的优点。
# 启用AOF
appendonly yes
# 使用混合持久化
aof-use-rdb-preamble yes
# 每秒同步一次AOF,平衡性能和安全性
appendfsync everysec
# AOF重写触发条件:文件增长100%且至少达到64MB
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# RDB备份策略
save 900 1 # 15分钟内有1个修改
save 300 10 # 5分钟内有10个修改
save 60 10000 # 1分钟内有10000个修改
对于单纯的缓存场景,或者本地开发,我会只启用 RDB,关闭 AOF:
# 禁用AOF
appendonly no
# 较宽松的RDB策略
save 3600 1 # 1小时内有1个修改
save 300 100 # 5分钟内有100个修改
而对于金融类等高一致性的系统,我通常会在关键时间窗口动态将 appendfsync 设置为 always:
# 启用AOF
appendonly yes
# 使用混合持久化
aof-use-rdb-preamble yes
# 每个命令都同步(谨慎使用,性能影响大)
# 通常我会在关键时间窗口动态修改为always
appendfsync always
# 更频繁的RDB快照
save 300 1 # 5分钟内有1个修改
save 60 100 # 1分钟内有100个修改
另外,对于高并发场景,应该设置no-appendfsync-on-rewrite yes,避免 AOF 重写影响主进程性能;对于大型实例,也应该设置 rdb-save-incremental-fsync yes 来减少大型 RDB 保存对性能的影响。
# AOF重写期间不fsync,AOF 重写期间,主进程不会对新写入的 AOF 缓冲区执行 fsync 操作(即不强制刷盘),而是等重写结束后再统一刷盘。
no-appendfsync-on-rewrite yes
# RDB 快照保存时采用增量 fsync,即每写入一定量的数据就执行一次 fsync,将数据分批同步到磁盘。
rdb-save-incremental-fsync yes
- Java 面试指南(付费)收录的字节跳动面经同学 1 Java 后端技术一面面试原题:Redis 的持久化机制?
- Java 面试指南(付费)收录的小公司面经合集同学 1 Java 后端面试原题:Redis 宕机哪种恢复的比较快?
- Java 面试指南(付费)收录的美团面经同学 18 成都到家面试原题:如何设置持久化模式
- Java 面试指南(付费)收录的美团面经同学 4 一面面试原题:业界使用哪一种数据持久化,两种持久化方法的优缺点
- Java 面试指南(付费)收录的作业帮面经同学 1 Java 后端一面面试原题:两种 Redis持久化机制可以混合使用吗
memo:2025 年 5 月 6 日修改至此,今天在修改球友简历时,碰到一个东北大学硕合肥工业大学本的球友,真的非常优秀,也希望大家能够通过星球这个平台彼此激励,共同进步。
高可用
15.主从复制了解吗?
主从复制允许从节点维护主节点的数据副本。在这种架构中,一个主节点可以连接多个从节点,从而形成一主多从的结构。主节点负责处理写操作,从节点自动同步主节点的数据变更,并处理读请求,从而实现读写分离。
主从复制的主要作用是什么?
第一,主节点负责处理写请求,从节点负责处理读请求,从而实现读写分离,减轻主节点压力的同时提升系统的并发能力。
第二,从节点可以作为主节点的数据备份,当主节点发生故障时,可以快速将从节点提升为新的主节点,从而保证系统的高可用性。
什么情况下会出现主从复制数据不一致?
Redis 的主从复制是异步进行的,因此在主节点宕机、网络波动或复制延迟较高时会出现从节点数据不同步的情况。
比如主节点写入数据后宕机,但从节点还未来得及复制,就会出现数据不一致。
时间线:→
客户端 → 向主节点 SET user:1 二哥 → 主节点处理成功 ✅
↓
正准备推送给从节点(异步复制)... 但还没推送完 ❌
↓
—— 突然主节点宕机(机器死机、断网) 💥 ——
↓
Sentinel 监测到故障,failover:将从节点提升为新主节点 🧠
↓
客户端继续请求:GET user:1 ❓→ 从节点返回:空 ❌(数据没同步过来)
另一个容易被忽视的因素是主节点内存压力。当主节点内存接近上限并启用了淘汰策略时,某些键可能被自动删除,而这些删除操作如果未能及时同步,就会造成从节点保留了主节点已经不存在的数据。
主从复制数据不一致的解决方案有哪些?
首先是网络层面的优化,理想情况下,主从节点应该部署在同一个网络区域内,避免跨区域的网络延迟。
其次是配置层面的调整,比如说适当增大复制积压缓冲区的大小和存活时间,以便从节点重连后进行增量同步而不是全量同步,以最大程度减少主从同步的延迟。
repl-backlog-size 1mb # 默认值 1MB,表示主节点的复制缓冲区大小
repl-backlog-ttl 3600 # 默认值 3600 秒,表示主节点的复制缓冲区存活时间
第三是引入监控和自动修复机制,定期检查主从节点的数据一致性。
比如说通过比较主从的 offset 差值判断从库是否落后。一旦超过设定阈值,就将从节点剔除,并重新进行全量同步。
- Java 面试指南(付费)收录的得物面经同学 1 面试原题:Redis 分布式,主从,一个节点挂掉怎么办
- Java 面试指南(付费)收录的小米面经同学 F 面试原题:redis 的主从架构和主从哨兵区别
- Java 面试指南(付费)收录的收钱吧面经同学 1 Java 后端一面面试原题:Redis解决单点故障主要靠什么?主从模式用的是异步还是同步?
memo:2025 年 5 月 7 日修改至此,今天在修改球友简历时,收到了球友对简历修改的认可:“现在这份简历应该比较完美了”,完美这个词我觉得褒奖的有点多了,哈哈,不过我还是很开心的。
16.Redis主从有几种常见的拓扑结构?
主要有三种。
最基础的是一主一从,这种模式适合小型项目。一个主节点负责写入,一个从节点负责读和数据备份。这种结构虽然简单,但维护成本低。
随着业务增长,读请求增多,可以考虑扩展为一主多从结构。主节点负责写入,多个从节点还可以分摊压力。
在跨地域部署场景中,树状主从结构可以有效降低主节点负载和需要传送给从节点的数据量。通过引入复制中间层,从节点不仅可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。
17.Redis的主从复制原理了解吗?
了解。
Redis 的主从复制是指通过异步复制将主节点的数据变更同步到从节点,从而实现数据备份和读写分离。这个过程大致可以分为三个阶段:建立连接、同步数据和传播命令。
在建立连接阶段,从节点通过执行 replicaof 命令连接到主节点。连接建立后,从节点向主节点发送 psync 命令,请求数据同步。这时主节点会为该从节点创建一个连接和复制缓冲区。
同步数据阶段分为全量同步和增量同步。当从节点首次连接主节点时,会触发全量同步。
在这个过程中,主节点会 fork 一个子进程生成 RDB 文件,同时将文件生成期间收到的写命令缓存到复制缓冲区。然后将 RDB 文件发送给从节点,从节点清空自己的数据并加载这个 RDB 文件。等 RDB 传输完成后,主节点再将缓存的写命令发送给从节点执行,确保数据完全一致。
主从完成全量同步后,主要依靠传播命令阶段来保持数据的增量同步。主节点会将每次执行的写命令实时发送给所有从节点。
Redis 2.8 版本后,主节点会为每个从节点维护一个复制积压缓冲区,用于存储最近的写命令。
增量复制时,主节点会把要同步的写命令暂存一份到复制积压缓冲区。这样当从节点和主节点发生网络断连,从节点重新连接后,可以从复制积压缓冲区中复制尚未同步的写命令。
memo:2025 年 5 月 8 日修改至此,今天有球友在星球里发帖说拿到了腾讯的实习 offer,真的要恭喜了。面经,我看题目主要集中在技术派项目和MySQL、计算机网络的八股上。
18.详细说说全量同步和增量同步?
全量同步会将主节点的完整数据集传输给从节点,通常发生在从节点首次连接主节点时。
此时,从节点发送 psync ? -1 命令请求同步。? 表示从节点没有主节点 ID,-1 表示没有偏移量。主节点收到后会回复 FULLRESYNC响应从节点。同时也会包含主库 runid 和复制偏移量 offset 两个参数。
然后 fork 一个子进程生成 RDB 文件,并将新的写命令存入复制缓冲区。
从库收到 RDB 文件后,清空旧数据并加载新的 RDB 文件。加载完成后,从节点会向主节点回复确认消息,主节点再将复制缓冲区中的数据发送给从节点,确保从节点的数据与主节点一致。
全量同步的代价很高,因为完整的 RDB 文件在生成时会占用大量的 CPU 和磁盘 IO;在网络传输时还会消耗掉不少带宽。
于是 Redis 在 2.8 版本后引入了增量同步的概念,目的是在断线重连后避免全量同步。
增量依赖三个关键要素:
①、复制偏移量:主从节点分别维护一个复制偏移量,记录传输的字节数。主节点每传输 N 个字节数据,自身的复制偏移量就会增加 N;从节点每收到 N 个字节数据,也会相应增加自己的偏移量。
②、主节点 ID:每个主节点都有一个唯一 ID,即复制 ID,用于标识主节点的数据版本。当主节点发生重启或者角色变化时,ID 会改变。
③、复制积压缓冲区:主节点维护的一个固定长度的先进先出队列,默认大小为 1M。主节点在向从节点发送命令的同时,也会将命令写入这个缓冲区。
当从节点与主节点断开重连后,会发送 psync{runId}{offset} 命令,带上之前记录的主节点 ID 和复制偏移量。
主节点收到这个命令后,会检查 runId 和 offset:
如果主节点 ID 与从节点提供的 runId 不匹配,说明主节点已经变化,必须进行全量同步。
如果 ID 匹配,主节点会查找从节点请求的偏移量之后的数据是否还在复制积压缓冲区。
如果在,只发送从该偏移量开始的增量数据,这就是增量同步;否则说明断线时间太长,积压缓冲区已经覆盖了这部分数据,需要全量同步。
增量同步的优势显而易见:只传输断线期间的命令数据,大大减少了网络传输量和主从节点的负载,从节点也不需要清空重载数据,能更快地跟上主节点状态。
对于写入频繁或网络不稳定的环境,应该增大复制积压缓冲区的大小,确保短时间断线后能进行增量同步而不是全量同步。
repl-backlog-size 1mb # 默认值 1MB,表示主节点的复制缓冲区大小
repl-backlog-ttl 3600 # 默认值 3600 秒,表示主节点的复制缓冲区存活时间
memo:2025 年 5 月 9 日修改至此,今天在修改球友简历时,碰到一个河北大学硕东华理工大学本的球友,希望这个大家庭能给大家带来更多的帮助和支持。
19.主从复制存在哪些问题呢?
Redis 主从复制的最大挑战来自于它的异步特性,主节点处理完写命令后会立即响应客户端,而不会等待从节点确认,这就导致在某些情况下可能出现数据不一致。
另一个常见问题是全量同步对系统的冲击。全量同步会占用大量的 CPU 和 IO 资源,尤其是在大数据量的情况下,会导致主节点的性能下降。
脑裂问题了解吗?
在 Redis 的哨兵架构中,脑裂的典型表现为:主节点与哨兵、从节点之间的网络发生故障了,但与客户端的连接是正常的,就会出现两个“主节点”同时对外提供服务。
哨兵认为主节点已经下线了,于是会将一个从节点选举为新的主节点。但原主节点并不知情,仍然在继续处理客户端的请求。
等主节点网络恢复正常了,发现已经有新的主节点了,于是原主节点会自动降级为从节点。在降级过程中,它需要与新主节点进行全量同步,此时原主节点的数据会被清空。导致客户端在原主节点故障期间写入的数据全部丢失。
为了防止这种数据丢失,Redis 提供了 min-slaves-to-write 和 min-slaves-max-lag 参数。
这两个参数可以设置最少需要多少个从节点在线,以及从节点的最大延迟时间。
# 设置主节点能进行数据同步的最少从节点数量
min-slaves-to-write 1
# 设置主从节点间进行数据同步时,从节点给主节点发送 ACK 消息的最大延迟(以秒为单位)
min-slaves-max-lag 10
设置这两个参数后,如果主节点连接不到指定数量的从节点,或者从节点响应超时,主节点会拒绝写入请求,从而避免脑裂期间的数据冲突。
具体来说,当网络分区发生,主节点与从节点、哨兵之间的连接断开,但主节点与客户端的连接正常时,由于主节点无法再连接到任何从节点,或者延迟超过了设定值,比如说配置了min-slaves-to-write 1,主节点就会自动拒绝所有写请求。
同时在网络的另一侧,哨兵会检测到主节点"下线",选举一个从节点成为新的主节点。由于原主节点已经停止接受写入,所以不会产生新的数据变更,等网络恢复后,即使原主节点降级为从节点并进行全量同步,也不会丢失网络分区期间的写入数据,因为根本就没有新的写入发生。
- Java 面试指南(付费)收录的同学 30 腾讯音乐面试原题:主从复制有什么缺点呢?redis的脑裂问题
memo:2025 年 5 月 10 日今天把新项目的前置环境也配的七七八八了,还差一个 Kafka 的安装教程。日拱一卒,争取秋招前给大家球友们见面。
20.Redis哨兵机制了解吗?
Redis 中的哨兵用于监控主从集群的运行状态,并在主节点故障时自动进行故障转移。
核心功能包括监控、通知和自动故障转移。哨兵会定期检查主从节点是否按预期工作,当检测到主节点故障时,就在从节点中选举出一个新的主节点,并通知客户端连接到新的主节点。
# 监控的主节点信息 + 多少个哨兵同意才算宕机
sentinel monitor mymaster 127.0.0.1 6379 2
# 多久不响应就标记为“主观下线”
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时时间
sentinel failover-timeout mymaster 60000
# 同时允许多少个从节点同步新主节点数据
sentinel parallel-syncs mymaster 1
- Java 面试指南(付费)收录的比亚迪面经同学 1 面试原题:Redis 的哨兵机制了解吗?
21.Redis哨兵的工作原理知道吗?
哨兵的工作原理可以概括为 4 个关键步骤:定时监控、主观下线、领导者选举和故障转移。
首先,哨兵会定期向所有 Redis 节点发送 PING 命令来检测它们是否可达。如果在指定时间内没有收到回复,哨兵会将该节点标记为“主观下线”。
当一个哨兵判断主节点主观下线后,会询问其他哨兵的意见,如果达到配置的法定人数,主节点会被标记为“客观下线”。
然后开始故障转移,这个过程中,哨兵会先选举出一个领导者,领导者再从从节点中选择一个最适合的节点作为新的主节点,选择标准包括复制偏移量、优先级等因素。
确定新主节点后,哨兵会向其发送 SLAVEOF NO ONE 命令使其升级为主节点,然后向其他从节点发送 SLAVEOF 命令指向新主节点,最后通过发布/订阅机制通知客户端主节点已经发生变化。
在实际部署中,为了保证哨兵机制的可靠性,通常建议至少部署三个哨兵节点,并且这些节点应分布在不同的物理机器上,降低单...
2 条评论
回复