Redis场景的部署模式
| 模式 | 一句话理解 | 常见程度 |
|---|---|---|
| 单机 | 一台 Redis,最简单 | ⭐⭐⭐ |
| 哨兵 | 一主一备 + 自动切换 | ⭐⭐⭐⭐ |
| 集群 | 多台 Redis 分摊数据 | ⭐⭐⭐⭐ |
单机模式
单机模式只有一台 Redis 服务器。
生活类比 🏪
一家小卖部:
- 只有一个老板
- 老板在,一切正常
- 老板病了 = 关门
优点
- 部署简单
- 成本低
- 学习 / 测试 / 小项目常用
致命缺点(重点)
❌ Redis 挂了,所有服务缓存 / 锁 / 会话 全部不可用
哨兵模式
一主 + 多个备份 + 哨兵盯着。通常是 1 主 + N 从 + Sentinel 负责故障切换
-
Sentinel = 哨兵,Redis Sentinel = 专门“盯着 Redis 的看门人”
-
它不存数据, 它只干 3 件事:
- 看 Redis 活没活着
- 主 Redis 挂了,选一个从 Redis 顶上
- 告诉你的应用: 👉「主 Redis 换人了!」
生活类比 🏪🏪
一家店:
- 老板(主)
- 副老板(从)
- 店长(哨兵)天天盯着
如果老板倒了: 👉 店长立刻让副老板上位
哨兵干了什么?
- 监控 Redis 是否活着
- 主 Redis 挂了
- 自动把从 Redis 升级为主
- 通知客户端切换
优点
- 高可用(不怕一台挂)
- 业务基本无感知
- 成本比集群低
缺点
- 数据还是在一台主 Redis 上
- 扛不住特别大的数据量
适合谁?
👉 90% 的中小公司生产环境 👉 你这种“单体 + K8s 多实例”非常常见
集群模式
多台 Redis,一起存数据。每个分片节点通常也会有从节点做副本(比如 A 主 + A 从)
你的服务 → Redis A(存一部分) → Redis B(存一部分) → Redis C(存一部分)
核心特点(记住这 3 个)
-
数据分片存储
-
分片存储 = 把数据“拆开”,分片就是对数据(更准确说:对 key 空间)做拆分,分到多台 Redis 里存
-
没有分片时,所有的数据都存储在一台redis里面,请求多了,一个redis服务器扛不住,数据存多了,内存也装不了那么多,所以就搞多台redis服务器,
- Redis A:存一部分 key Redis B:存一部分 key Redis C:存一部分 key
-
客户端怎么知道去哪一台找,所有 key 映射到 16384 个槽,每个 Redis 负责一部分槽,客户端(Redisson / Lettuce)会自动算 key 属于哪个槽,把请求发到对的 Redis。
-
-
每台 Redis 只存一部分
-
单机挂了,还有副本
-
“副本”就是我们常说的:主从复制(replication)。
-
主节点(Master)负责写,从节点(Replica/Slave)复制主节点的数据,作为备份。
-
从节点复制流程, 2 段:
A. 第一次同步(全量)
- 从节点刚加入时,数据是空的
- 主节点把“当前全部数据”打包一份给它 (这就叫全量同步)
B. 后续同步(增量)
- 之后主节点每次写入
- 都会把“写操作日志/命令流”持续发给从节点 (这就叫增量同步)
-
读扩展:读请求可以分给从节点(看业务是否允许读到稍微旧的数据)
-
以上类似于hdfs的Active NameNode和Observer NameNode
-
优点
- 数据量大
- QPS 高
- 真·高可用 + 高扩展
缺点
- 架构复杂
- 运维成本高
- 不适合小项目
适合谁?
- 电商
- 大促系统
- 大数据量缓存
Java Redis 客户端
基础 Redis 客户端(“只管存取”)
Jedis
最早、API 直观
现在基本不推荐新项目
Lettuce
Spring Boot 默认,基于 Netty,线程安全,支持单机 / 哨兵 / 集群
redisTemplate.opsForValue().get("k");
👉 特点:
- 稳定
- 性能好
- 但只负责“操作 Redis”
❌ 不负责:
- 分布式锁
- 限流
- 并发控制
RedisTemplate
Spring 封装
本质是 Lettuce/Jedis 的再封装
几乎所有 Spring 项目都有
缓存读写首选
高级客户端(并发 & 分布式)
-
在 Redis 之上
-
提供:
- 分布式锁
- 限流
- 信号量
- 延迟队列
- 分布式集合
👉 写法像 Java 并发包
框架级(“你甚至不知道 Redis 在哪”)
1️⃣ Spring Cache
@Cacheable
@CacheEvict
👉 特点:
- 极简单
- 底层可以是 Redis
- 适合:纯缓存
❌ 不适合:
- 复杂并发控制
- 锁
2️⃣ Spring Session(基于 Redis)
用 Redis 存 Session
解决多实例登录态问题
总结
简单来说,Java 操作 Redis 的客户端发展史,就像是从 “原始手动工具” 进化到 “全自动智能工厂” 的过程。
1. Jedis
这一层直接和 Redis 服务器打交道,负责最基础的“存”和“取”。
- Jedis (老牌大哥): 就像一把老式扳手。好用、直观,但它是线程不安全的(多个人不能同时用一把扳手),需要配合“工具箱”(连接池)使用。现在新项目很少用了。
2.Lettuce
-
Lettuce (现代标配): 就像一把电动螺丝刀。它是基于 Netty 开发的,性能极强且线程安全(多个人可以同时用)。它是 Spring Boot 默认的选择。
Netty 是目前 Java 生态中最顶级的异步网络通信框架。 基于 Netty 开发,本质上是把 Redis 的通信从“老式的同步阻塞”升级为了“现代的异步非阻塞” 1. 极高的并发处理能力(非阻塞 I/O) 传统方式 (BIO): 像是一对一的服务员。如果要处理 1000 个请求,就得雇 1000 个服务员(线程)。线程多了,服务器内存会爆,CPU 切换线程也会累死。 Netty 方式 (NIO): 像是“旋转餐厅”。一个服务员(线程)可以同时看管几十个桌子。只有当某桌客人真正要点菜(数据准备好了)时,服务员才过去处理。 好处: 极少的线程就能处理海量的连接,内存占用低,吞吐量极大。 2. 真正实现“连接共享”(线程安全) 这是 Lettuce 区别于 Jedis 的最大痛点: Jedis: 不是线程安全的。多个线程同时用一个 Jedis 实例会把数据搞混。所以必须用 JedisPool(连接池),每次用都要借,用完还。 Netty/Lettuce: 因为是异步的,Netty 允许成百上千个线程同时使用同一个 Redis 连接。它在底层会把这些请求排好队发送给 Redis,再把结果准确回传给对应的线程。 好处: 节省了建立和销毁连接的开销,也不再需要庞大的连接池。 3. 异步与响应式编程支持 Netty 原生支持异步回调。 你可以发起一个 Redis 操作后直接去干别的,等 Redis 返回结果了再触发后续逻辑。 这使得 Lettuce 完美支持 Project Reactor 或 RxJava,能够配合 Spring WebFlux 实现全链路异步,非常适合现代的微服务架构。 4. 强大的断线重连与容错机制 网络波动是分布式系统的常态。 Netty 内置了非常成熟的重连逻辑、心跳检测和流量控制。 如果 Redis 挂了又重启,基于 Netty 的客户端能更稳健地自动恢复连接,而不需要开发者手动写复杂的重试代码。 一句话总结: 基于 Netty 让 Lettuce 变成了一个更轻量、更强壮、反应更快的客户端,特别是在高并发环境下,它的稳定性远超传统客户端。
3. RedisTemplate
Spring 的官方翻译官,RedisTemplate 底层确实默认使用 Lettuce,但 Redisson 并不是。
- 在 Spring Boot 2.x 及之后的版本中,
RedisTemplate就像是一台精密设备的控制面板,而 Lettuce 就是这台设备的发动机。 - 只要你引入了
spring-boot-starter-data-redis,Spring 会自动为你创建一个LettuceConnectionFactory。
4. Redisson
Redisson 并不使用 Lettuce。 它是直接基于 Netty 进行自研开发的。
- 独立地位: Redisson 是一个独立的 Redis 客户端。它和 Lettuce 是平级关系(都是基于 Netty 写的)。
为什么不封装 Lettuce?
- 目标不同: Lettuce 的目标是做一个完美的“Redis 指令翻译官”;而 Redisson 的目标是把 Redis 变成 Java 的“本地工具箱”。
- 深度定制: 为了实现诸如“分布式锁的自动续期(Watch Dog)”、“异步流处理”等复杂功能,Redisson 需要对底层通信协议有极强的控制力,直接基于 Netty 开发更利于其实现这些“魔法”。
5 幕后大佬:框架集成 (Spring Cache & Session)
Spring Cache 和 Spring Session 都是“抽象派”,它们本身并不负责存储,而是像两个大管家:你只需要下命令,它们去指挥底层的驱动(通常是 Lettuce)去干活。
Spring Boot 项目,默认的链路是这样的: Spring Cache / Session → RedisTemplate (或类似的连接工厂) → Lettuce → Redis 数据库。
这一层你甚至看不到 Redis 的影子,一切都在后台自动完成。
- Spring Cache: 只要在方法上加个注解
@Cacheable,Spring 就会自动帮你查询 Redis。如果 Redis 有,直接返回;如果没有,再查数据库并存入 Redis。 - Spring Session: 解决“分布式登录”问题。以前登录信息存服务器内存,换台服务器就得重新登录;现在统一存在 Redis 里,哪台服务器都能识别你。
Lettuce 是最底层的“发动机”。
RedisTemplate 是“汽车方向盘”,它默认连着 Lettuce 发动机,也可以连 Jedis 发动机。
Redisson 是一台自带发动机的“全自动收割机”。它不借用 Lettuce 的发动机,而是自己用 Netty 造了一个更猛的,专门用来干“锁、限流、分布式集合”这种苦力活。
缓存“三剑客”
在生产环境中,下面是导致数据库“猝死”的三种情况:
1. 缓存穿透 (Penetration) —— “透点攻击”
-
字面意思: 像一支利箭,直接穿透了防弹衣,射中了身体。
-
场景: 坏人故意查询不存在的数据(如
ID = -1)。- 缓存: 没有(因为数据不存在)。
- 数据库: 也没有,但必须得查一次。
-
结果: 请求每次都直接打在数据库上。
-
记忆口诀: “透”是指数据不存在,直接透过去打库。
-
防御: * 布隆过滤器(安检门:不在名单上的不准进)。
- 存空对象(哪怕没这人,也先在缓存记个“空”,下次别来问了)。
布隆过滤器
布隆过滤器(Bloom Filter) 就像是一个 “超级节省空间的黑名单安检员” 。
它的主要任务是挡住那些根本不存在的数据请求,从而完美解决缓存穿透问题。
想象你有一本巨大的名册,但你不想随身带着它。于是你在墙上画了 10 个树洞(编号 0-9)。
如果不分公式,只用一个动作: 如果你规定“张三”来了就在 1号树洞 放个石头。那万一“李四”算出来的结果也是 1号树洞 怎么办?你看到 1 号有石头,就会以为李四也来过。这就是严重的误判。
为什么要用三个公式? 这就像给每个人设置了三道不同的指纹/特征。
公式 A 看他的“鞋码”
公式 B 看他的“生日”
公式 C 看他的“身高”
只有这三个特征同时对上了,我们才觉得他可能来过。虽然“鞋码”可能撞,“生日”可能撞,但“鞋码+生日+身高”三个同时撞上的概率就小得多了!
2. “计算结果”代表什么意思?(代表位置)
计算结果(比如 1, 4, 8)其实就是**“坐标”或者“门牌号”**。
布隆过滤器其实就是一个全员 0/1 的排队阵列。
计算结果为 1: 意味着“去把 1 号位的灯点亮(变成 1)”。
计算结果为 4: 意味着“去把 4 号位的灯点亮(变成 1)”。
它不存“张三”这两个字,它只存“灯亮没亮”。
3. 全过程再模拟一次(小白视角)
你要守住数据库,不让坏人乱查。你手里只有一排 10 盏灯(初始全是灭的:0000000000)。
第一步:登记“张三”
你拿出三个公式算“张三”:
公式 A(看鞋码):算出他在 1 号灯。
公式 B(看生日):算出他在 4 号灯。
公式 C(看身高):算出他在 8 号灯。 于是你跑过去,把 1、4、8 号灯都按亮了。 灯的状态变成了:灭 亮 灭 灭 亮 灭 灭 灭 亮 灭 (0 1 0 0 1 0 0 0 1 0)
第二步:检查“李四”
有人问:“李四在不在?” 你拿出同样的公式算“李四”:
公式 A:算出他在 2 号灯。
公式 B:算出他在 4 号灯。
公式 C:算出他在 7 号灯。 你去检查灯:
看 2 号灯:灭的!
看 4 号灯:亮的(那是张三点亮的)。
看 7 号灯:灭的!
真相大白: 虽然 4 号灯亮着,但 2 号和 7 号灯都没亮!如果“李四”真的来登记过,他必须要把这三盏灯都点亮。 结论: 2 号灯和 7 号灯是灭的,说明“李四”百分之百没来过。你直接拒绝请求,不需要去翻数据库了。
4. 总结:为什么这么干?
为了省空间: 你不需要存“张三”这个名字,也不需要存“李四”这个名字,你只需要记几盏灯亮了。几亿个数据,也只需要一排灯。
为了快: 算三个公式非常快,看一眼灯亮没亮也快。
一句话理解: 布隆过滤器就像是一个**“灯阵记录仪”,它利用多重特征(多公式)映射到固定位置(计算结果),通过检查灯是否全亮**来帮你过滤掉不存在的请求。
2. 缓存击穿 (Breakdown) —— “定点爆破”
-
字面意思: 像一把重锤,对着防弹衣的某一个点疯狂锤击,直到把这个点锤烂。
-
场景: 一个超热点 Key(比如双 11 的抢购链接)突然过期了。
- 瞬间: 数万个请求同时涌入,发现缓存没了。
-
结果: 几万个请求同时去数据库“抢”这同一条数据,数据库瞬间压力爆炸。
-
记忆口诀: “击”是指一个点坏了,针对热点 Key 的定点爆破。
-
防御:
- 分布式锁(Redisson) :没必要所有人都去数据库查这一个记录的数据,所以只放一个人去查库,如果有自动放到缓存,其他人先等着看结果就行了。
- 逻辑永不过期:后台起个线程,快到期了自动续费。
3. 缓存雪崩 (Avalanche) —— “大面积崩塌”
-
字面意思: 山上的雪大面积滑坡,所有防线瞬间被淹没。
-
场景: 大批量的 Key 恰好在同一时间集体过期,或者 Redis 服务器宕机了。
-
结果: 整个系统的所有请求全部堆向数据库,数据库直接瘫痪。
-
记忆口诀: “崩”是指一大片全完了,整个系统崩盘。
-
防御:
- 随机过期时间:别让大家同时下班(过期时间设为 5min + 随机秒)。
- Redis 高可用:哨兵或集群模式,防止 Redis 整体挂掉。
常见问题
Redis 是单线程模型(核心执行命令的部分)。这意味着如果一个任务太重,后面的任务就得排长队,导致系统“卡死”。
大 Key
什么是: 比如一个 String 类型的值有 50MB,或者一个 Hash 列表里存了 100 万个元素。
危害: 1. 读取慢: 网络传输几十 MB 耗时很久。 2. 删除慢: 删除一个百万级的 Hash 可能会阻塞 Redis 几秒钟。
解决方案:
- 拆: 把一个大 Hash 拆成 100 个小 Hash(比如按用户 ID 取模)。
- 清理: 不要用
DEL,要用UNLINK。UNLINK会在后台慢慢删,不会卡住主线程。
热 Key (Hot Key)
什么是: 某个 Key(如:周杰伦演唱会门票)每秒被访问 10 万次,流量全挤在一台 Redis 服务器上。
危害: 流量超过网卡上限,甚至把 Redis 服务器压宕机。
解决方案:
- 二级缓存: 在 Java 程序本地(如 Caffeine 框架)存一份副本,请求先看本地,不去 Redis。
- 散列 Key: 给 Key 加随机后缀,把流量分散到 Redis 集群的不同机器上(如
Ticket_1,Ticket_2)。
淘汰策略:内存满了,Redis 怎么“扔行李”?
当 Redis 内存达到上限(maxmemory)时,它必须扔掉一些旧数据。
核心算法
- LRU (Least Recently Used): 淘汰最近最久没被用过的数据。看的是“时间”。(就像清理手机后台,关掉最久没点开的那个)。
- LFU (Least Frequently Used): 淘汰访问频率最低的数据。看的是“次数”。(哪怕刚访问过,但如果这一天只访问了这一次,也可能被删)。
常见策略设置
- allkeys-lru: 从所有数据中,剔除最久没用的。 (最推荐,最通用)
- volatile-lru: 只从设置了过期时间的数据中,剔除最久没用的。
- noeviction: 不删数据,直接报错拒绝写入。 (生产环境慎用,会导致业务崩溃)
为什么不能用 KEYS *?
原因:
KEYS * 会全表扫描 Redis 所有的 Key。
- 如果你的 Redis 里有几百万个 Key,这个扫描过程会非常久。
- 因为 Redis 是单线程,扫表期间所有其他请求(如登录、下单)全部在排队等待。
- 表现出来的现象就是:你点了一下回车,整个公司的 APP/网页全卡住了。
解决方案:
- 使用
SCAN命令: 它是“分批次”扫描的。每次只看一小部分,中间会给其他命令留出执行空档。虽然总时间可能更长,但不会卡死系统。