Redis100道高频面试题
一、Redis基础
Redis是什么?主要应用场景有哪些?
Redis 是一个开源的、基于内存的数据结构存储系统,支持多种数据结构(如字符串、哈希、列表、集合等),可以用作数据库、缓存和消息中间件。 主要应用场景:
-
缓存:利用Redis的高性能读写能力,作为应用的缓存层,减少对后端数据库的压力。
-
会话存储:存储用户的会话信息,实现分布式环境下的Session共享。
-
消息队列:通过发布/订阅模式或列表数据结构实现消息队列功能。
-
计数器与排行榜:利用Redis的原子操作实现实时计数和排行榜功能。
-
实时分析:处理实时数据流,进行统计分析。
-
分布式锁:实现跨服务的分布式锁。
Redis与Memcached的区别是什么?
特性 | Redis | Memcached |
---|---|---|
数据持久化 | 支持RDB和AOF持久化 | 不支持持久化 |
数据类型 | 支持多种数据结构(字符串、哈希等) | 仅支持简单的键值对 |
单线程/多线程 | 单线程 | 多线程 |
内存管理 | 更灵活的内存管理 | 固定大小的内存分配(Slab机制) |
事务支持 | 支持事务 | 不支持事务 |
插件扩展性 | 支持Lua脚本 | 不支持脚本 |
Redis支持哪些数据类型?分别举例说明。
Redis支持以下数据类型:
-
字符串(String):存储简单的键值对。例如:
SET name "Qwen"
。 -
哈希(Hash):存储字段-值对。例如:
HSET user:1001 name "Alice" age 25
。 -
列表(List):存储有序的字符串列表。例如:
LPUSH queue item1 item2
。 -
集合(Set):存储无序且唯一的字符串集合。例如:
SADD fruits apple banana
。 -
有序集合(Sorted Set):存储带分数排序的集合。例如:
ZADD scores 100 user1 90 user2
。 -
位图(Bitmap):用于高效存储布尔值。例如:
SETBIT bitmap 10 1
。 -
HyperLogLog:用于估算唯一值的数量。例如:
PFADD visitors user1 user2
。
Redis为什么是单线程的?单线程为何高效?
-
单线程的原因:
-
Redis的核心操作(如读写、命令执行)非常简单且高效,单线程可以避免多线程间的上下文切换开销。
-
使用I/O多路复用技术(epoll/kqueue/select)来处理并发连接,保证高吞吐量。
-
-
单线程高效的原理:
-
减少了锁竞争和线程切换的开销。
-
命令执行时间短,适合内存操作。
-
数据结构简单,减少了复杂度。
-
Redis的持久化机制有哪些?RDB和AOF的区别?
持久化机制:
-
RDB(快照持久化):定期将内存中的数据保存到磁盘上。
-
AOF(追加日志持久化):记录每个写操作的命令日志,重启时重新执行这些命令恢复数据。
RDB与AOF的区别:
特性 | RDB | AOF |
---|---|---|
持久化方式 | 定期生成数据快照 | 记录操作命令日志 |
恢复速度 | 快 | 较慢 |
数据安全性 | 可能丢失最后一次快照后的数据 | 数据更安全,但可能有重复命令问题 |
文件大小 | 较小 | 较大 |
如何配置Redis的RDB持久化?
在Redis配置文件 redis.conf
中设置以下参数:
save 900 1 # 900秒内至少有1个key发生变化时触发快照 save 300 10 # 300秒内至少有10个key发生变化时触发快照 save 60 10000 # 60秒内至少有10000个key发生变化时触发快照 rdbcompression yes # 是否压缩RDB文件 rdbchecksum yes # 是否计算校验和
AOF重写(AOF Rewrite)的作用是什么?
AOF重写的作用是压缩AOF文件,通过合并冗余命令生成一个新的更小的日志文件,从而节省磁盘空间并提高加载效率。例如,多个INCR
命令可以合并为一个最终值。
Redis的过期键删除策略有哪些?
Redis提供了三种过期键删除策略:
-
定时删除:在设置键过期时间时启动定时器,立即删除。
-
惰性删除:只有当访问某个键时才检查是否已过期,过期则删除。
-
定期删除:后台定期扫描一部分键空间,删除已过期的键。
解释Redis的LRU内存淘汰机制。
LRU(Least Recently Used)是一种内存淘汰策略,Redis会在内存不足时优先淘汰最近最少使用的键值对。 例如:maxmemory-policy allkeys-lru
表示在所有键中使用LRU算法淘汰数据。
Redis的事务(MULTI/EXEC)与数据库事务有何区别?
特性 | Redis事务 | 数据库事务 |
---|---|---|
隔离性 | 不提供隔离性,命令间可能会被其他客户端打断 | 提供ACID特性,严格隔离 |
回滚支持 | 不支持回滚 | 支持回滚 |
执行方式 | 将命令打包成队列,一次性执行 | 在事务块中执行多个SQL语句 |
锁机制 | 无锁机制 | 使用行锁、表锁等机制 |
总结:Redis事务更轻量级,适用于快速操作场景;而数据库事务更适合复杂的业务逻辑。
二、Redis数据结构
String类型的底层实现是什么?
Redis的String
类型底层使用SDS(Simple Dynamic String,简单动态字符串)实现。 SDS的特点:
-
内部是一个结构体,包含长度信息、分配的空间大小和实际存储的字符数组。
-
提供了预分配冗余空间机制,减少内存分配频率。
-
相比C语言的字符串,SDS更安全(避免缓冲区溢出),且支持快速获取字符串长度。
如何用Redis实现分布式锁?
Redis可以利用其原子性和过期时间特性实现分布式锁。以下是基本实现步骤:
-
使用
SET key value NX PX timeout
命令尝试设置锁(NX
表示只有键不存在时才设置,PX
指定超时时间)。 -
如果设置成功,则获得锁;否则等待一段时间后重试。
-
解锁时,确保当前客户端持有的锁与加锁时的值一致,避免误删其他客户端的锁。可以通过Lua脚本实现解锁操作。
示例代码(Lua脚本解锁):
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
List类型的应用场景有哪些?(如消息队列)
List
类型适用于以下场景:
-
消息队列:通过
LPUSH
将任务推入队列,RPOP
或BLPOP
消费任务。 示例:LPUSH queue task1
和BRPOP queue
。 -
最新N条数据缓存:如保存最新的100条评论。 示例:
LTRIM list 0 99
保留前100个元素。 -
发布/订阅系统:配合
PUBLISH
和SUBSCRIBE
实现简单的消息通知。
Hash类型适合存储什么数据?
Hash
类型适合存储对象属性或结构化数据,例如用户信息、商品详情等。 示例:
HSET user:1001 name "Alice" age 25 gender "female" HGETALL user:1001
优点:
-
数据紧凑,占用较少内存。
-
支持原子性更新字段值。
Set和Sorted Set的区别是什么?
特性 | Set | Sorted Set |
---|---|---|
数据结构 | 无序集合,存储唯一值 | 带分数排序的有序集合 |
排序功能 | 不支持排序 | 按分数排序 |
典型命令 | SADD、SMEMBERS、SINTER | ZADD、ZRANGE、ZREVRANGE |
应用场景 | 存储唯一值(如好友列表) | 排行榜、带权重的数据排序 |
Sorted Set的底层数据结构是什么?
Sorted Set
的底层由两个数据结构组成:
-
跳跃表(Skip List):用于按分数排序,支持快速查找和插入。
-
哈希表:用于存储成员到分数的映射关系,保证成员唯一性。
跳跃表的优点是能在O(log N)时间内完成插入、删除和查找操作。
HyperLogLog的作用是什么?误差率多少?
HyperLogLog
是一种概率数据结构,用于估算唯一值的数量(即基数)。 特点:
-
内存占用极低,适合处理大规模数据集。
-
误差率默认为 0.81%,可以通过调整精度参数进一步优化。
典型应用场景:
-
统计独立访客数(UV)。
-
计算不重复的IP地址数量。
示例:
PFADD visitors user1 user2 user3 PFCOUNT visitors
Bitmap的应用场景有哪些?(如用户签到)
Bitmap
是一种基于位的操作方式,每个位表示一个布尔值(0或1)。 典型应用场景:
-
用户签到:记录用户某天是否签到。 示例:
SETBIT user:1001 1 1
表示用户1001在第2天签到。 -
统计活跃用户:记录某段时间内的活跃状态。
-
权限管理:用位图表示用户的权限集合。
优点:内存占用非常小,适合大规模数据统计。
GEO类型如何实现地理位置查询?
Redis的GEO
类型基于地理空间索引实现,支持存储经纬度坐标并进行距离计算。 常用命令:
-
GEOADD
:添加地理坐标。 示例:GEOADD places 116.4074 39.9042 "Beijing"
-
GEODIST
:计算两点间距离。 示例:GEODIST places Beijing Shanghai
-
GEORADIUS
:查询某个范围内的位置。 示例:GEORADIUS places 116.4074 39.9042 10 km
底层使用GeoHash算法编码经纬度,支持高效的空间查询。
Stream类型的作用是什么?
Stream
是Redis 5.0引入的一种新数据结构,专为消息流设计,具有以下特点:
-
高可靠性:消息不会丢失,支持持久化。
-
消费者组:支持多消费者并发消费消息。
-
回溯能力:可以读取历史消息。
-
分片扩展:支持水平扩展以处理海量消息。
典型应用场景:
-
实时日志处理。
-
分布式任务队列。
-
消息中间件替代方案。
示例:
XADD stream * field1 value1 field2 value2 # 添加消息 XREAD STREAMS stream ID # 读取消息
三、持久化与高可用
RDB持久化的优缺点是什么?
优点:
-
恢复速度快:RDB文件是紧凑的二进制快照,加载时速度较快。
-
文件体积小:相比AOF日志,RDB文件占用空间更少。
-
适合全量备份:便于定期备份和迁移。
缺点:
-
数据安全性较低:由于是周期性快照,可能会丢失最后一次快照后的数据。
-
保存过程阻塞:在保存RDB文件时,可能会影响Redis性能(可通过
BGSAVE
异步保存缓解)。
AOF持久化的优缺点是什么?
优点:
-
数据安全性高:几乎不会丢失数据,每次写操作都会记录到AOF文件中。
-
可配置同步频率:支持
always
(每次写入)、everysec
(每秒同步)和no
(由操作系统决定)三种同步策略。
缺点:
-
文件体积大:AOF文件记录了所有写操作命令,文件增长较快。
-
恢复速度慢:重启时需要重新执行AOF文件中的所有命令,耗时较长。
-
冗余信息多:可能存在重复或冗长的日志记录(可通过AOF重写优化)。
如何同时启用RDB和AOF?
可以在redis.conf
中同时开启RDB和AOF:
save 900 1 # 配置RDB快照规则 appendonly yes # 开启AOF
当两者同时启用时,Redis会优先使用AOF进行数据恢复(因为AOF的数据更新更及时)。如果AOF文件损坏,则会回退到RDB恢复。
Redis主从复制的原理是什么?
Redis主从复制的基本原理:
-
全量复制:
-
主节点将内存数据生成RDB快照并发送给从节点。
-
从节点接收RDB文件并加载到内存。
-
-
增量复制:
-
主节点将后续的写操作以命令形式追加到从节点。
-
如果网络中断导致命令丢失,可以通过部分重同步机制(PSYNC)恢复。
-
主从复制如何配置?
在从节点的redis.conf
中添加以下配置:
slaveof <master-ip> <master-port>
或者通过命令动态设置:
SLAVEOF <master-ip> <master-port>
主节点默认允许从节点连接,无需额外配置。如果需要密码验证,可以设置requirepass
并在从节点配置masterauth
。
什么是哨兵(Sentinel)模式?作用是什么?
哨兵模式是一种高可用解决方案,Redis Sentinel负责监控主从节点的状态,并在主节点故障时自动切换到新的主节点。 主要作用:
-
监控:实时检测主从节点是否正常运行。
-
故障转移:当主节点不可用时,选举一个从节点升级为主节点。
-
通知:向客户端或其他系统发送故障通知。
哨兵如何选举新的主节点?
哨兵选举新主节点的过程:
-
多数投票:多个哨兵节点协商,确保大多数哨兵同意切换。
-
选择从节点:
-
优先选择与原主节点同步数据最多的从节点。
-
如果同步数据相同,则选择优先级最高的从节点(通过
slave-priority
配置)。 -
如果优先级相同,则选择运行时间最长的从节点。
-
-
升级为新主节点:被选中的从节点执行
SLAVEOF NO ONE
命令,成为新的主节点。
Redis Cluster如何实现数据分片?
Redis Cluster通过哈希槽(Hash Slot)实现数据分片:
-
整个集群有16384个哈希槽,每个键根据其哈希值映射到一个槽。
-
每个节点负责一部分槽,数据存储在对应的槽中。
-
客户端通过计算
CRC16(key) % 16384
确定键所属的槽,并找到负责该槽的节点。
Redis Cluster的节点间通信协议是什么?
Redis Cluster使用Gossip协议进行节点间通信,主要包括以下几种消息类型:
-
Ping消息:用于心跳检测,确认节点状态。
-
Pong消息:对Ping消息的响应。
-
Meet消息:新节点加入集群时广播自身信息。
-
Fail消息:通知其他节点某个节点已失效。
通过这些消息,节点之间可以共享集群拓扑信息、槽分布情况和节点状态。
如何解决Redis Cluster的扩容和缩容问题?
扩容:
-
添加新节点到集群。
-
使用
CLUSTER ADDSLOTS
分配部分槽给新节点。 -
使用
redis-trib
工具或手动迁移数据到新节点。
缩容:
-
将目标节点上的槽迁移到其他节点。
-
使用
CLUSTER DELSLOTS
移除槽。 -
将目标节点从集群中移除。
注意事项:
-
扩容和缩容过程中需确保集群始终处于稳定状态(至少一半的主节点在线)。
-
数据迁移可能会影响性能,建议在低峰期操作。
四、性能与优化
Redis的QPS一般能达到多少?
Redis的QPS(每秒查询次数)取决于硬件配置和使用场景,通常在单线程模式下:
-
普通操作:如
GET
、SET
,QPS可达10万~20万。 -
复杂操作:如
ZINTERSTORE
、SORT
等,QPS可能降至几千。
通过多核部署(如Cluster模式)或优化网络延迟,可以进一步提升QPS。
如何监控Redis的性能?(如INFO命令)
Redis提供了多种方式监控性能:
-
INFO命令:
-
INFO stats
:查看命中率、内存使用、连接数等统计信息。 -
INFO memory
:检查内存分配情况。 -
INFO replication
:监控主从复制状态。
-
-
MONITOR命令:实时查看所有执行的命令。
-
SLOWLOG命令:查看慢查询日志。
-
外部工具:如
redis-cli --stat
、Prometheus + Grafana等。
Redis的Pipeline有什么作用?
Pipeline是一种批量处理机制,允许客户端一次性发送多个命令并接收响应,减少网络往返时间。 优点:
-
提高吞吐量,降低延迟。
-
适合批量操作场景,如批量写入或读取。
示例:
MULTI SET key1 value1 SET key2 value2 EXEC
如何解决Redis的大Key问题?
大Key会占用大量内存,并可能导致删除时阻塞主线程。解决方案包括:
-
分片存储:将大Key拆分为多个小Key存储。
-
渐进式删除:使用
UNLINK
命令异步释放内存。 -
定期清理:通过监控工具(如
SCAN
命令)查找并删除大Key。 -
限制大小:设置最大Key大小,超出时拒绝写入。
什么是热Key问题?如何解决?
热Key问题是指某些Key被高频访问,导致单个节点负载过高甚至崩溃。 解决方法:
-
数据分片:将热点数据分散到多个节点。
-
本地缓存:在应用层缓存热Key,减少对Redis的请求。
-
随机前缀:为热Key添加随机前缀,分散访问压力。
-
分布式锁限流:控制对热Key的访问频率。
Redis的慢查询日志如何配置?
在redis.conf
中配置慢查询日志:
slowlog-log-slower-than 10000 # 记录执行时间超过10ms的命令(单位:微秒) slowlog-max-len 128 # 慢查询日志的最大条数
通过SLOWLOG GET
命令查看慢查询记录。
如何优化Redis的内存占用?
-
选择合适的数据结构:
-
使用
Hash
代替多个String
。 -
使用
Bitmap
或HyperLogLog
节省内存。
-
-
启用LRU淘汰策略:自动清理不常用的Key。
-
压缩数据:对字符串值进行编码压缩。
-
避免过期Key堆积:合理设置过期时间,定期清理无用数据。
-
调整持久化频率:减少RDB/AOF文件生成频率以降低内存开销。
Redis的过期策略对性能有什么影响?
Redis的过期策略包括惰性删除和定期删除:
-
惰性删除:只有当访问某个Key时才检查是否已过期,可能会导致过期Key堆积。
-
定期删除:后台定时扫描部分Key空间,删除已过期的Key,但可能增加CPU负担。
建议:
-
对于写密集型场景,适当调高定期删除频率。
-
避免设置过多短生命周期的Key。
Redis的SCAN命令与KEYS命令有何区别?
特性 | SCAN | KEYS |
---|---|---|
执行方式 | 渐进式扫描,分批返回结果 | 一次性遍历所有Key |
性能影响 | 不会阻塞主线程 | 可能导致主线程阻塞 |
使用场景 | 安全地遍历大量Key | 快速查找少量Key |
推荐使用SCAN
命令替代KEYS
,尤其是在生产环境中。
如何避免Redis的缓存穿透?
缓存穿透是指查询一个不存在的Key,导致每次请求都直接访问数据库。解决方案:
-
布隆过滤器:在应用层使用布隆过滤器预判Key是否存在。
-
空值缓存:将不存在的Key缓存为特殊值(如
NULL
),设置较短的过期时间。 -
接口校验:在请求到达缓存之前,验证参数合法性。
示例(空值缓存):
GET non_existent_key // 如果返回空,设置空值缓存 SET non_existent_key NULL EX 60
五、缓存问题与解决方案
什么是缓存穿透?如何解决?
缓存穿透是指查询一个不存在的Key,导致每次请求都直接访问数据库。 解决方法:
-
布隆过滤器:在应用层使用布隆过滤器预判Key是否存在。
-
空值缓存:将不存在的Key缓存为特殊值(如
NULL
),并设置较短的过期时间。 -
接口校验:在请求到达缓存之前,验证参数合法性。
示例(空值缓存):
GET non_existent_key // 如果返回空,设置空值缓存 SET non_existent_key NULL EX 60
什么是缓存雪崩?如何预防?
缓存雪崩是指大量缓存同时失效,导致短时间内数据库压力骤增。 预防方法:
-
设置随机过期时间:为不同Key设置不同的过期时间,避免集中失效。
-
预热缓存:在高并发场景下,提前加载热点数据到缓存中。
-
限流降级:对数据库请求进行限流或熔断,防止系统崩溃。
-
持久化缓存:使用Redis的RDB/AOF机制保存缓存数据。
什么是缓存击穿?如何解决?
缓存击穿是指某个热点Key突然失效,大量请求瞬间涌向数据库。 解决方法:
-
延迟双删策略:在删除Key时,设置一个短暂的过期时间,防止立即失效。 示例:
DEL key SET key value EX 10 // 设置10秒过期时间
-
互斥锁:通过分布式锁确保同一时间只有一个请求可以更新缓存。
-
永不过期:对于极热点数据,设置永不过期,并定期异步更新缓存。
如何保证缓存与数据库的一致性?
-
写入时更新缓存:
-
写数据库成功后,更新或删除缓存(Write Through模式)。
-
-
读取时修复缓存:
-
如果缓存未命中,则从数据库读取数据并更新缓存(Read Through模式)。
-
-
双写机制:
-
同时写入数据库和缓存,但需注意顺序一致性。
-
-
消息队列异步更新:
-
使用消息队列解耦缓存和数据库的更新操作。
-
延迟双删策略是什么?
延迟双删策略是一种解决缓存击穿的方法:
-
删除Key时,不立即彻底清除,而是设置一个短暂的过期时间(如几秒)。
-
在这段时间内,允许部分请求命中缓存,避免所有请求直接打到数据库。
示例:
DEL key SET key value EX 5 // 设置5秒过期时间
如何用Redis实现分布式Session?
-
存储Session数据:
-
将用户的Session信息存储在Redis中,Key为用户ID,Value为Session内容。
-
设置合理的过期时间以清理无效Session。
-
-
跨服务共享:
-
所有服务通过Redis访问Session数据,实现分布式环境下的Session共享。
-
示例:
SET session:1001 "{\"user_id\":1001, \"username\":\"Alice\"}" EX 3600 GET session:1001
Redis如何实现消息队列?(如List、Stream)
-
基于List:
-
使用
LPUSH
将任务推入队列,RPOP
或BLPOP
消费任务。 -
示例:
LPUSH queue task1 BRPOP queue
-
-
基于Stream:
-
使用
XADD
添加消息,XREAD
或XREADGROUP
消费消息。 -
支持消费者组和消息确认机制。
-
示例:
XADD stream * field1 value1 XREAD STREAMS stream ID
-
如何用Redis实现排行榜功能?
使用Sorted Set
实现排行榜功能:
-
Key为排行榜名称,Score为排名分数,Member为用户ID。
-
添加或更新排名:
ZADD leaderboard 100 user1 ZADD leaderboard 200 user2
-
查询排名:
ZRANGE leaderboard 0 10 WITHSCORES // 获取前10名 ZREVRANK leaderboard user1 // 获取用户1的排名
Redis如何实现限流(Rate Limiting)?
-
令牌桶算法:
-
使用
INCR
命令增加令牌计数,结合EXPIRE
设置时间窗口。 -
示例:
INCRBY user:1001:requests 1 EXPIRE user:1001:requests 60 // 60秒内有效 GET user:1001:requests
-
-
漏桶算法:
-
使用
LPUSH
和LTRIM
模拟漏桶行为,限制请求速率。
-
如何用Redis实现布隆过滤器?
Redis本身不直接支持布隆过滤器,但可以通过Bitmap实现类似功能:
-
使用Bitmap记录Hash函数的结果。
-
检查多个Bit位是否全为1来判断Key是否存在。
示例:
# 添加元素 SETBIT bloomfilter (hash1(key)) 1 SETBIT bloomfilter (hash2(key)) 1 # 查询元素 GETBIT bloomfilter (hash1(key)) AND GETBIT bloomfilter (hash2(key))
布隆过滤器的特点:
-
空间效率高,适合海量数据。
-
可能存在误判率,需根据需求调整Hash函数数量和Bitmap大小。
八、Java与Redis集成
Java常用的Redis客户端有哪些?(如Jedis、Lettuce)
常用的Redis Java客户端包括:
-
Jedis:轻量级、阻塞式客户端,适合单线程或少量并发场景。
-
Lettuce:基于Netty的非阻塞客户端,支持多线程和高并发。
-
Redisson:功能强大的Redis客户端,内置分布式锁、队列等高级特性。
-
Spring Data Redis:Spring框架提供的Redis抽象层,封装了Jedis或Lettuce。
Jedis和Lettuce的区别是什么?
特性 | Jedis | Lettuce |
---|---|---|
并发模型 | 阻塞式(每个连接独占线程) | 非阻塞式(基于Netty,共享连接池) |
线程安全性 | 不线程安全(需手动管理连接池) | 线程安全 |
连接复用 | 每次请求需要从连接池获取新连接 | 单个连接可被多个线程复用 |
性能 | 较低 | 更高 |
集群支持 | 需要额外配置Cluster模式 | 原生支持Redis Cluster |
Spring Data Redis如何配置连接池?
使用Jedis
或Lettuce
作为连接池时,可以通过以下方式配置:
-
Jedis连接池配置:
@Bean public JedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("localhost", 6379); JedisClientConfiguration jedisConfig = JedisClientConfiguration.builder() .usePooling().poolConfig(new GenericObjectPoolConfig()) .build(); return new JedisConnectionFactory(config, jedisConfig); }
-
Lettuce连接池配置:
@Bean public LettuceConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("localhost", 6379); LettuceClientConfiguration lettuceConfig = LettuceClientConfiguration.builder() .commandTimeout(Duration.ofSeconds(5)) .build(); return new LettuceConnectionFactory(config, lettuceConfig); }
如何用Spring Boot整合Redis?
-
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置
application.yml
:spring: redis: host: localhost port: 6379 password: timeout: 5000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0
-
使用
RedisTemplate
或StringRedisTemplate
操作Redis。
RedisTemplate和StringRedisTemplate的区别?
特性 | RedisTemplate | StringRedisTemplate |
---|---|---|
Key/Value类型 | 支持任意类型(需序列化) | 仅支持字符串类型 |
序列化方式 | 默认使用JdkSerializationRedisSerializer | 使用StringRedisSerializer |
使用场景 | 复杂对象存储 | 简单字符串存储 |
示例:
// RedisTemplate redisTemplate.opsForValue().set("key", myObject); // StringRedisTemplate stringRedisTemplate.opsForValue().set("key", "value");
如何用Redis实现Spring Cache?
-
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
-
配置
application.yml
:spring: cache: type: redis
-
在方法上添加
@Cacheable
注解:@Cacheable(value = "users", key = "#id") public User getUserById(String id) { // 查询数据库逻辑 }
Redis如何实现分布式锁(RedLock算法)?
RedLock算法是一种改进的分布式锁实现,适用于跨多个Redis节点的环境。步骤如下:
-
获取当前时间戳。
-
尝试在多个Redis实例上加锁,超时时间为短时间(如10ms)。
-
如果超过半数实例加锁成功,则认为锁获取成功。
-
计算锁的有效期为最小成功锁的剩余时间减去一定安全缓冲时间。
-
如果失败,则释放所有已加的锁。
Redis的分布式锁在Java中如何实现?
使用Redisson
库可以轻松实现分布式锁:
import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.config.Config; public class DistributedLockExample { public static void main(String[] args) throws InterruptedException { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("myLock"); lock.lock(); // 加锁 try { // 执行业务逻辑 } finally { lock.unlock(); // 解锁 } } }
如何解决Redis连接池资源泄漏问题?
-
确保连接正确关闭:
-
使用
try-with-resources
或finally
块确保连接归还到池中。 -
示例:
try (Jedis jedis = jedisPool.getResource()) { jedis.set("key", "value"); }
-
-
监控连接池状态:
-
定期检查连接池的活跃连接数和空闲连接数。
-
如果发现异常,及时调整连接池配置。
-
-
设置连接超时:
-
配置合理的
timeout
值,避免连接长时间占用。
-
如何监控Java应用中的Redis性能?
-
使用Spring Actuator:
-
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
访问
/actuator/metrics/redis
查看Redis指标。
-
-
自定义监控:
-
使用
INFO
命令获取Redis运行状态。 -
结合Prometheus + Grafana进行可视化监控。
-
-
第三方工具:
-
使用Redis自带的
redis-cli --stat
实时监控。 -
使用专业监控工具(如RedisInsight)。
-
九、场景设计题
如何设计一个短链生成服务?
-
短链生成:
-
使用
UUID
或Base64
编码生成唯一短码。 -
确保短码不重复(可结合Redis的
SETNX
命令)。
-
-
存储映射关系:
-
使用Redis的
Hash
类型存储长链接和短链接的映射关系。HSET url_map short_code long_url
-
-
访问解析:
-
用户访问短链接时,通过
HGET
获取对应的长链接并重定向。
-
如何实现用户签到功能?
-
使用Bitmap:
-
将用户的签到记录存储在Bitmap中,每个位表示一天。
SETBIT user:1001:sign 1 1 // 表示用户1001第2天签到
-
-
查询连续签到天数:
-
使用
BITCOUNT
统计签到总天数。 -
使用
GETRANGE
检查连续性。
-
-
奖励机制:
-
根据连续签到天数发放奖励。
-
如何统计网站的UV(独立访客)?
-
使用HyperLogLog:
-
HyperLogLog适合估算唯一值数量(如IP地址或用户ID)。
PFADD uv_set user_id_123 PFCOUNT uv_set
-
-
按时间分组:
-
按天、小时等粒度存储UV数据,便于历史统计。
-
如何实现秒杀系统的库存扣减?
-
使用分布式锁:
-
在扣减库存时加锁,避免并发问题。
RLock lock = redisson.getLock("product:1001:lock"); lock.lock(); try { if (inventory > 0) { inventory--; } } finally { lock.unlock(); }
-
-
使用原子操作:
-
使用
DECR
命令直接扣减库存。DECR product:1001:inventory
-
如何设计一个实时排行榜?
-
使用Sorted Set:
-
Key为排行榜名称,Score为排名分数,Member为用户ID。
ZADD leaderboard 100 user1 ZADD leaderboard 200 user2
-
-
更新分数:
-
使用
ZINCRBY
动态更新用户分数。
-
-
查询排名:
-
获取前N名:
ZRANGE leaderboard 0 N WITHSCORES
-
查询某个用户排名:
ZREVRANK leaderboard user1
-
如何用Redis实现延迟队列?
-
基于ZSet:
-
使用Score表示任务的执行时间。
ZADD delay_queue <timestamp> task_id
-
-
轮询任务:
-
定期检查当前时间之前的任务并执行。
ZRANGEBYSCORE delay_queue 0 now ZREM delay_queue task_id
-
如何实现微博的关注/粉丝列表?
-
关注列表:
-
使用
Set
存储用户关注的人。SADD follow:1001 1002 1003
-
-
粉丝列表:
-
使用
Set
存储用户的粉丝。SADD follower:1002 1001
-
-
互相关注:
-
使用
SINTER
查找共同关注的人。SINTER follow:1001 follow:1002
-
如何缓存商品详情页并解决并发更新?
-
缓存策略:
-
使用
String
类型存储商品详情页的HTML内容。SETEX product:1001:detail "<html>..." 3600
-
-
并发更新:
-
使用
SETNX
确保只有一个线程更新缓存。 -
更新完成后设置过期时间。
-
如何实现分布式环境下的全局ID生成?
-
基于时间戳:
-
使用
Unix时间戳 + 机器ID + 序列号
生成唯一ID。
-
-
使用Redis自增:
-
使用
INCR
命令生成递增ID。INCR global_id
-
-
Snowflake算法:
-
分布式环境下推荐使用Snowflake算法,包含时间戳、机器ID和序列号。
-
如何用Redis实现自动补全(搜索提示)?
-
基于Prefix Tree(Trie):
-
使用
Sorted Set
存储以不同前缀开头的词。ZADD autocomplete "apple" 0 ZADD autocomplete "app" 0
-
-
模糊匹配:
-
使用
ZRANGEBYLEX
查询以特定前缀开头的词。ZRANGEBYLEX autocomplete [apple [appz
-
-
优化性能:
-
对常用前缀进行预加载,减少查询次数。
-
十、故障排查与运维
Redis内存占用过高如何排查?
-
检查大Key:
-
使用
SCAN
命令查找占用内存较大的Key。SCAN 0 MATCH * COUNT 1000
-
-
分析内存分布:
-
使用
MEMORY USAGE key
查看单个Key的内存占用。 -
使用
INFO memory
获取整体内存使用情况。
-
-
检查淘汰策略:
-
确保启用了合适的淘汰策略(如
LRU
)。
-
-
清理无用数据:
-
删除过期或无效的Key。
-
Redis的CPU使用率过高可能是什么原因?
-
慢查询:
-
使用
SLOWLOG GET
查看耗时较长的命令。
-
-
频繁的RDB/AOF操作:
-
持久化操作可能导致CPU占用升高。
-
-
复杂命令:
-
如
KEYS
、SORT
等全局扫描命令会阻塞主线程。
-
-
主从复制:
-
全量同步时,主节点生成RDB文件会消耗大量CPU。
-
解决方案:
-
优化慢查询。
-
调整持久化频率。
-
避免使用耗时命令。
主从复制延迟过大如何解决?
-
网络问题:
-
检查主从节点间的网络延迟。
-
-
主节点压力过大:
-
分担主节点负载,减少写入频率。
-
-
从节点性能不足:
-
增加从节点资源(CPU、内存)。
-
-
调整同步策略:
-
使用
replicaof no one
暂时断开从节点,手动同步后重新连接。
-
AOF文件损坏如何修复?
-
手动修复:
-
找到损坏的日志部分,删除或修正命令。
-
-
使用
redis-check-aof
工具:-
Redis自带工具可以检测和修复AOF文件。
redis-check-aof --fix aof_file
-
-
重置AOF文件:
-
如果无法修复,可以通过全量快照重新生成AOF文件。
BGREWRITEAOF
-
如何安全地重启Redis实例?
-
备份数据:
-
确保启用了RDB持久化,并生成最新快照。
-
-
停止写操作:
-
在业务低峰期暂停写请求。
-
-
优雅关闭:
-
使用
SHUTDOWN
命令确保所有数据已保存。
-
-
启动实例:
-
启动Redis并验证数据完整性。
-
Redis的CLUSTER NODES命令返回什么信息?
CLUSTER NODES
命令返回当前Redis Cluster的拓扑信息,包括:
-
节点ID:每个节点的唯一标识。
-
地址:节点的IP和端口。
-
状态:节点是否在线(
connected
或disconnected
)。 -
槽分配:节点负责的哈希槽范围。
-
角色:节点是主节点还是从节点。
-
其他信息:如最后通信时间、配置版本等。
示例输出:
c8f3d9...fe7e2a 192.168.1.1:6379@16379 master - 0 1626516339000 1 connected 0-5460
如何备份和恢复Redis数据?
-
备份:
-
使用
BGSAVE
生成RDB快照文件。 -
将RDB文件拷贝到安全位置。
-
-
恢复:
-
将备份的RDB文件放到Redis数据目录。
-
启动Redis时自动加载RDB文件。
-
Redis的日志文件如何分析?
-
日志级别:
-
Redis支持
debug
、verbose
、notice
、warning
四种日志级别。 -
修改
redis.conf
中的loglevel
参数。
-
-
常见日志内容:
-
客户端连接/断开记录。
-
错误信息(如OOM、持久化失败)。
-
慢查询记录。
-
-
分析工具:
-
使用
grep
或awk
提取关键信息。 -
结合ELK(Elasticsearch + Logstash + Kibana)进行集中化分析。
-
如何升级Redis版本?
-
备份数据:
-
确保启用了RDB持久化并生成最新快照。
-
-
停止服务:
-
使用
SHUTDOWN
命令优雅关闭Redis。
-
-
安装新版本:
-
替换旧版Redis二进制文件。
-
-
验证兼容性:
-
检查新版本的配置文件是否有变化。
-
-
启动服务:
-
启动Redis并验证数据和功能正常。
-
如何实现Redis的监控报警?
-
使用内置命令:
-
定期执行
INFO
命令监控指标(如内存使用、连接数、命中率)。
-
-
集成监控工具:
-
使用Prometheus + Grafana采集和展示Redis指标。
-
配置Alertmanager设置报警规则。
-
-
第三方工具:
-
使用RedisInsight、RedisLive等可视化工具。
-
-
自定义脚本:
-
编写Shell或Python脚本定期检查Redis状态,并通过邮件/SMS发送报警。
-