Redis 相关知识点整理
·
一、Redis 基础认知
1. 核心定位
Redis 是基于内存的键值型 NoSQL 数据库,Java 开发中核心用于:缓存(减轻 MySQL 压力)、分布式锁、计数器、排行榜、限流等场景,是 Java 后端高性能架构的核心组件。
2. 为什么快(Java 面试必答)
Redis 高性能的核心原因,需结合 Java 开发视角理解:
- 纯内存操作:数据存储在内存,读写延迟远低于磁盘(Java 中需注意 Redis 内存占用,避免 OOM);
- 单线程模型(核心命令):Redis 核心命令执行为单线程,避免 Java 多线程的上下文切换、锁竞争开销;
- 补充:Redis 6.0 + 引入多线程 IO(仅处理网络读写),命令执行仍为单线程,Java 客户端无需适配多线程逻辑;
- IO 多路复用:基于 epoll 实现,单线程处理数千个网络连接,对应 Java NIO 的 Reactor 模式,避免阻塞 IO 的性能瓶颈;
- 数据结构优化:底层编码(如 ziplist、跳表)适配高频操作,Java 开发无需关心底层,但需知道不同数据结构的性能特性。
3. 开发选型:Redis vs Memcached
| 维度 | Redis | Memcached | Java 开发选型建议 |
|---|---|---|---|
| 数据结构 | 支持 String/Hash/List/ZSet 等 | 仅支持 String | Java 复杂业务(如排行榜、对象缓存)选 Redis |
| 持久化 | 支持 RDB/AOF | 不支持 | Java 项目需数据落地(如缓存重启不丢失)选 Redis |
| 集群方案 | 原生 Cluster / 哨兵,Java 客户端(Redisson/Jedis)原生支持 | 需第三方集群方案,Java 客户端支持弱 | 分布式 Java 项目选 Redis |
| 客户端生态 | Jedis(基础)、Redisson(高级,封装分布式锁 / 集合)成熟 | Java 客户端生态薄弱 | 优先 Redis |
二、核心数据结构(Java 开发高频场景)
| 数据结构 | 核心特点 | Java 开发典型场景 | 底层编码(面试深问) |
|---|---|---|---|
| String | 二进制安全,支持原子增减(incr/decr) | 1. 分布式 ID(incr 生成);2. 缓存简单对象(JSON 序列化);3. 计数器(点赞数 / 阅读量) | 短字符串→embstr,长字符串→raw,整数→int,Java 序列化对象需控制长度避免编码切换 |
| Hash | 键值对集合,适合存储对象(减少 Key 数量) | 缓存用户 / 商品对象(如 user:1001 的 name/age/phone),避免 String 序列化 / 反序列化开销 | 少量字段→ziplist,大量字段→hashtable,Java 中需控制字段数量避免性能下降 |
| List | 双向链表,支持阻塞读写 | 1. 简单消息队列(异步通知);2. 最新消息列表 | 少量元素→ziplist,大量元素→linkedlist,Java 中阻塞读取(blpop)需处理超时 |
| Set | 无序唯一,支持交集 / 并集 / 差集 | 1. 共同好友;2. 抽奖去重;3. 数据去重 | 整数元素→intset,其他→hashtable,Java 中集合运算(如 sinter)需注意性能 |
| ZSet | 有序唯一(按 score 排序),支持范围查询 | 1. 排行榜(游戏积分 / 商品热度);2. 延迟队列 | 少量元素→ziplist,大量元素→跳表 + hashtable,Java 中 zrevrange 是高频操作 |
特殊数据结构(拓展场景)
- Bitmap:按位存储,极致省空间,Java 中用于用户签到、在线状态统计;
- HyperLogLog:极小空间统计海量数据基数,Java 中用于 UV 统计(容忍 0.81% 误差);
- Geospatial:存储经纬度,Java 中用于「附近的人」等 LBS 场景;
- Stream:持久化消息队列,Java 中替代 List 解决消息丢失问题,支持消费组(类似 Kafka)。
三、Redis 核心特性
1. 过期策略与内存淘汰
(1)过期策略
Redis 采用「惰性删除 + 定期删除」:
- 惰性删除:访问 Key 时检查过期,需注意:过期 Key 若长期不访问,会导致内存泄漏;
- 定期删除:每 100ms 随机扫描部分 Key,Java 开发无需干预,但需合理设置过期时间。
(2)内存淘汰策略(面试必答)
| 策略 | Java 应用场景 |
|---|---|
| volatile-lru | 业务缓存(仅淘汰带过期时间的 Key),Java 项目中缓存用户 / 商品数据优先选 |
| allkeys-lru | 纯缓存场景(所有 Key 都是缓存),Java 中如缓存热点数据 |
| noeviction | 不淘汰,拒绝写操作,Java 中金融类项目(不允许数据丢失)可选 |
2. 事务
Redis 事务特性:弱原子性、无回滚、无隔离级别,Java 开发中几乎不直接使用 Redis 事务,替代方案:
- 简单原子操作:使用 Redis 单命令(如 incr、hincrby,天然原子);
- 复杂原子操作:使用 Lua 脚本(Jedis/Redisson 支持),一次性执行多个命令;
- 分布式事务:Java 中用 Seata + Redis,或 Redisson 的分布式事务组件。
3. 管道(Pipeline)
用于批量操作(如批量插入数据),核心价值:减少网络往返次数(RTT),Jedis/Redisson 均支持 Pipeline,需注意:
- Pipeline 无原子性,Java 中需处理部分命令失败的情况;
- 批量操作时,Pipeline 性能远高于循环单命令。
四、Redis 持久化(Java 开发核心配置)
1. RDB vs AOF(Java 面试核心对比)
| 维度 | RDB(快照) | AOF(追加日志) | Java 项目选型建议 |
|---|---|---|---|
| 原理 | 定时将内存数据快照写入磁盘 | 记录所有写命令,重启重放 | - |
| 数据安全性 | 低(两次快照间数据丢失) | 高(最多丢 1 秒) | Java 核心业务(如订单缓存)选 AOF |
| 恢复速度 | 快 | 慢 | Java 项目重启时,RDB 适合快速恢复 |
| 性能开销 | 低(fork 子进程) | 高(写日志) | Java 高并发场景需平衡性能与安全 |
2. Java 项目持久化配置原则
- 生产环境:开启「RDB + AOF(混合持久化)」,兼顾数据安全与恢复速度;
- AOF 同步策略:选 everysec(平衡安全与性能),Java 金融项目可考虑 always;
- 混合持久化:Redis 4.0 + 支持,Java 项目开启后,重启时先加载 RDB,再重放 AOF 增量日志。
五、Redis 集群(Java 开发核心方案)
1. 主从复制
- 原理:主库写、从库读,Java 客户端可配置读写分离(JedisPool 分主从池);
- 关注点:主库宕机后需手动切换,生产环境需配合哨兵。
2. 哨兵(Sentinel)
- 核心功能:监控主从、自动故障转移,Java 客户端(Jedis/Redisson)原生支持哨兵模式,无需额外开发;
- 关注点:配置哨兵地址列表,客户端自动感知主库切换,无需重启应用。
3. Redis Cluster(分片集群)
- 原理:16384 个哈希槽,数据按槽分布,Java 客户端需支持槽位计算;
- Java 适配:
- Jedis:使用 JedisCluster,自动处理槽位重定向;
- Redisson:原生支持 Cluster,封装更友好;
- 注意:Java 中避免跨槽的批量操作(如 mset 操作不同槽的 Key),会降低性能。
六、缓存核心问题
1. 三大问题核心对比( 面试高频)
| 问题 | 定义 | Java 核心解决方案 |
|---|---|---|
| 缓存穿透 | 请求不存在的 Key,打穿到 DB | 1. 缓存空值(设置短过期);2. 布隆过滤器(Java 中用 Guava BloomFilter 或 Redis 布隆过滤器模块) |
| 缓存击穿 | 热点 Key 过期,大量请求打 DB | 1. 热点 Key 永不过期;2. 分布式锁(仅一个请求查 DB) |
| 缓存雪崩 | 大量 Key 同时过期 / Redis 宕机,所有请求打 DB | 1. 过期时间加随机值;2. Redis 集群高可用;3. 多级缓存(本地缓存 Caffeine + Redis) |
2. 缓存与数据库一致性(Java 核心方案)
- 核心原则:禁止「先更缓存,再更 DB」(并发必出脏数据);
- 基础方案:先更 DB,再删缓存(Java 中用 RedisTemplate.delete (),而非更新缓存);
- 进阶方案:延迟双删(删缓存→更 DB→延迟 1-2 秒再删缓存),Java 中用线程池 / 定时任务实现延迟;
- 强一致性场景:Java 中用分布式事务(Seata)或消息队列(RocketMQ)异步更新缓存。
七、Redis 分布式锁(Java 开发核心)
1. 基础实现(RedisTemplate)
核心逻辑:原子加锁(set nx ex)+ 原子释放(Lua 脚本),Java 中需注意:
- 加锁:
redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS),requestId 避免误删; - 释放:Lua 脚本(一次性执行,原子性),避免「查值 + 删除」非原子导致的误删。
2. 进阶实现(Redisson)
Java 中优先使用 Redisson,封装了分布式锁的所有核心能力:
- 可重入锁:RedissonLock 支持重入,适配 Java 多线程重入场景;
- 锁续期:Watch Dog 机制自动续期,解决业务执行时间超过锁过期时间的问题;
- 公平锁:避免线程饥饿,适配 Java 公平锁场景;
- RedLock:多节点加锁,避免 Redis 单点故障导致锁失效,Java 中用 RedissonRedLock 实现。
3. Java 开发避坑点
- 必须设置过期时间,避免死锁;
- 释放锁必须校验 requestId,避免误删其他线程的锁;
- 高并发场景优先用 Redisson,而非手动实现(避免踩坑)。
八、面试高频题(核心答案)
1. Redis 单线程为什么能支撑高并发?
答:Redis 核心命令执行是单线程,但结合以下特性支撑高并发:
- 纯内存操作,无磁盘 IO 瓶颈;
- IO 多路复用处理大量网络连接,避免阻塞 IO;
- 单线程避免线程切换和锁竞争开销;
- 补充:Redis 6.0 + 多线程仅处理网络读写,命令执行仍为单线程,Java 客户端无需适配多线程逻辑。
2. 如何解决缓存穿透 / 击穿 / 雪崩?
答:
- 穿透:缓存空值(短过期)+ 布隆过滤器(Guava/Redis)拦截无效请求;
- 击穿:热点 Key 永不过期 + 分布式锁(Redisson)限制查 DB 的请求数;
- 雪崩:过期时间加随机值 + Redis 集群高可用 + 多级缓存(Caffeine 本地缓存 + Redis)。
3. 如何实现 Redis 分布式锁?
答:
- 基础版:用 RedisTemplate 执行 set nx ex 原子加锁,Lua 脚本原子释放;
- 进阶版:使用 Redisson,支持可重入、锁续期、公平锁、RedLock;
- 注意事项:加锁设过期时间、释放锁校验 requestId、高可用场景用 RedLock。
4. 如何保证缓存和数据库一致性?
答:核心原则是「先更数据库,再删缓存」;
- 基础场景:更新 DB 后调用 RedisTemplate.delete () 删除缓存;
- 并发场景:延迟双删(删缓存→更 DB→延迟 1-2 秒再删);
- 强一致性场景:用 Seata 分布式事务或 RocketMQ 异步更新缓存。
总结
- Java 开发中 Redis 核心应用场景:缓存、分布式锁、计数器、排行榜,需结合数据结构特性选型;
- 缓存三大问题的核心解决思路:拦截无效请求、避免批量过期、保证 Redis 高可用;
- 分布式锁优先用 Redisson,遵循「原子加锁、原子释放、设置过期时间」三大原则;
- 缓存与 DB 一致性核心:先更 DB 再删缓存,避免先更缓存的脏数据问题。
更多推荐
所有评论(0)