侧边栏壁纸
博主头像
小黄的日记

行动起来,活在当下

  • 累计撰写 22 篇文章
  • 累计创建 24 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Redis面试宝典:360度全方位深度解析

henry
2025-10-29 / 0 评论 / 0 点赞 / 5 阅读 / 0 字

Redis面试宝典:360度全方位深度解析

Redis作为当今最流行的内存数据库,已成为后端开发工程师必备技能。本文将从基础概念到高级应用,全面覆盖Redis面试中的各个知识点,帮助你在面试中脱颖而出。

一、Redis基础概念

1.1 什么是Redis?

答案:

Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能键值对数据库。它支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。

核心特点:

  • 基于内存:数据存储在内存中,读写速度极快,可达10万+QPS
  • 持久化支持:提供RDB和AOF两种持久化机制
  • 数据结构丰富:不仅仅是简单的key-value存储
  • 支持事务:通过MULTI、EXEC等命令支持事务操作
  • 发布订阅:支持消息的发布与订阅模式
  • 主从复制:支持数据的主从同步
  • 集群模式:支持分布式集群部署

1.2 Redis与Memcached的区别?

答案:

特性 Redis Memcached
数据类型 支持String、Hash、List、Set、Sorted Set等 仅支持简单的String类型
持久化 支持RDB和AOF持久化 不支持持久化
数据过期 支持键级别的过期时间 支持键级别的过期时间
分布式 支持主从复制、哨兵、集群 客户端实现分布式
线程模型 单线程(6.0后支持多线程IO) 多线程
内存管理 自己实现内存管理 使用Slab Allocation机制
事务支持 支持简单事务 不支持事务
Lua脚本 支持 不支持

1.3 Redis为什么这么快?

答案:

  1. 基于内存操作:数据存储在内存中,避免了磁盘IO,内存访问速度远超磁盘(内存访问延迟约100纳秒,磁盘约10毫秒)
  2. 单线程模型:避免了线程切换和锁竞争的开销(Redis 6.0之前)
  3. IO多路复用:使用epoll/select等机制,单线程可以处理多个客户端连接
  4. 高效的数据结构:针对不同场景优化的底层数据结构(SDS、跳表、压缩列表等)
  5. 优化的编码方式:对于小数据使用紧凑的编码方式节省内存
  6. 简单的协议:RESP协议简单高效

二、Redis数据类型及应用场景

2.1 Redis支持哪些数据类型?

答案:

基础数据类型(5种):

  1. String(字符串):最基本的数据类型,二进制安全,最大512MB
  2. Hash(哈希):键值对集合,适合存储对象
  3. List(列表):双向链表,支持正向/反向遍历
  4. Set(集合):无序、不重复的字符串集合
  5. Sorted Set(有序集合):每个元素关联一个分数,按分数排序

高级数据类型(3种):

  1. Bitmap(位图):基于String类型实现的位操作
  2. HyperLogLog:用于基数统计的概率数据结构
  3. Geospatial(地理位置):存储地理位置信息

Redis 5.0新增:

  1. Stream(流):消息队列数据结构

2.2 各数据类型的应用场景是什么?

String应用场景:

  • 缓存:缓存用户信息、商品信息等(最常用)
  • 计数器:文章阅读量、点赞数(INCR/DECR命令)
  • 分布式锁:SETNX实现简单的分布式锁
  • Session共享:分布式系统的Session存储
# 缓存示例
SET user:1001 "{\"name\":\"张三\",\"age\":25}"
GET user:1001

# 计数器示例
INCR article:1001:views
INCRBY article:1001:likes 1

Hash应用场景:

  • 对象存储:存储用户信息、商品信息等结构化数据
  • 购物车:用户ID为key,商品ID为field,数量为value
# 用户信息存储
HSET user:1001 name "张三" age 25 city "北京"
HGET user:1001 name
HGETALL user:1001

# 购物车示例
HSET cart:1001 product:100 2
HSET cart:1001 product:200 1
HINCRBY cart:1001 product:100 1

List应用场景:

  • 消息队列:LPUSH+BRPOP实现阻塞队列
  • 最新列表:微博时间线、文章列表
  • 粉丝列表:关注列表、粉丝列表
