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为什么这么快?
答案:
- 基于内存操作:数据存储在内存中,避免了磁盘IO,内存访问速度远超磁盘(内存访问延迟约100纳秒,磁盘约10毫秒)
- 单线程模型:避免了线程切换和锁竞争的开销(Redis 6.0之前)
- IO多路复用:使用epoll/select等机制,单线程可以处理多个客户端连接
- 高效的数据结构:针对不同场景优化的底层数据结构(SDS、跳表、压缩列表等)
- 优化的编码方式:对于小数据使用紧凑的编码方式节省内存
- 简单的协议:RESP协议简单高效
二、Redis数据类型及应用场景
2.1 Redis支持哪些数据类型?
答案:
基础数据类型(5种):
- String(字符串):最基本的数据类型,二进制安全,最大512MB
- Hash(哈希):键值对集合,适合存储对象
- List(列表):双向链表,支持正向/反向遍历
- Set(集合):无序、不重复的字符串集合
- Sorted Set(有序集合):每个元素关联一个分数,按分数排序
高级数据类型(3种):
- Bitmap(位图):基于String类型实现的位操作
- HyperLogLog:用于基数统计的概率数据结构
- Geospatial(地理位置):存储地理位置信息
Redis 5.0新增:
- 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种淘汰策略:
- noeviction(默认):不淘汰,写操作返回错误
- allkeys-lru:从所有key中,移除最近最少使用的key
- allkeys-lfu:从所有key中,移除最少频率使用的key(Redis 4.0+)
- allkeys-random:从所有key中,随机移除
- volatile-lru:从设置了过期时间的key中,移除最近最少使用的key
- volatile-lfu:从设置了过期时间的key中,移除最少频率使用的key
- volatile-random:从设置了过期时间的key中,随机移除
- volatile-ttl:从设置了过期时间的key中,移除TTL最小的key
如何选择?
- 缓存场景:推荐
allkeys-lru或allkeys-lfu - 部分数据持久化:使用
volatile-lru或volatile-ttl - 严格不能淘汰:使用
noeviction,需监控内存
4.2 Redis的过期键删除策略是什么?
答案:
Redis采用惰性删除 + 定期删除的组合策略:
1. 惰性删除(Lazy Expiration)
- 访问key时检查是否过期,过期则删除
- 优点:节省CPU
- 缺点:可能存在大量过期键未被访问,浪费内存
2. 定期删除(Periodic Expiration)
- 每隔一段时间(默认100ms)随机检查一批设置了过期时间的key
- 删除其中过期的key
- 如果过期key比例超过25%,继续检查下一批
3. 内存淘汰策略(补充)
- 当内存达到maxmemory时,根据淘汰策略主动删除键
4.3 Redis如何优化内存使用?
答案:
- 使用合适的数据结构:
- 小数据使用ziplist、intset等紧凑编码
- 配置
hash-max-ziplist-entries等参数
- 设置过期时间:避免数据永久占用内存
- 控制key的长度:key名尽量简短
- 使用Redis的压缩功能:启用压缩列表
- 定期清理无用数据:使用SCAN命令分批清理
- 使用内存分析工具:redis-rdb-tools分析内存占用
- 避免大key:单个key过大会影响性能
- 使用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
复制过程:
- 从节点发送PSYNC命令
- 主节点执行BGSAVE生成RDB
- 主节点发送RDB文件给从节点
- 从节点加载RDB文件
- 主节点发送缓冲区的写命令
- 后续增量同步
优点:读写分离,提高读性能
缺点:主节点故障需手动切换
2. 哨兵模式(Sentinel)
原理:
- 在主从复制基础上增加哨兵进程
- 监控主从节点健康状态
- 主节点故障时自动故障转移
核心功能:
- 监控:检测主从节点是否正常
- 通知:故障时通知管理员或其他程序
- 自动故障转移:主节点故障时,选举新的主节点
- 配置提供:客户端连接哨兵获取主节点地址
故障转移流程:
- 哨兵检测到主节点下线(主观下线)
- 多个哨兵确认主节点下线(客观下线)
- 哨兵选举出Leader
- Leader从从节点中选出新的主节点
- 其他从节点改为复制新主节点
- 通知客户端主节点地址变更
优点:高可用,自动故障转移
缺点:不能横向扩展,写能力受限
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实现要点:
- 设置唯一标识:防止误删其他客户端的锁
- 设置过期时间:防止死锁
- 原子操作:加锁和设置过期时间必须原子
- 锁续期:业务执行时间可能超过过期时间,需要自动续期(watchdog)
- 可重入:支持同一线程多次获取锁
Redlock算法(集群环境):
- 获取当前时间戳
- 依次向N个Redis实例尝试获取锁
- 如果超过半数(N/2+1)实例加锁成功,且耗时小于锁有效时间,则认为加锁成功
- 解锁时向所有实例发送解锁命令
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单线程为什么还这么快?
答案:
- 纯内存操作:内存访问速度远超磁盘
- 单线程避免上下文切换:无锁竞争,无线程切换开销
- IO多路复用:epoll/select,单线程处理多个连接
- 高效的数据结构:SDS、跳表、压缩列表等
- 简单的协议:RESP协议解析快
Redis 6.0引入多线程:
- 仅用于网络IO读写
- 命令执行仍是单线程
- 提升网络IO性能
10.3 Redis的rehash过程是怎样的?
答案:
渐进式rehash:
- 为ht[1]分配空间,大小为第一个大于等于ht[0].used*2的2^n
- 将rehashidx设为0,表示rehash开始
- 每次对字典增删改查时,顺带将ht[0]在rehashidx索引上的键值对rehash到ht[1]
- rehashidx++
- 随着操作进行,最终ht[0]的所有键值对都迁移到ht[1]
- 释放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有哪些安全措施?
答案:
- 设置密码:
# redis.conf requirepass your_strong_password # 客户端连接 AUTH your_strong_password - 绑定IP:
# redis.conf bind 127.0.0.1 192.168.1.100 - 修改默认端口:
# redis.conf port 6380 - 禁用危险命令:
# redis.conf rename-command FLUSHDB "" rename-command FLUSHALL "" rename-command CONFIG "" rename-command KEYS "" - 使用防火墙:限制访问来源
- 开启保护模式:
protected-mode yes - 使用SSL/TLS加密传输(Redis 6.0+)
- 使用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、面试、缓存、分布式、高性能
评论区