1.Redis 简介(数据模型 & 基本命令举例)
Redis(Remote Dictionary Server)是一款开源的、基于内存的高性能键值存储数据库,由 Salvatore Sanfilippo(antirez)于 2009 年创建,并以 BSD 许可协议发布。与传统的关系型数据库不同,Redis 将数据全部或部分保存在内存中,依靠单线程的事件循环模型处理请求,从而在读写性能上达到亚毫秒级延迟和数百万次请求/秒的吞吐量。
核心特性
-
丰富的数据结构
除了最基本的字符串(String),Redis 还原生支持哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)、位图(Bitmap)、超日志(HyperLogLog)、地理空间(Geo)和流(Stream)。这些结构使 Redis 能直接处理多种应用场景,无需在客户端做二次封装。 -
持久化与高可用
-
RDB 快照(BGSAVE):定期生成数据快照,启动速度快、文件体积小。
-
AOF 日志(Append-Only File):记录每条写命令,可配置不同的 fsync 策略,保证最小化丢失。
-
主从复制、哨兵(Sentinel)、集群(Cluster):支持多节点部署,实现数据冗余、故障切换和自动分片。
-
-
扩展与脚本
-
Lua 脚本:在服务器端原子执行复杂逻辑,避免多次网络往返。
-
模块化:Redis Modules 生态(如 RediSearch、RedisJSON、RedisBloom)不断丰富 Redis 功能。
-
典型应用场景
-
缓存:作为缓存层,减轻后端数据库压力,提升系统响应速度。
-
消息队列:基于 List、Stream 实现轻量级队列与发布/订阅。
-
排行榜与实时统计:利用有序集合维护分数排名,实现秒级更新。
-
会话存储:Session/Cookie 信息、用户登录状态等。
-
分布式锁、限流:结合原子命令(SETNX、Lua)与 Redisson 等客户端库,实现可靠的分布式协作和流量控制。
Redis 简洁易用、性能卓越,并在缓存、实时分析、消息队列、配置存储等多个领域得到了广泛应用,是现代微服务和高并发系统中不可或缺的基础组件。
2.持久化机制(RDB vs AOF)
1.RDB vs. AOF:选型建议
特性 |
RDB 快照(BGSAVE) |
AOF 日志 |
数据安全性 |
丢失窗口与快照间隔有关 |
always 模式下零丢失;everysec 最多丢 1 秒数据 |
恢复速度 |
非常快 |
相对较慢,需要重放所有写命令 |
磁盘占用 |
小,二进制压缩格式 |
较大,文本格式,可通过重写减小 |
对运行时影响 |
fork 期间有短暂开销 |
写命令时 I/O 同步开销 |
运维难度 |
简单易用 |
需要考虑重写策略、日志增长 |
-
高性能缓存,对持久化要求不高,可只用 RDB;
-
对数据丢失零容忍,推荐开启 AOF(appendfsync everysec 或更严格);
-
综合考虑,可同时开启 RDB + AOF,取两者优点:RDB 负责定期备份,AOF 负责补足两次快照间的写操作。
实用命令
# 手动触发 RDB 快照
redis-cli BGSAVE
# 查看上次快照信息
redis-cli INFO Persistence | grep rdb
# 启用 AOF
# 在 redis.conf:
# appendonly yes
# appendfsync everysec
# 手动触发 AOF 重写
redis-cli BGREWRITEAOF
# 查看 AOF 信息
redis-cli INFO Persistence | grep aof
通过以上机制配置与选型,就能灵活平衡数据安全、恢复速度与运行性能,满足不同业务场景的需求。
2.BGSAVE(RDB 快照)
1. 原理
-
当你在 Redis CLI 中执行 BGSAVE(或配置了自动快照策略后由 Redis 自动触发)时,Redis 主进程会调用 fork() 派生出一个子进程。
-
子进程在后台将当前内存中的所有数据以二进制方式写入一个临时的 RDB 文件(默认:dump.rdb 或者 appendonly.rdb)。
-
写入完成后,子进程将临时文件替换为正式快照文件,主进程继续处理客户端命令不受阻塞。
2. 优点
-
效率高:快照文件体积小,写入压缩后的二进制格式,恢复速度快。
-
对实时性能影响小:由于写入由子进程完成,主进程几乎无停顿(只是短暂的 fork)。
-
自动快照:可在 redis.conf 中配置 save <seconds> <changes> 定时触发,比如 save 900 1 表示 15 分钟内至少有 1 次修改就做一次快照。
3. 缺点
-
数据丢失窗口:快照只在触发时刻保存数据,若在两次快照之间 Redis 宕机,期间的写操作会丢失。
-
fork 消耗:在数据量巨大时,fork() 可能占用较多内存和 CPU,且子进程写入磁盘的 I/O 压力也不小。
3.AOF(Append-Only File,追加日志)
1. 原理
-
当你启用 AOF(在 redis.conf 中设置 appendonly yes)后,Redis 会把每个写命令(如 SET、DEL、INCR 等)都以文本形式追加到 AOF 日志文件(默认:appendonly.aof)末尾。
-
在重启时,Redis 会依次重放(replay)AOF 中的所有命令来重建内存状态。
2. 同步策略
在 appendfsync 配置下可选三种同步策略:
- always:每条命令写入后都 fsync(最安全,最慢)
- everysec:每秒 fsync 一次(性能与安全的折中,默认)
- no:由操作系统决定何时刷盘(最快,但最不安全)
3. 优点
-
最小化数据丢失:在 always 模式下,写一条命令就刷一遍盘,理论上不会丢数据;在 everysec 模式下最坏也只丢最后 1 秒的数据。
-
可读性:AOF 是可读文本,便于审计与调试。
-
可重写压缩:通过 BGREWRITEAOF 命令,Redis 会在后台重写日志,去掉冗余操作,生成体积更小的 AOF 文件。
4. 缺点
-
恢复速度慢:重放每条命令比直接加载 RDB 更耗时,尤其是大量小命令时。
-
文件体积大:相比 RDB,AOF 文件更大,占用更多磁盘空间。
-
写入开销:大量 fsync 会增加 I/O 压力,可能影响性能。
3.过期 & 内存淘汰策略
1.数据过期策略
Redis 针对 Key 的过期(TTL)管理,主要通过以下几方面来实现:命令接口、过期时间精度、过期删除策略(惰性 + 定期)、以及底层实现细节。下面分块说明:
定义
一、过期命令接口
命令 |
含义 |
EXPIRE key seconds |
为 key 设置秒级过期时间 |
PEXPIRE key milliseconds |
为 key 设置毫秒级过期时间 |
EXPIREAT key timestamp |
以**Unix 时间戳(秒)**形式设置绝对过期时间 |
PEXPIREAT key ms-timestamp |
以**Unix 时间戳(毫秒)**形式设置绝对过期时间 |
TTL key |
返回 key 距离过期的剩余秒数;-1 表示永不过期;-2 表示不存在 |
PTTL key |
返回以毫秒计的剩余过期时间,其它含义同 TTL |
PERSIST key |
移除过期时间,使 key 变为永久 |
二、过期时间精度
-
秒级 vs 毫秒级
-
EXPIRE / EXPIREAT:秒级,精度为 1s
-
PEXPIRE / PEXPIREAT:毫秒级,精度为 1ms
-
实际执行
-
Redis 内部使用 mstime() 获取当前毫秒,因此所有过期比较都是毫秒精度;秒级命令内部会换算为毫秒。
三、过期删除策略
Redis 结合了 惰性删除(Lazy)和 定期删除(Active)两种方式,既保证不返回过期数据,又及时释放内存。
1. 惰性删除(Lazy Deletion)
-
触发时机:客户端对某个 Key 做访问(GET/SET/…)时,检查它是否过期
-
实现流程:
-
查主字典看 Key 是否存在
-
若存在,再查过期字典看是否已过期
-
若已过期,删除 Key 并返回 Miss;否则,正常返回
-
优缺点:
-
✅ 无额外定时开销,只有访问才检查
-
❌ 如果大量过期 Key 不被访问,会长期占用内存
2. 定期删除(Active Deletion)
-
触发时机:Redis 事件循环中,每隔一段时间(配置项 hz 决定,默认 10 次/秒)主动执行一次过期扫描
-
扫描算法:
-
从过期字典中随机抽取若干(默认 20)个 Key
-
对每个 Key 检查是否过期,过期则删除
-
计算抽样中被删比例,如果超过 25%,重复本次扫描(最多做一次)
-
优缺点:
-
✅ 能回收不再被访问的过期 Key
-
❌ 扫描量可控但不保证全覆盖,短时间内仍有部分过期 Key 未被清理
四、底层数据结构
Redis 每个数据库(DB)内部维护两张哈希表(dict):
主字典 db->dict:存储实际的 Key → Value
过期字典 db->expires:存储需要过期的 Key → 到期毫秒时间戳
-
设置过期:向 db->expires 插入或更新一条记录
-
移除过期:在删除或持久化(PERSIST)时,从 db->expires 删除对应条目
-
检查过期:惰性或定期扫描都只需要读 db->expires,再去主字典删除对应条目
五、参数调优
- hz(事件循环频率)
- 影响定期删除的触发频率与次数
- 在 redis.conf 或运行时 CONFIG SET hz <n> 调整;较高可更及时清理过期 Key,但会增加 CPU 开销
- 抽样大小与比例阈值
- 源码中固定为抽样 20 个 Key,25% 过期比例可以触发二次扫描
- 通过修改源码可调,但一般默认足够
六、使用建议
-
合理设置 TTL:对业务缓存和会话等用短 TTL;对某些辅助数据可长 TTL 或不设过期
-
监控过期情况:关注 INFO Persistence 或 INFO keyspace 中的 expired_keys、expired_stale_perc 等指标
-
防止雪崩:对同一类 Key 过期时间加抖动;结合缓存三兄弟策略避免突发失效
-
定期清理:在业务低峰可主动调用 BGSAVE+Bgrewriteaof 后再手工触发 CLIENT KILL TYPE normal 等命令清理离线连接或脏数据
通过上述机制,Redis 能在高性能的前提下,既保证访问路径上不返回已过期的 Key,又能及时回收“沉睡”在内存中的过期数据,平衡实时性与内存利用。
惰性删除(Lazy Deletion)
定义
只有在客户端访问某个 Key 时,才检查它是否已过期。如果过期,才进行真正的删除(并返回 miss)。
实现流程
- 客户端发起对 Key 的 GET/SET 等访问请求。
- 缓存检查 Key 是否存在:
- 不存在:直接返回 Miss。
- 存在:再检查其 TTL:
- 未过期 → 正常返回或设置。
- 已过期 → 删除 Key,返回 Miss。
优点
-
零额外开销:不需要额外的定时任务或扫描,当且仅当访问时才做检查。
-
简单易实现:直接在访问路径里加一次过期判断。
缺点
-
过期 Key 堆积:如果某些 Key 长期不被访问,即使已过期也不会被清理,占用内存。
-
穿透压力:当大量客户端同时访问同一 Key 且该 Key 已过期,会触发 “雪崩式” 并发删除检查和后端加载。
定期删除(Periodic/Active Deletion)
定义
系统定时(或以固定频率)扫描一部分缓存数据,主动清理已过期的 Key。
实现方式
-
定时任务:在后台启动一个定时线程,周期性(如每秒或每分钟)随机扫描若干个 Key,检查过期并删除。
-
分批采样:每次只扫描一小批记录,分散 I/O 和 CPU,避免一次全量扫描。
优点
-
及时回收:能够较快地清理掉不再访问的过期 Key,释放内存。
-
可控制清理节奏:通过调整扫描频率和扫描批次大小,平衡清理速度与系统开销。
缺点
-
额外开销:需要占用 CPU 和 I/O 资源来周期性扫描、判断和删除。
-
不一定全覆盖:若扫描范围有限,某些过期 Key 可能要等很久才被命中扫描。
两者结合的常见做法
大多数高性能缓存(如 Redis)会同时采用这两种策略:
-
惰性删除保证访问路径上不会返回已过期的数据。
-
定期删除则在后台回收那些“沉睡”很久、不会再被访问的过期 Key。
这样既能保证访问实时性的开销最小化,也能及时清理不再需要的资源,达到内存回收和访问性能的平衡。
小结
策略 |
触发时机 |
优点 |
缺点 |
惰性删除 |
客户端访问时检查 |
无额外定时开销;实现简单 |
不访问的不清理;并发访问冲击大 |
定期删除 |
后台定时扫描片段 |
可及时回收过期 Key;节奏可控 |
占用额外资源;扫描不覆盖全量 |
通过合理配置扫描频率、批次大小,以及结合访问模式,就能在性能与内存利用之间取得最佳平衡。
2.数据淘汰策略
当 Redis 达到 maxmemory 限制后,就会根据配置的 淘汰策略(maxmemory-policy)来决定如何清理旧数据,以腾出空间给新写入的数据。下面按触发条件和常见用途,对八种常见策略做一个梳理:
策略名 |
范围 |
淘汰算法 |
行为特征 |
典型场景 |
noeviction |
— |
— |
不淘汰,写命令超内存后返回错误(OOM) |
强一致场景,需要严控写入 |
allkeys-lru |
所有键 |
最近最少使用(LRU) |
对所有 Key 计算 LRU,淘汰最久未访问的那个 |
对访问热点敏感、希望热点常驻 |
volatile-lru |
仅限有过期键 |
最近最少使用(LRU) |
只在带 TTL 的 Key 中做 LRU 淘汰 |
数据库 + 缓存混合,短期缓存 |
allkeys-random |
所有键 |
随机 |
从全量 Key 中随机选一个淘汰 |
访问分布均匀,无明显热点 |
volatile-random |
仅限有过期键 |
随机 |
从设置了过期时间的 Key 中随机淘汰 |
只想淘汰缓存数据,不动永久数据 |
volatile-ttl |
仅限有过期键 |
最近过期优先 |
从带 TTL 的 Key 中,优先淘汰过期时间最短(最快过期) |
缓存强时效场景 |
allkeys-lfu |
所有键 |
最近最不常用(LFU) |
近似统计访问频率,淘汰访问频率最低的 Key |
热点变化慢,希望保留“热”数据 |
volatile-lfu |
仅限有过期键 |
最近最不常用(LFU) |
仅在带 TTL 的 Key 中做 LFU 淘汰 |
对缓存访问频率敏感 |
如何选择
-
“写入超出就拒绝”:
当业务对写入顺序和成功率绝对敏感,可选 noeviction,让客户端自己限流或降级。
-
保留热点、淘汰冷数据:
-
访问模式有明显热点且希望热点常驻:allkeys-lru 或 allkeys-lfu
-
热点变化慢、希望精细跟踪频率:allkeys-lfu
-热点变化快、关注最近使用时间:allkeys-lru
-
只淘汰缓存、保留“永久”数据:
生产环境往往混合了“缓存”(带 TTL)与“业务数据”(无 TTL),这时使用 volatile-* 系列:
-
volatile-lru:淘汰最久未用的缓存
-
volatile-ttl:优先淘汰快过期的缓存
-
volatile-random:随机淘汰缓存
-
极简场景:
无明显访问热点,或者只想快速释放空间,可以选 allkeys-random。
配置示例
# 设置最大内存为 4GB
maxmemory 4gb
# 当超过 maxmemory 时,使用全量 LRU 淘汰
maxmemory-policy allkeys-lru
也可以在运行时通过 CONFIG SET 动态调整:
# 查看当前策略
redis-cli CONFIG GET maxmemory-policy
# 修改为 LFU 策略
redis-cli CONFIG SET maxmemory-policy allkeys-lfu
通过合理地为不同类型的数据和访问模式选择合适的淘汰策略,既可保证缓存命中率,也能避免业务数据被意外清理。
4.缓存常见问题与模式
1.缓存三兄弟
缓存穿透(Cache Penetration)
什么情况?
客户端不断请求一些缓存和数据库都不存在的“脏”数据,比如 user:不存在ID。由于缓存查不到,直接穿透到数据库,DB 承受极大压力。
具体流程:
请求 user:9999
→ 缓存 MISS
→ 数据库查找,不存在
→ 返回空或 Null
→ 下次相同请求,重复上述流程
防护办法:
- 布隆过滤器:在缓存之前用 BloomFilter 快速判断 ID 是否可能存在,不存在则直接拒绝或返回默认值。
- 空对象缓存:对“确实不存在”的 Key,缓存一个空值(或特定标记),并设置短 TTL(如几分钟),避免重复穿透。
- 校验层:在业务层做严格参数校验(ID 范围/格式),尽量拦截非法请求。
缓存击穿(Cache Breakdown/Hot Key Avalanche)
什么情况?
某个“热点 Key”(如商品详情、热门文章)在高并发下突然过期,瞬间大量请求同时穿透到数据库,DB 一下子被压垮。
具体流程:
vbnet
Key “item:123” TTL 到期
大量并发请求同时来
→ 缓存 MISS
→ 全部到数据库查询
→ DB 压力骤增
防护办法:
- 互斥锁(Mutex)或信号量
- 第一个请求查询 DB 并重建缓存,其它请求等待或用旧值兜底。
- Java 示例:if (cacheMiss) { lock.lock(); try{…} finally{ lock.unlock(); } }
- 请求队列/单飞(Single Flight)
- 类似 Go 里的 singleflight,相同 Key 只允许一次后端加载,其它排队复用结果。
- 永不过期
- 对极少数超热点 Key,采用长 TTL 或永不过期+后台主动刷新。
缓存雪崩(Cache Avalanche)
什么情况?
在同一时间点,大量缓存 Key 同时失效(如配置相同的固定过期时间),或者缓存集群不可用,导致瞬时大量请求落到数据库。
具体成因:
-
TTL 设定过于集中。
-
缓存重启/失联(网络抖动、内存溢出、宕机重启)。
防护办法:
- TTL 随机化
- 对缓存过期时间加上随机值,使失效时点分散;
ini
ttl = baseTTL + random(0, jitter)
- 多级缓存
- L1:本地进程内缓存;L2:分布式缓存。L1 缓存失效时,可短暂依赖 L2。
- 缓存预热/主动刷新
- 在缓存重启或过期前,由后台任务批量预加载热点数据;
- 对极其关键的数据,设置永不过期,并后台定时更新。
- 降级策略
- 当缓存失效集中时,部分非核心功能可返回降级结果或限流熔断,保护后端数据库。
小结
问题类型 |
触发条件 |
核心风险 |
典型方案 |
缓存穿透 |
查询不存在的数据 |
连续打穿缓存,压力打到 DB |
BloomFilter、空值缓存、参数校验 |
缓存击穿 |
热点 Key 单点过期 |
热点瞬间落库并发读,DB 压力骤增 |
互斥锁/单飞/永不过期+后台刷新 |
缓存雪崩 |
大量 Key 同时过期或缓存宕机 |
瞬时大量 MISS,DB 承受雪崩式压力 |
TTL 随机化、多级缓存、预热、降级限流 |
2.延时双删
1. 定义
针对读多写少的Cache-Aside缓存策略,更新数据时直接删除缓存可能会遇到“并发写与读交错”导致的缓存脏数据问题。延时双删在“更新数据库前后”各删除一次缓存,并在两次删除之间等待一段短暂延时,以尽量避免并发读写导致的不一致。
2. 实现流程
pseudo
# 假设要更新 user:123 的数据
1. DELETE cache:user:123 # 首次删缓存
2. UPDATE database SET ... WHERE id # 更新数据库
3. SLEEP X ms # 等待并发读操作完成
4. DELETE cache:user:123 # 二次删缓存
3. 原理 & 优点
-
第一次删除保证“更新前”的旧缓存消失
-
第二次删除兜底:若在更新数据库期间,有其他线程读旧数据并写入缓存,就在延时后再删掉一次
4. 缺点
-
存在短暂延时窗口,延时设置难以平衡“性能”与“一致性”
-
并不能 100% 消除脏读,只是大幅降低概率
3.双写一致性
1. 定义
双写一致性(常见于“数据库 + 缓存”或“数据库 + 搜索引擎”场景)指:在一次更新操作中,需要同时将数据写入数据库和写入另一存储层(如缓存、ES)。如何保证二者的数据一致性,就是“双写一致性”问题。
2. 常见策略
策略 |
流程描述 |
优缺点 |
先写 DB 再写 Cache |
1. 更新数据库 |
+ 简单 |
先写 Cache 再写 DB |
1. 更新缓存 |
+ 写 DB 慢时,缓存可先给前端响应 |
异步消息队列 |
1. 写数据库 |
+ 松耦合,可重试 |
分布式事务(2PC/TCC) |
通过事务协调器保证两端要么都提交、要么都回滚 |
+ 强一致 |
3. 典型伪码(先 DB 后 Cache + MQ 补偿)
pseudo
# 写操作入口
BEGIN TRANSACTION
UPDATE database SET ... WHERE id = 123
if success:
COMMIT
try:
# 异步发送更新缓存的消息
MQ.publish(topic="update_cache", payload={id:123, data:newData})
except:
# MQ 失败,可记录补偿表,定时重试
record_compensation(id=123, data=newData)
else:
ROLLBACK
4. 注意点
-
幂等:消费者更新缓存/ES 时要保证幂等性
-
补偿机制:消息发送或消费失败后,需要自动或人工介入重试
-
监控与告警:缓存和数据库数据不一致时要及时发现并修复
小结
-
分布式锁:保证分布式环境下对共享资源的互斥访问;
-
延时双删:缓存更新时的一种“前后各删一次”策略,用以减少并发读写带来的脏数据;
-
双写一致性:在一次更新中同时写入数据库和另一层存储(缓存/搜索引擎),需设计可靠的顺序、补偿与重试机制。
5.分布式锁
介绍
1.定义
在分布式系统中,多个进程/服务可能并发地访问同一份资源(如同一条数据库记录、同一个文件、同一段内存计数器等)。为了保证“同一时刻只有一个客户端能操作该资源”,就需要分布式锁。
2. 典型场景
-
并发下发放抢购商品库存
-
多实例环境中保证定时任务只执行一次
-
保证分布式事务中的临界区互斥
3. 常见实现方式
-
基于 Redis
-
使用 SET key value NX PX expire 原子地申请锁
-
释放锁时要做“先比对再删除”的操作(用 Lua 脚本保证原子性)
-
基于 ZooKeeper
-
利用其临时顺序节点特性,最小顺序节点持有锁
-
基于 etcd
-
借助 etcd 的租约(lease)和 Compare-and-Swap
pseudo
# 以 Redis 为例
# 获取锁
if SET resource_key lock_id NX PX 30000 == OK:
# 获得锁,执行业务
...
# 释放锁(Lua 脚本保证原子性)
if GET resource_key == lock_id then DEL resource_key
else:
# 未获得,重试或失败
4. 注意事项
-
锁过期:设置合理的过期时间,防止持锁方宕机后锁永不释放
-
续租:长业务可定期延长锁过期
-
防死锁:加超时、重试、回退策略
Redis 分布式锁(基于 SETNX)
1. 原理与伪码
- 尝试加锁
text
# SETNX:仅当 key 不存在时才设置,返回 1 表示成功,0 表示已被别人占用
SETNX lock:order:123 clientId
EXPIRE lock:order:123 30000 # 设置过期时间,避免死锁
释放锁
- 为防止误删别人锁(锁失效后被别的客户端持有),要先比对 clientId,再删除:
lua
-- unlock.lua
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
text
EVALSHA unlock.lua SHA1(lock.lua) 1 lock:order:123 clientId
2. 注意事项
-
唯一标识:每次加锁都用一个全局唯一的 token(如 UUID),保证释放时不误删别人的锁。
-
锁超时:业务时间可能超过预设过期时间,需要在锁持有期间定时续期(watchdog)。
-
重试与退避:未拿到锁时,不要不停打空转,可配合固定/指数退避重试。
Redisson 分布式锁
Redisson 是基于 Netty 的 Java Redis 客户端,封装了大量分布式结构,包括可重入锁、读写锁、信号量、布隆过滤器等。
1. 核心优势
-
API 接近 java.util.concurrent:使用习惯与本地锁几乎一致。
-
自动续期:默认“看门狗”续租,避免锁过期。
-
锁可重入、公平锁、读写锁 等多种类型。
-
多种部署模式兼容:单节点、主从、哨兵、集群模式。
2. 示例代码
// 1. 获取 Redisson 客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 2. 获取可重入锁
RLock lock = redisson.getLock("orderLock:123");
// 3. 加锁
lock.lock(); // 默认看门狗续租
try {
// 业务代码:处理订单…
} finally {
// 释放锁
lock.unlock();
}
// 4. 关闭客户端(应用关闭时)
redisson.shutdown();
3. 进阶用法
-
尝试加锁:
boolean acquired = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (acquired) {
try { … } finally { lock.unlock(); }
}
-
公平锁:redisson.getFairLock("fair:lock")
-
读写锁:redisson.getReadWriteLock("rw:lock")
6.高可用 & 集群
主从同步
Redis 的主从同步(Replication)是指将一个 Redis 节点(Master)上的数据,复制(同步)到一个或多个其他节点(Slave/Replica)上,以达到读写分离、水平扩展、容灾备份等目的。下面分几部分来说明:
1.为什么要主从同步
-
读写分离
-
写只发生在 Master 上,保证数据唯一性。
-
读可以分担到多个 Slave,提高吞吐量和并发读性能。
-
-
容灾备份
从节点实时保留一份数据副本,Master 宕机时可提升为新的 Master。 -
运维场景
做线上备份、架设只读报告库、升级或迁移时可先把流量切到 Slave 上。
2.主从拓扑
-
一主多从:最常用拓扑,主节点负责写,从节点负责读。
-
级联复制:从节点也可以再作为其它节点的 Master,形成多级链路。
-
哨兵(Sentinel) & 集群(Cluster):在此基础上增加故障自动切换和数据分片能力。
3.同步流程
全量同步(Full Sync)
当 Slave 第一次连接,或请求的 replication ID/offset Master 无法续传时,就触发全量同步:
-
Slave → Master
发送PSYNC ? -1
(或者旧版命令SYNC
),表示自己没有任何复制状态。 -
Master → Slave
-
Master fork 出子进程执行
BGSAVE
,生成 RDB 快照。 -
主进程继续接收写命令,并将这些命令追加到复制积压缓冲区(replication backlog),即事务日志。
-
子进程将 RDB 文件流式发送给 Slave。
-
-
Slave 接收 RDB
-
Slave 将接收到的 RDB 持久化到本地,再加载到内存,完成“全量载入”。
-
-
同步积压日志
-
RDB 传输完成后,Master 将积压缓冲区中从生成快照到此刻的写命令,一并发送给 Slave。
-
Slave 按序执行这些命令,直到赶上 Master 的当前状态。
-
-
进入半同步
-
Slave 记录下自己最后执行的 replication offset,后续同步切换到增量模式。
-
增量同步(Partial Sync)
全量同步完成后,Slave 和 Master 会维护一个 replication ID 和 offset:
-
Slave → Master:定期发送
PSYNC <replid> <offset>
,告诉 Master 自己已同步到哪个偏移量。 -
Master → Slave:如果收到的
<replid>
与自己当前一致,且<offset>
在积压缓冲区范围内,则只把从该 offset 之后新的写命令,直接推送给 Slave。 -
若无法续传(ID 变了或 offset 不在缓冲区中),则退回全量同步流程。
优势:避免每次重连都做 RDB 快照,只同步最新命令,提高效率、降低磁盘和网络开销。
四、关键命令与配置
-
在 Master 上
conf
# 默认即可开放复制,Slave 会主动连接 # 若要做只读,防止误写: replica-read-only yes
-
在 Slave 上
conf
replicaof <master-ip> <master-port> # or 在运行时 > SLAVEOF <master-ip> <master-port>
-
查看状态
redis-cli INFO replication
7.小结
-
丰富的数据结构:Redis 不仅支持最基本的字符串,还原生提供哈希、列表、集合、有序集合、位图、HyperLogLog、Geo 和 Stream 等多种结构,满足从简单缓存到复杂实时统计、地理位置检索等场景的需求。
-
极速的读写性能:基于单线程事件循环模型和内存存储,Redis 能实现亚毫秒级延迟和百万级 QPS 吞吐,适合对延迟敏感的高并发系统。
-
可靠的持久化与高可用:RDB 快照和 AOF 日志相辅相成,兼顾启动速度与数据安全;主从复制、Sentinel 守护和 Cluster 分片,提供故障切换与线性扩展能力。
-
灵活的扩展生态:通过 Lua 脚本原子执行复杂逻辑、模块化插件(如 RediSearch、RedisJSON、RedisBloom)不断丰富功能,实现从消息队列到分布式锁、限流、实时分析等多样化应用。
总之,Redis 以其卓越的性能、灵活的数据模型和完善的生态,已成为现代互联网系统中不可或缺的基础组件。无论是加速缓存层、构建实时服务,还是实现分布式协作与大规模扩展,Redis 都能提供高效、可靠的解决方案。