# 消息队列
LPUSH queue:order "order:1001"
BRPOP queue:order 0

# 最新文章列表(最多保留100条)
LPUSH articles:latest "article:1001"
LTRIM articles:latest 0 99

Set应用场景:

  • 标签系统:用户标签、文章标签
  • 共同关注:求交集获取共同好友
  • 去重:UV统计(需要精确值时)
  • 抽奖系统:SRANDMEMBER随机抽取
# 标签系统
SADD user:1001:tags "Java" "Redis" "MySQL"
SADD user:1002:tags "Redis" "Python"

# 共同标签
SINTER user:1001:tags user:1002:tags

# 抽奖(不重复)
SPOP lottery:pool 5

Sorted Set应用场景:

  • 排行榜:游戏排行、热搜榜
  • 延迟队列:以时间戳作为score
  • 范围查询:按分数范围查询数据
# 游戏排行榜
ZADD game:rank 1000 "player:1001"
ZADD game:rank 2000 "player:1002"
ZREVRANGE game:rank 0 9 WITHSCORES  # 前10名

# 延迟队列
ZADD delay:queue 1698765432 "task:1001"
ZRANGEBYSCORE delay:queue 0 1698765432 # 获取到期任务

Bitmap应用场景:

  • 用户签到:每天一位表示是否签到
  • 在线状态:千万用户在线状态统计
  • 布隆过滤器:判断元素是否存在
# 用户签到(用户ID 1001,第100天签到)
SETBIT sign:1001 100 1
GETBIT sign:1001 100
BITCOUNT sign:1001  # 统计签到天数

HyperLogLog应用场景:

  • UV统计:网站访问用户数统计(允许误差)
  • 搜索关键词统计:独立搜索用户数
# UV统计
PFADD page:index:uv user:1001 user:1002
PFCOUNT page:index:uv

Geospatial应用场景:

  • 附近的人:查找附近的用户、商家
  • 打车应用:计算距离、查找附近车辆
# 添加位置
GEOADD drivers 116.404 39.915 "driver:1001"
GEOADD drivers 116.405 39.916 "driver:1002"

# 查找5公里内的司机
GEORADIUS drivers 116.404 39.915 5 km WITHDIST

三、Redis持久化机制

3.1 Redis有哪些持久化方式?

答案:

1. RDB(Redis Database)快照持久化

原理:在指定时间间隔内,将内存中的数据集快照写入磁盘,生成dump.rdb文件。

触发方式:

  • 手动触发:SAVE(阻塞)、BGSAVE(后台执行)
  • 自动触发:配置save规则,如 save 900 1(900秒内至少1个键被修改)
  • 其他触发:SHUTDOWN、主从复制时

优点:

  • RDB文件紧凑,适合备份和灾难恢复
  • 恢复速度快,直接加载到内存
  • 性能影响小(fork子进程执行)

缺点:

  • 可能丢失最后一次快照后的数据
  • 数据量大时,fork子进程可能耗时

2. AOF(Append Only File)追加文件持久化

原理:记录每个写操作命令,以日志形式追加到appendonly.aof文件。

同步策略:

  • always:每个写命令都立即同步(最安全,性能最差)
  • everysec:每秒同步一次(默认,平衡性能和安全)
  • no:由操作系统决定何时同步(性能最好,可能丢失较多数据)

AOF重写:

当AOF文件过大时,Redis会重写AOF文件,只保留恢复当前数据所需的最小命令集。

优点:

  • 数据安全性高,最多丢失1秒数据(everysec)
  • 文件可读,可手动修复

缺点:

  • AOF文件通常比RDB大
  • 恢复速度比RDB慢
  • 性能开销比RDB大

3. 混合持久化(Redis 4.0+)

原理:AOF重写时,将重写前的数据以RDB格式写入AOF文件开头,增量数据以AOF格式追加。

优点:结合了RDB的快速恢复和AOF的数据安全性

3.2 如何选择持久化方式?

答案:

  • 对数据安全性要求高:使用AOF(everysec)或混合持久化
  • 可以接受分钟级数据丢失:使用RDB,性能更好
  • 生产环境推荐:同时开启RDB和AOF,兼顾性能和安全
  • 缓存场景:可以不开启持久化,降低性能开销

四、Redis内存管理

