Appearance
Redis 入门指南
Redis 数据结构
5 种基本数据类型
┌─────────────────────────────────────────────────────────────────┐
│ Redis 数据类型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ String - 字符串 SDS (Simple Dynamic String) │
│ Hash - 哈希表 Dict (Hash Table) │
│ List - 列表 QuickList (压缩列表+双向链表) │
│ Set - 集合 IntSet + Hash Table │
│ Sorted Set - 有序集合 ZipList + SkipList │
│ │
│ 高级类型: │
│ Bitmap - 位图 String 类型 │
│ HyperLogLog - 基数统计 String 类型 │
│ Geo - 地理坐标 Sorted Set │
│ Stream - 流数据 Radix Tree │
│ │
└─────────────────────────────────────────────────────────────────┘常用命令
bash
# String
SET key value
GET key
MSET key1 v1 key2 v2
SETNX key value # 不存在则设置
INCR key # 原子递增
DECR key # 原子递减
EXPIRE key 10 # 10秒过期
SETEX key 10 value # 设置值并指定过期时间
# Hash
HSET user:1 name "张三"
HGET user:1 name
HGETALL user:1
HDEL user:1 name
HINCRBY user:1 age 1
# List
LPUSH list a b c # 左边入栈
RPUSH list d e # 右边入栈
LPOP list # 左边出栈
RPOP list # 右边出栈
LRANGE list 0 -1 # 获取所有元素
LLEN list # 列表长度
# Set
SADD tags java python
SMEMBERS tags # 获取所有成员
SISMEMBER tags java # 是否存在
SINTER tag1 tag2 # 交集
SUNION tag1 tag2 # 并集
# Sorted Set
ZADD leaderboard 100 "张三"
ZADD leaderboard 200 "李四"
ZRANGE leaderboard 0 -1 # 按分数升序
ZREVRANGE leaderboard 0 9 # 按分数降序前10
ZSCORE leaderboard "张三" # 获取分数💡 实战场景:
- String:缓存用户信息、Token、计数器
- Hash:存储对象(如用户资料)
- List:消息队列、排行榜
- Set:标签系统、好友关系
- ZSet:有序排行榜、延迟队列
Redis 持久化
RDB(Redis Database)
┌─────────────────────────────────────────────────────────────────┐
│ RDB 持久化 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 原理:定时生成数据快照 │
│ │
│ 触发方式: │
│ 1. 定时任务(redis.conf 配置) │
│ 2. BGSAVE 命令 │
│ 3. SHUTDOWN 时 │
│ 4. FLUSHDB + AOF 关闭时 │
│ │
│ 优点:恢复速度快,适合备份 │
│ 缺点:可能丢失最后一次快照后的数据 │
│ │
└─────────────────────────────────────────────────────────────────┘AOF(Append Only File)
┌─────────────────────────────────────────────────────────────────┐
│ AOF 持久化 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 原理:记录每个写命令 │
│ │
│ 同步策略: │
│ - always 每条命令同步 │
│ - everysec 每秒同步(推荐) │
│ - no 由系统同步 │
│ │
│ 重写机制: │
│ BGREWRITEAOF - 压缩 AOF 文件 │
│ │
│ 优点:数据安全性高 │
│ 缺点:文件体积大,恢复速度慢 │
│ │
└─────────────────────────────────────────────────────────────────┘面试点:⭐⭐⭐⭐ 实战重要性:⭐⭐⭐⭐
Redis 过期策略
三种过期策略
bash
# 1. 定时删除(定时扫描)
# 设置过期时间时创建定时器,到期删除
# 优点:内存友好
# 缺点:CPU 压力大
# 2. 惰性删除(访问时检查)
# 访问 key 时检查是否过期
# 优点:CPU 友好
# 缺点:内存可能泄漏
# 3. 定期删除(折中方案)
# 周期性扫描,定期检查
# Redis 默认使用惰性删除 + 定期删除内存淘汰策略
bash
# 当内存达到 maxmemory 时
maxmemory-policy volatile-lru
# 策略选项:
# - noeviction 不淘汰,返回错误
# - allkeys-lru 所有 key LRU 淘汰
# - volatile-lru 有过期时间的 key LRU 淘汰
# - allkeys-random 所有 key 随机淘汰
# - volatile-random 有过期时间的 key 随机淘汰
# - volatile-ttl 有过期时间的 key 优先淘汰 TTL 短的Redis 集群
主从复制
┌─────────────────────────────────────────────────────────────────┐
│ 主从复制架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ Master │ │
│ └────┬─────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Slave 1 │ │Slave 2 │ │Slave 3 │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ 主节点:写 + 读 │
│ 从节点:读(分担压力) │
│ │
└─────────────────────────────────────────────────────────────────┘哨兵模式
bash
# 哨兵监控主从节点
# 自动故障转移
# 客户端连接哨兵获取主节点地址
# 配置文件:sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000Cluster 模式
┌─────────────────────────────────────────────────────────────────┐
│ Redis Cluster 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 16384 个槽位分片 │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ 0-5460 │ │5461-10922│10923-16383│ │
│ └────┬───┘ └────┬───┘ └────┬───┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Master 1│ │Master 2│ │Master 3│ │
│ └────────┘ └────────┘ └────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Slave 1 │ │ Slave 2 │ │ Slave 3 │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘面试点:⭐⭐⭐⭐
分布式锁
Redis 分布式锁
java
// 加锁
public boolean lock(String key, String value, long expireTime) {
String result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
// 解锁(Lua 脚本保证原子性)
public boolean unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 end";
return redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
) == 1L;
}Redisson 实现
java
// Redisson 分布式锁
RLock lock = redissonClient.getLock("order:lock:" + orderId);
try {
// 等待锁30秒,锁定后自动60秒释放
if (lock.tryLock(30, 60, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.unlock();
}面试点:⭐⭐⭐⭐⭐ 实战重要性:⭐⭐⭐⭐⭐
缓存问题
缓存穿透
💡 实战场景:黑客恶意请求不存在的数据,每次都打到数据库
┌─────────────────────────────────────────────────────────────────┐
│ 缓存穿透示意 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 请求 ──▶ Redis ──▶ DB ──▶ 返回 NULL │
│ ↑ ↑ │
│ │ │ │
│ 缓存不存在 数据库也不存在 │
│ │ │ │
│ ←─────────┘ │
│ 每次请求都打满数据库 │
│ │
└─────────────────────────────────────────────────────────────────┘解决方案:
java
// 解决方案1:布隆过滤器
BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1000000, // 预期元素数量
0.01 // 误判率
);
// 查询前先检查布隆过滤器
public User getUser(Long id) {
String key = "user:" + id;
// 布隆过滤器判断
if (!bloomFilter.mightContain(key)) {
return null; // 一定不存在
}
// 缓存查询
User user = redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 数据库查询
user = userDao.findById(id);
if (user != null) {
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
bloomFilter.put(key);
}
return user;
}java
// 解决方案2:缓存空值
if (user == null) {
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
} else {
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
}缓存击穿
💡 实战场景:热点 key 过期,瞬间大量请求同时打满数据库
┌─────────────────────────────────────────────────────────────────┐
│ 缓存击穿示意 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 热点 key 过期 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 瞬间大量请求同时发现缓存不存在 │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ DB │
│ │
└─────────────────────────────────────────────────────────────────┘解决方案:
java
// 解决方案:互斥锁
public User getUserMutex(Long id) {
String key = "user:" + id;
User user = redisTemplate.opsForValue().get(key);
if (user == null) {
String lockKey = "lock:" + key;
String lockValue = UUID.randomUUID().toString();
if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS)) {
try {
user = userDao.findById(id);
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 其他线程等待后重试
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
return getUserMutex(id);
}
}
return user;
}缓存雪崩
💡 实战场景:大量缓存同时过期,导致数据库压力过大
┌─────────────────────────────────────────────────────────────────┐
│ 缓存雪崩示意 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 大量 key 同时过期 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 大量请求同时发现缓存过期 │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ DB │
│ │
└─────────────────────────────────────────────────────────────────┘解决方案:
java
// 解决方案1:过期时间加随机值
redisTemplate.opsForValue().set(key, value, 1 + random.nextInt(5), TimeUnit.HOURS);
// 解决方案2:热点数据永不过期 + 异步更新
// 解决方案3:Redis Cluster 高可用面试点:⭐⭐⭐⭐⭐ 实战重要性:⭐⭐⭐⭐⭐
总结
Redis 核心知识点:
| 知识点 | 面试频率 | 实战重要性 |
|---|---|---|
| 5 种数据结构 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 持久化 RDB/AOF | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 过期策略 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 内存淘汰 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 主从复制 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 哨兵/Cluster | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 分布式锁 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 缓存穿透/击穿/雪崩 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
⚠️ 易错点提醒:
- Redis 单线程是指处理命令单线程,但持久化是后台线程
- 分布式锁要使用 Lua 脚本保证原子性
- 缓存穿透、击穿、雪崩是不同的概念和解决方案
- 布隆过滤器有误判率,但不会漏判
- String 是最常用的类型,但 Hash 存储对象更节省内存