Redis 是互联网技术栈的标配组件 ,既是高性能内存数据库 ,又是万能缓存中间件,其定位比传统数据库更灵活,比纯缓存更强大
一、Redis 的定位:不仅仅是缓存
Redis 的官方定位是 "In-Memory Data Structure Store" ,它同时具备三重身份:
1. 内存数据库
- 数据纯内存存储 ,读写性能达 10万+ QPS(sub-millisecond 延迟)
- 支持持久化,重启后可恢复数据(区别于 Memcached)
- 提供丰富的数据结构(String/List/Set/ZSet/Hash/Bitmap/Stream 等)
2. 高速缓存层
- 缓存热点数据,减轻后端数据库压力(MySQL → Redis → 应用)
- 支持自动过期(TTL) 、LRU 淘汰策略
- 缓存击穿/穿透/雪崩防护机制
3. 数据结构服务器
- 原子操作:INCR、SETNX、LPUSH、ZADD 等单线程原子命令
- 发布订阅:Pub/Sub 实现轻量级消息广播
- 分布式锁:SET key value NX EX 30 实现 Redisson 锁
二、内存数据库的核心优势
优势 1:性能碾压磁盘数据库
| 指标 | Redis | MySQL | 性能倍数 |
|---|---|---|---|
| 读取延迟 | 0.1-1ms | 5-10ms | 10-50 倍 |
| 写入延迟 | 0.1-1ms | 10-20ms | 20-100 倍 |
| QPS(单实例) | 10万+ | 5000 | 20 倍 |
| 并发连接 | 10万+ | 2000 | 50 倍 |
原理 :内存寻址速度是磁盘寻址的 10万倍(纳秒 vs 毫秒)
优势 2:数据结构丰富(超越 KV)
java
// String:缓存用户 Token
SET user:1001:token "abc123" EX 3600
// Hash:存储用户对象
HSET user:1001 name "tom" age 25
HGETALL user:1001 // {name: "tom", age: "25"}
// List:消息队列(先进先出)
LPUSH notify_queue "message1"
RPOP notify_queue
// Set:标签系统、共同好友
SADD user:1001:tags "vip" "active"
SINTER user:1001:tags user:1002:tags // 共同标签
// ZSet:排行榜、延时队列
ZADD game_rank 1000 "player1" // score = 1000
ZRANGE game_rank 0 10 WITHSCORES // 前十名
// Bitmap:日活统计
SETBIT active:20240101 1001 1 // 用户1001 在 1月1日活跃
BITCOUNT active:20240101 // 统计当日活跃用户数
// HyperLogLog:UV 去重统计(占用内存仅 12KB)
PFADD page:uv:20240101 "user1" "user2" "user3"
PFCOUNT page:uv:20240101 // 估算 UV
// Stream:消息流(Kafka 简化版)
XADD user_events * action "login" user_id 1001
XREAD COUNT 10 STREAMS user_events 0
对比 Memcached:仅支持 String,Redis 支持 10+ 种数据结构
优势 3:原子操作保障并发安全
java
// 单线程模型(6.0 前),命令串行执行,天然线程安全
// 无需加锁即可实现分布式场景
// 秒杀扣库存(原子性保障)
DECR product:SKU123:stock // 返回最新库存,不存在竞态
// 分布式锁(原子性获取 + 过期)
SET lock:order:1001 "request_id" NX EX 30 // NX = 不存在才设置
// 乐观锁(WATCH + MULTI/EXEC)
WATCH user:1001:balance
MULTI
DECRBY user:1001:balance 100
INCRBY user:1001:points 10
EXEC // 若 balance 被修改,EXEC 返回 nil(回滚)
优势 4:高可用架构
java
// 主从复制(一主多从)
Master → Slave1 → Slave2
- 读写分离:主写从读
- 故障切换:Sentinel 自动选主
// Redis Cluster(去中心化,16384 槽位)
Node1(0-5460) ↔ Node2(5461-10922) ↔ Node3(10923-16383)
- 自动分片:数据按 CRC16(key) % 16384 分布
- 故障转移:Gossip 协议自动检测
- 线性扩展:增加节点,槽位重新分配
// Redis Sentinel 哨兵
监控 + 自动故障转移 + 配置提供
优势 5:客户端生态成熟
- Java:Jedis(老牌)、Lettuce(响应式)、Redisson(分布式对象)
- Spring Boot:spring-boot-starter-data-redis 开箱即用
- 监控:RedisInsight、Prometheus + Grafana
三、Redis 核心应用场景
场景 1:缓存层(占比 70%)
java
// 缓存击穿:热点数据永不过期 + 后台刷新
String getUser(Long userId) {
String key = "user:" + userId;
String value = redis.get(key);
if (value == null) {
// 加锁防止并发查询 DB
if (redis.setnx("lock:" + userId, "1")) {
redis.expire("lock:" + userId, 10);
value = db.query("SELECT * FROM user WHERE id = ?", userId);
redis.setex(key, 3600, value); // 写入缓存
redis.del("lock:" + userId);
} else {
Thread.sleep(100); // 等待后重试
return getUser(userId);
}
}
return value;
}
// 缓存雪崩:过期时间加随机值
redis.setex(key, 3600 + random(0, 300), value);
场景 2:计数器与限速器
java
// 接口限流(每分钟 100 次)
String key = "rate_limit:user:1001:" + System.currentTimeMillis() / 60000;
Long count = redis.incr(key);
if (count > 100) {
throw new RateLimitException("访问超限");
}
redis.expire(key, 60); // 1 分钟过期
// 全局唯一 ID(INCR 原子递增)
Long orderId = redis.incr("global:order:id"); // 分布式唯一
场景 3:分布式锁
java
// Redisson 实现(官方推荐)
RLock lock = redissonClient.getLock("lock:order:1001");
try {
// 尝试加锁,最多等待 10 秒,锁自动释放 30 秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 执行业务逻辑
processOrder(1001);
}
} finally {
lock.unlock(); // 释放锁
}
// 底层命令
SET lock:order:1001 "UUID" NX EX 30 // NX = Not Exists
场景 4:消息队列
java
// List 实现简单队列
// 生产者
redis.lpush("queue:email", "email:tom@example.com");
// 消费者(阻塞弹出)
while (true) {
String email = redis.brpop(0, "queue:email"); // 0 = 永久阻塞
sendEmail(email);
}
// Stream 实现可靠队列(类似 Kafka)
// 生产者
XADD queue:orders * order_id 1001 user_id 2001
// 消费者组
XGROUP CREATE queue:orders order_group $ MKSTREAM
XREADGROUP GROUP order_group consumer1 COUNT 1 STREAMS queue:orders >
场景 5:排行榜
java
// ZSet 实现实时榜单
ZADD game:rank 1000 "player:1001" // player:1001 分数 1000
ZADD game:rank 1500 "player:1002"
// 查询 Top 10
ZRANGE game:rank 0 9 WITHSCORES // O(log n) + O(m)
// 查询玩家排名
ZREVRANK game:rank "player:1001" // 返回排名
场景 6:发布/订阅(Pub/Sub)
java
// 发布者
redis.publish("channel:order", "order:1001 created");
// 订阅者(独立线程)
redis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
if ("channel:order".equals(channel)) {
processOrderEvent(message);
}
}
}, "channel:order");
场景 7:布隆过滤器(防缓存穿透)
java
// Redisson 实现
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloom:product");
bloomFilter.tryInit(1000000, 0.01); // 预期插入 100 万,误判率 1%
// 预热数据
bloomFilter.add("product:1001");
bloomFilter.add("product:1002");
// 查询时先查布隆过滤器
if (!bloomFilter.contains(productId)) {
return null; // 一定不存在,直接返回
}
// 再查 Redis/DB
四、持久化机制:RDB vs AOF
Redis 是内存数据库,数据易失。持久化保证重启后数据恢复
1、RDB(Redis Database)快照持久化
原理 :fork 子进程 ,将内存数据全量写入 二进制文件(dump.rdb)
触发方式:
bash
# redis.conf
save 900 1 # 900 秒内有 1 次修改则触发
save 300 10 # 300 秒内有 10 次修改
save 60 10000 # 60 秒内有 10000 次修改
# 手动触发
SAVE # 阻塞主进程,生产环境禁用
BGSAVE # 后台 fork 子进程(推荐)
实现流程:
bash
1. 主进程接收 BGSAVE 命令
2. fork 子进程(Copy-On-Write,内存页只读共享)
3. 子进程扫描内存数据,序列化写入 temp.rdb
4. 子进程写完信号通知主进程
5. 主进程替换旧 dump.rdb
核心特点:
- 全量备份:文件紧凑,适合全量恢复
- 恢复快:加载 RDB 比 AOF 快 5-10 倍
- 性能影响小:fork 子进程,主线程几乎无阻塞
- 数据丢失风险:两次快照之间宕机,中间数据丢失
文件结构:
bash
dump.rdb
├── REDIS0009 # 版本号
├── 数据区(压缩存储)
└── 校验和
优点:
✅ 文件小,适合备份
✅ 恢复速度快
✅ 对性能影响小
缺点:
❌ 数据丢失(最多丢失上次快照后的所有数据)
❌ fork 子进程消耗内存(Copy-On-Write 期间内存可能翻倍)
❌ 大数据量时 fork 耗时(>10GB 数据可能 fork 1 秒以上)
2、AOF(Append Only File)日志持久化
原理 :将每条写命令 追加到日志文件(redis.aof),重启时重放命令恢复数据
三种同步策略:
bash
# redis.conf
appendonly yes # 开启 AOF
appendfsync always # 每个命令都 fsync(最安全,最慢)
appendfsync everysec # 每秒 fsync(默认,平衡)
appendfsync no # 不主动 fsync(最快,最不安全)
实现流程:
bash
1. 客户端执行 SET key value
2. 命令写入 AOF 缓冲区
3. 根据策略刷盘:
- always:立即 fsync 到磁盘
- everysec:每秒由后台线程 fsync
- no:等待 OS 刷盘
4. 重启时读取 AOF 文件,逐条执行命令恢复
AOF 重写(Rewrite) :
AOF 文件会膨胀(多次修改同一 key),需定期重写瘦身
bash
# 自动重写条件
auto-aof-rewrite-percentage 100 # 文件大小翻倍时触发
auto-aof-rewrite-min-size 64mb # 最小 64MB 才触发
重写过程:
bash
# 原理:fork 子进程,扫描内存数据,生成最简命令集
# 原命令:INCR counter; INCR counter; INCR counter
# 重写后:SET counter 3
BGREWRITEAOF # 手动触发后台重写
核心特点:
- 增量备份:记录所有写命令,数据更安全
- 最多丢 1 秒数据(everysec 策略)
- 文件体积大:比 RDB 大 3-5 倍
- 恢复慢:重放命令比加载 RDB 慢
优点:
✅ 数据安全(最多丢 1 秒)
✅ 可读性强(文本文件,可手动修复)
✅ 支持后台重写,瘦身文件
缺点:
❌ 文件体积大
❌ 恢复速度慢
❌ 写入性能低于 RDB(always/everysec)
RDB vs AOF 对比总览表
| 对比维度 | RDB | AOF |
|---|---|---|
| 持久化方式 | 全量快照(内存镜像) | 增量日志(命令追加) |
| 文件大小 | 小(二进制压缩) | 大(文本命令) |
| 数据安全性 | 低(可能丢分钟级数据) | 高(最多丢 1 秒) |
| 恢复速度 | 快(直接加载内存) | 慢(逐条执行命令) |
| 写入性能 | 高(fork 不影响主线程) | 中(everysec 稍有影响) |
| 内存消耗 | 高(fork 时翻倍) | 低(无 fork) |
| 可读性 | 差(二进制) | 好(文本可编辑) |
| 适用场景 | 全量备份、快速恢复 | 数据安全优先、能接受慢恢复 |
五、混合持久化(Redis 4.0+ 推荐)
最佳实践:RDB + AOF 混合模式,兼具两者优点
bash
# redis.conf
appendonly yes # 开启 AOF
aof-use-rdb-preamble yes # 混合持久化(RDB + AOF)
工作机制:
1.AOF 重写 时:先RDB 快照写入文件头,再追加增量命令
2.恢复时:先加载 RDB(快),再重放增量命令(补全)
文件结构:
bash
appendonly.aof
├── RDB 二进制头部(全量数据)
└── AOF 命令尾部(增量数据)
优势:
✅ 恢复速度接近 RDB
✅ 数据安全性接近 AOF(最多丢 1 秒)
✅ 文件体积适中
六、生产环境配置建议
方案 1:数据安全优先
bash
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes # 混合持久化
# RDB 作为备份
save 60 10000
方案 2:性能优先(允许少量数据丢失)
bash
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
# 关闭 RDB(减少 fork 开销)
save ""
方案 3:全量备份(每日 RDB)
bash
# AOF 保证日常安全
appendonly yes
appendfsync everysec
# RDB 每日凌晨备份
save 3600 1 # 1 小时 1 次修改即触发(实际通过 cron 控制)
# 备份脚本
0 2 * * * redis-cli BGSAVE && cp dump.rdb /backup/redis-$(date +%Y%m%d).rdb
七、总结
Redis 定位是"内存数据库 + 缓存 + 数据结构服务器 ",优势在于极致性能和丰富数据结构。RDB 是快但可能丢数据的快照,AOF 是慢但更安全的日志,混合持久化是生产环境最优解。选择哪种模式,取决于你对"性能 "和"数据安全"的权衡