4.1 Redis的内存淘汰策略有哪些?

答案:

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

8种淘汰策略:

  1. noeviction(默认):不淘汰,写操作返回错误
  2. allkeys-lru:从所有key中,移除最近最少使用的key
  3. allkeys-lfu:从所有key中,移除最少频率使用的key(Redis 4.0+)
  4. allkeys-random:从所有key中,随机移除
  5. volatile-lru:从设置了过期时间的key中,移除最近最少使用的key
  6. volatile-lfu:从设置了过期时间的key中,移除最少频率使用的key
  7. volatile-random:从设置了过期时间的key中,随机移除
  8. volatile-ttl:从设置了过期时间的key中,移除TTL最小的key

如何选择?

  • 缓存场景:推荐 allkeys-lruallkeys-lfu
  • 部分数据持久化:使用 volatile-lruvolatile-ttl
  • 严格不能淘汰:使用 noeviction,需监控内存

4.2 Redis的过期键删除策略是什么?

答案:

Redis采用惰性删除 + 定期删除的组合策略:

1. 惰性删除(Lazy Expiration)

  • 访问key时检查是否过期,过期则删除
  • 优点:节省CPU
  • 缺点:可能存在大量过期键未被访问,浪费内存

2. 定期删除(Periodic Expiration)

  • 每隔一段时间(默认100ms)随机检查一批设置了过期时间的key
  • 删除其中过期的key
  • 如果过期key比例超过25%,继续检查下一批

3. 内存淘汰策略(补充)

  • 当内存达到maxmemory时,根据淘汰策略主动删除键

4.3 Redis如何优化内存使用?

答案:

  1. 使用合适的数据结构:
    • 小数据使用ziplist、intset等紧凑编码
    • 配置 hash-max-ziplist-entries 等参数
  2. 设置过期时间:避免数据永久占用内存
  3. 控制key的长度:key名尽量简短
  4. 使用Redis的压缩功能:启用压缩列表
  5. 定期清理无用数据:使用SCAN命令分批清理
  6. 使用内存分析工具:redis-rdb-tools分析内存占用
  7. 避免大key:单个key过大会影响性能
  8. 使用Pipeline:减少网络往返次数

五、Redis事务

5.1 Redis如何实现事务?

答案:

Redis通过MULTI、EXEC、DISCARD、WATCH四个命令实现事务。

基本使用:

MULTI           # 开启事务
SET key1 value1
SET key2 value2
INCR counter
EXEC            # 执行事务

事务特性:

  • 批量执行:事务中的命令会被序列化,按顺序执行
  • 原子性(部分):
    • 命令入队错误:整个事务不执行
    • 执行期错误:不会回滚,其他命令继续执行
  • 隔离性:事务执行期间不会被其他客户端命令打断

WATCH机制(乐观锁):

WATCH key1      # 监视key
MULTI
SET key1 value1
EXEC            # 如果key1被修改,事务失败

5.2 Redis事务与关系型数据库事务的区别?

答案:

特性 Redis事务 MySQL事务
原子性 部分支持,不支持回滚 完全支持,可回滚
一致性 支持 支持
隔离性 串行执行,天然隔离 多种隔离级别
持久性 取决于持久化配置 支持
回滚 不支持 支持

六、Redis集群与高可用

6.1 Redis有哪些集群方案?

答案:

1. 主从复制(Master-Slave Replication)

原理:

  • 一个主节点(Master),多个从节点(Slave)
  • 主节点负责写,从节点负责读
  • 数据从主节点异步复制到从节点

配置:

# 从节点配置
replicaof 192.168.1.100 6379

复制过程:

  1. 从节点发送PSYNC命令
  2. 主节点执行BGSAVE生成RDB
  3. 主节点发送RDB文件给从节点
  4. 从节点加载RDB文件
  5. 主节点发送缓冲区的写命令
  6. 后续增量同步

优点:读写分离,提高读性能

缺点:主节点故障需手动切换

2. 哨兵模式(Sentinel)

原理:

  • 在主从复制基础上增加哨兵进程
  • 监控主从节点健康状态
  • 主节点故障时自动故障转移

核心功能:

  • 监控:检测主从节点是否正常
  • 通知:故障时通知管理员或其他程序
  • 自动故障转移:主节点故障时,选举新的主节点
  • 配置提供:客户端连接哨兵获取主节点地址

故障转移流程:

  1. 哨兵检测到主节点下线(主观下线)
  2. 多个哨兵确认主节点下线(客观下线)
  3. 哨兵选举出Leader
  4. Leader从从节点中选出新的主节点
  5. 其他从节点改为复制新主节点
  6. 通知客户端主节点地址变更

优点:高可用,自动故障转移

缺点:不能横向扩展,写能力受限

3. Redis Cluster(集群模式)

原理:

  • 数据分片存储,每个节点存储部分数据
  • 使用哈希槽(Hash Slot)分配数据,共16384个槽
  • 每个节点负责一部分槽
  • 支持主从复制,高可用

数据分片:

# 计算key属于哪个槽
SLOT = CRC16(key) % 16384

优点:

  • 横向扩展,突破单机内存限制
  • 高可用,自动故障转移
  • 官方支持,无需第三方组件

缺点:

  • 不支持多键操作(除非key在同一槽)
  • 配置复杂
  • 客户端需要支持集群协议

4. 客户端分片(应用层)

原理:应用层通过一致性哈希等算法将数据分配到多个Redis实例

优点:简单灵活

缺点:需要应用层实现,扩容复杂

6.2 Redis集群如何保证数据一致性?

答案:

主从复制的一致性问题:

  • 异步复制:默认情况下主从复制是异步的,可能存在数据丢失
  • WAIT命令:等待至少N个从节点确认写入
SET key value
WAIT 2 1000  # 等待至少2个从节点确认,超时1000ms

集群模式的一致性:

  • 最终一致性:异步复制,可能短暂不一致
  • 脑裂问题:网络分区可能导致多个主节点,Redis通过min-replicas-to-write参数缓解

七、Redis缓存问题

7.1 什么是缓存穿透?如何解决?

答案:

定义:

查询一个缓存和数据库都不存在的数据,每次请求都会打到数据库。

危害:

  • 数据库压力激增
  • 可能被恶意攻击

解决方案:

1. 缓存空值

  • 将不存在的key也缓存,设置较短过期时间
String value = redis.get(key);
if (value == null) {
    value = db.query(key);
    if (value == null) {
        redis.setex(key, 60, "");  // 缓存空值,60秒过期
    } else {
        redis.setex(key, 3600, value);
    }
}

2. 布隆过滤器(Bloom Filter)

  • 在缓存前增加布隆过滤器
  • 判断key是否可能存在
  • 不存在直接返回,不查数据库
if (!bloomFilter.mightContain(key)) {
    return null;  // 一定不存在
}
// 可能存在,继续查缓存和数据库

3. 接口校验

  • 参数校验、权限校验
  • 限流、降级

7.2 什么是缓存击穿?如何解决?

答案:

定义:

一个热点key突然失效,大量请求同时访问这个key,瞬间打到数据库。

场景:

  • 热门商品详情
  • 热搜榜单

解决方案:

1. 互斥锁(Mutex Lock)

  • 缓存失效时,只允许一个线程查数据库
  • 其他线程等待
String value = redis.get(key);
if (value == null) {
    String lockKey = "lock:" + key;
    if (redis.setnx(lockKey, "1", 10)) {  // 获取锁
        try {
            value = db.query(key);
            redis.setex(key, 3600, value);
        } finally {
            redis.del(lockKey);  // 释放锁
        }
    } else {
        Thread.sleep(100);  // 等待后重试
        return get(key);
    }
}

2. 热点数据永不过期

  • 逻辑上不设置过期时间
  • 异步线程定期更新

3. 设置随机过期时间

  • 避免大量key同时失效
int expire = 3600 + new Random().nextInt(300);  // 3600-3900秒
redis.setex(key, expire, value);

7.3 什么是缓存雪崩?如何解决?

答案:

定义:

大量缓存同时失效,或者Redis宕机,大量请求打到数据库。

场景:

  • 缓存集中过期
  • Redis服务器宕机

解决方案:

1. 过期时间打散

  • 设置随机过期时间,避免同时失效
int baseExpire = 3600;
int randomExpire = baseExpire + new Random().nextInt(600);
redis.setex(key, randomExpire, value);

2. 使用Redis集群

  • 高可用架构(哨兵、集群)
  • 主从切换

3. 限流降级

  • Hystrix等限流组件
  • 降级返回默认值

4. 多级缓存

  • 本地缓存(Caffeine、Guava Cache) + Redis
  • Redis挂了还有本地缓存

5. 缓存预热

  • 系统启动时提前加载热点数据

7.4 如何保证缓存与数据库的双写一致性?

答案:

常见策略:

1. Cache Aside Pattern(旁路缓存)

  • 读:先读缓存,miss则读数据库,再写缓存
  • 写:先更新数据库,再删除缓存

为什么是删除而不是更新?

  • 更新缓存可能浪费(如果这个数据后续不再被访问)
  • 更新复杂数据需要计算,消耗资源

2. 先更新数据库,再删除缓存(推荐)

// 写操作
db.update(key, value);
redis.del(key);  // 删除缓存

// 读操作
value = redis.get(key);
if (value == null) {
    value = db.query(key);
    redis.setex(key, 3600, value);
}

可能的问题:删除缓存失败

解决:

  • 重试机制
  • 消息队列异步删除
  • 订阅MySQL的binlog,异步删除缓存

3. 延迟双删

redis.del(key);  // 第一次删除
db.update(key, value);
Thread.sleep(500);  // 延迟
redis.del(key);  // 第二次删除

4. 设置较短过期时间

  • 即使不一致,也会在短时间内自动恢复

八、Redis性能优化

8.1 如何排查Redis性能问题?

答案:

1. 使用慢查询日志

# 配置慢查询阈值(微秒)
CONFIG SET slowlog-log-slower-than 10000

# 查看慢查询日志
SLOWLOG GET 10

2. 使用INFO命令

INFO stats          # 统计信息
INFO memory         # 内存使用
INFO replication    # 主从复制
INFO clients        # 客户端连接

3. 使用MONITOR命令

MONITOR  # 实时打印所有命令(生产慎用,影响性能)

4. 使用redis-cli --bigkeys

redis-cli --bigkeys  # 找出大key

5. 查看延迟

redis-cli --latency  # 测试延迟
redis-cli --latency-history  # 延迟历史

8.2 Redis性能优化建议有哪些?

答案:

1. 避免大key

  • 单个key过大(如几MB的字符串)会导致:
    • 网络传输慢
    • 分配内存慢
    • 淘汰删除慢
  • 建议:拆分大key为多个小key

2. 避免热key

  • 单个key访问量特别大,可能导致:
    • 单点瓶颈
    • 集群节点负载不均
  • 解决:
    • 本地缓存
    • 将热key复制多份(key:1, key:2, ...),随机访问

3. 使用Pipeline

  • 批量执行命令,减少网络往返
Pipeline pipeline = redis.pipelined();
for (int i = 0; i < 1000; i++) {
    pipeline.set("key" + i, "value" + i);
}
pipeline.sync();

4. 使用连接池

  • 复用连接,避免频繁创建销毁

5. 合理设置过期时间

  • 避免内存占用过高
  • 过期时间打散,避免集中过期

6. 选择合适的数据结构

  • 小数据使用ziplist等紧凑编码

7. 禁用危险命令

  • 生产环境禁用KEYS、FLUSHALL等
# redis.conf
rename-command KEYS ""
rename-command FLUSHALL ""

8. 使用读写分离

  • 主节点写,从节点读

9. 合理配置持久化

  • 根据业务场景选择RDB/AOF
  • 避免在高峰期执行BGSAVE

九、Redis应用场景深入

9.1 如何使用Redis实现分布式锁?

答案:

简单实现(有缺陷):

# 加锁
SET lock:resource "unique_value" NX EX 30

# 解锁(需要判断是否是自己持有的锁)
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

完整的Redisson实现要点:

  1. 设置唯一标识:防止误删其他客户端的锁
  2. 设置过期时间:防止死锁
  3. 原子操作:加锁和设置过期时间必须原子
  4. 锁续期:业务执行时间可能超过过期时间,需要自动续期(watchdog)
  5. 可重入:支持同一线程多次获取锁

Redlock算法(集群环境):

  1. 获取当前时间戳
  2. 依次向N个Redis实例尝试获取锁
  3. 如果超过半数(N/2+1)实例加锁成功,且耗时小于锁有效时间,则认为加锁成功
  4. 解锁时向所有实例发送解锁命令

9.2 如何使用Redis实现限流?

答案:

1. 固定窗口计数器

-- 限流:每分钟最多100次
local key = "rate_limit:" .. KEYS[1]
local limit = 100
local current = redis.call("INCR", key)
if current == 1 then
    redis.call("EXPIRE", key, 60)
end
if current > limit then
    return 0  -- 限流
else
    return 1  -- 通过
end

2. 滑动窗口(Sorted Set)

local key = "rate_limit:" .. KEYS[1]
local now = tonumber(ARGV[1])
local window = 60000  -- 60秒
local limit = 100

-- 移除窗口外的记录
redis.call("ZREMRANGEBYSCORE", key, 0, now - window)

-- 统计当前窗口内的请求数
local current = redis.call("ZCARD", key)
if current < limit then
    redis.call("ZADD", key, now, now)
    redis.call("EXPIRE", key, 60)
    return 1
else
    return 0
end

3. 令牌桶(Token Bucket)

使用Lua脚本模拟令牌生成和消费

9.3 如何使用Redis实现延迟队列?

答案:

使用Sorted Set:

// 添加延迟任务(10秒后执行)
long executeTime = System.currentTimeMillis() + 10000;
redis.zadd("delay_queue", executeTime, taskId);

// 消费任务(轮询)
while (true) {
    long now = System.currentTimeMillis();
    Set tasks = redis.zrangeByScore("delay_queue", 0, now, 0, 1);
    if (tasks.isEmpty()) {
        Thread.sleep(100);
        continue;
    }
    
    String task = tasks.iterator().next();
    // 删除任务(防止重复消费)
    if (redis.zrem("delay_queue", task) > 0) {
        // 执行任务
        executeTask(task);
    }
}

十、Redis底层实现

10.1 Redis的底层数据结构有哪些?

答案:

1. SDS(Simple Dynamic String)简单动态字符串

  • 相比C字符串的优势:
    • 记录长度,O(1)获取长度
    • 二进制安全(可存储任意二进制数据)
    • 杜绝缓冲区溢出
    • 减少内存重分配(预分配和惰性释放)

2. 链表(LinkedList)

  • 双向链表
  • 用于List类型、发布订阅、慢查询等

3. 字典(Dict)哈希表

  • Hash类型的底层实现之一
  • Redis数据库本身也是字典
  • 渐进式rehash:避免一次性rehash导致的阻塞

4. 跳表(Skip List)

  • Sorted Set的底层实现之一
  • 平均O(logN)、最坏O(N)复杂度
  • 相比红黑树,实现简单,范围查询效率高

5. 整数集合(IntSet)

  • Set类型的底层实现之一(元素都是整数且数量不多时)
  • 有序、无重复
  • 支持升级(int16 → int32 → int64)

6. 压缩列表(ZipList)

  • List、Hash、Sorted Set的底层实现之一(元素少时)
  • 连续内存,节省空间
  • 查询复杂度O(N)

7. 快速列表(QuickList)

  • List的底层实现(Redis 3.2+)
  • 双向链表 + 压缩列表的组合
  • 兼顾空间和时间

8. 流对象(Stream)

  • Redis 5.0新增
  • 类似于Kafka的消息队列

10.2 Redis单线程为什么还这么快?

答案:

  1. 纯内存操作:内存访问速度远超磁盘
  2. 单线程避免上下文切换:无锁竞争,无线程切换开销
  3. IO多路复用:epoll/select,单线程处理多个连接
  4. 高效的数据结构:SDS、跳表、压缩列表等
  5. 简单的协议:RESP协议解析快

Redis 6.0引入多线程:

  • 仅用于网络IO读写
  • 命令执行仍是单线程
  • 提升网络IO性能

10.3 Redis的rehash过程是怎样的?

答案:

渐进式rehash:

  1. 为ht[1]分配空间,大小为第一个大于等于ht[0].used*2的2^n
  2. 将rehashidx设为0,表示rehash开始
  3. 每次对字典增删改查时,顺带将ht[0]在rehashidx索引上的键值对rehash到ht[1]
  4. rehashidx++
  5. 随着操作进行,最终ht[0]的所有键值对都迁移到ht[1]
  6. 释放ht[0],将ht[1]设置为ht[0],创建新的空白ht[1]

优点:避免一次性rehash导致的长时间阻塞

十一、Redis实战场景题

11.1 设计一个微博的关注/粉丝系统

答案:

# 用户A关注用户B
SADD following:A B      # A的关注列表
SADD followers:B A      # B的粉丝列表

# 用户A取消关注用户B
SREM following:A B
SREM followers:B A

# 获取A的关注列表
SMEMBERS following:A

# 获取B的粉丝列表
SMEMBERS followers:B

# A和C的共同关注
SINTER following:A following:C

# A关注的人也关注了谁(推荐关注)
SDIFF following:following:A following:A

11.2 设计一个热搜排行榜

答案:

# 搜索词被搜索时,分数+1
ZINCRBY hotsearch:20231029 1 "关键词"

# 获取实时热搜Top10
ZREVRANGE hotsearch:20231029 0 9 WITHSCORES

# 获取某个词的排名(从0开始)
ZREVRANK hotsearch:20231029 "关键词"

11.3 设计一个秒杀系统的库存扣减

答案:

-- Lua脚本保证原子性
local key = KEYS[1]
local count = tonumber(ARGV[1])
local stock = tonumber(redis.call("GET", key) or "0")

if stock >= count then
    redis.call("DECRBY", key, count)
    return 1  -- 成功
else
    return 0  -- 库存不足
end
// Java调用
String script = "...";  // 上面的Lua脚本
Object result = redis.eval(script, 
    Collections.singletonList("product:1001:stock"), 
    Collections.singletonList("1")
);
if ((Long)result == 1) {
    // 扣减成功,继续下单流程
} else {
    // 库存不足
}

11.4 设计一个文章阅读量统计(去重)

答案:

方案1:精确统计(Set)

# 用户ID 1001阅读了文章 article:100
SADD article:100:readers 1001

# 获取阅读量
SCARD article:100:readers

缺点:内存占用大

方案2:近似统计(HyperLogLog)

# 用户阅读文章
PFADD article:100:uv 1001

# 获取UV(近似值,误差0.81%)
PFCOUNT article:100:uv

优点:内存占用小(12KB),适合大数据量

十二、Redis安全

12.1 Redis有哪些安全措施?

答案:

  1. 设置密码:
    # redis.conf
    requirepass your_strong_password
    
    # 客户端连接
    AUTH your_strong_password
    
  2. 绑定IP:
    # redis.conf
    bind 127.0.0.1 192.168.1.100
    
  3. 修改默认端口:
    # redis.conf
    port 6380
    
  4. 禁用危险命令:
    # redis.conf
    rename-command FLUSHDB ""
    rename-command FLUSHALL ""
    rename-command CONFIG ""
    rename-command KEYS ""
    
  5. 使用防火墙:限制访问来源
  6. 开启保护模式:
    protected-mode yes
    
  7. 使用SSL/TLS加密传输(Redis 6.0+)
  8. 使用ACL访问控制(Redis 6.0+)
    ACL SETUSER alice on >password ~cached:* +get
    

总结

本文全面覆盖了Redis面试中的各个知识点,包括:

  • 基础概念:Redis特性、与Memcached区别、性能优势
  • 数据类型:5种基础类型+高级类型,应用场景详解
  • 持久化:RDB、AOF、混合持久化
  • 内存管理:淘汰策略、过期删除、内存优化
  • 事务:MULTI/EXEC、WATCH机制
  • 集群:主从复制、哨兵、Cluster
  • 缓存问题:穿透、击穿、雪崩、双写一致性
  • 性能优化:慢查询、大key/热key、Pipeline
  • 应用场景:分布式锁、限流、延迟队列
  • 底层实现:数据结构、单线程模型、rehash
  • 实战场景:关注系统、排行榜、秒杀、统计
  • 安全措施:密码、ACL、命令重命名

建议结合实际项目经验,多做练习,深入理解Redis的各个知识点。祝你面试顺利!🚀


关键词:Redis、面试、缓存、分布式、高性能

0

评论区