折腾侠
技术教程

Redis 缓存实战 - 从入门到高可用

缓存穿透、击穿、雪崩怎么办?本文全面讲解 Redis 缓存实战和高可用方案。

折腾侠
2026/03/15 发布
17约 6 分钟959 字 / 695 词00

Redis 缓存实战 - 从入门到高可用

缓存穿透、击穿、雪崩怎么办?本文全面讲解 Redis 缓存实战和高可用方案。

📋 前言

Redis 是现代架构的标配。

常见场景:

  • 缓存热点数据
  • 分布式锁
  • 会话存储
  • 消息队列
  • 排行榜

常见问题:

  • 缓存穿透、击穿、雪崩
  • 数据一致性
  • 内存溢出
  • 高可用方案

本文从基础到高可用,全面讲解 Redis 实战。


🎯 Redis 基础

数据类型

类型用途示例
String缓存、计数器用户信息、访问次数
List消息队列、最新列表消息队列、最新文章
Hash存储对象用户详情、商品详情
Set去重、共同好友标签、好友关系
ZSet排行榜热度排行、分数排行

常用命令

Bash
# String
SET key value
GET key
INCR key  # 自增
SETEX key seconds value  # 带过期时间

# Hash
HSET user:1 name "张三"
HGET user:1 name
HGETALL user:1

# List
LPUSH queue "message"
RPOP queue

# Set
SADD tags "java"
SMEMBERS tags

# ZSet
ZADD leaderboard 100 "user1"
ZREVRANGE leaderboard 0 9

⚡ 缓存三大问题

1. 缓存穿透

问题: 查询不存在的数据,缓存不命中,直接打到数据库。

解决方案:

Java
// 方案 1:缓存空值
public User getUser(String id) {
    User user = redis.get(id);
    if (user != null) {
        return user;
    }
    
    // 查询数据库
    user = db.query(id);
    if (user == null) {
        // 缓存空值,防止重复查询
        redis.setex(id, 60, null);
        return null;
    }
    
    redis.setex(id, 3600, user);
    return user;
}

// 方案 2:布隆过滤器
if (!bloomFilter.mightContain(id)) {
    return null;  // 肯定不存在
}

2. 缓存击穿

问题: 热点 key 过期瞬间,大量请求打到数据库。

解决方案:

Java
// 方案 1:互斥锁
public User getUser(String id) {
    User user = redis.get(id);
    if (user != null) {
        return user;
    }
    
    // 获取分布式锁
    if (tryLock(id)) {
        try {
            // 双重检查
            user = redis.get(id);
            if (user != null) {
                return user;
            }
            
            user = db.query(id);
            redis.setex(id, 3600, user);
        } finally {
            unlock(id);
        }
    } else {
        // 等待重试
        Thread.sleep(100);
        return getUser(id);
    }
}

// 方案 2:永不过期 + 异步更新
// 逻辑过期时间,后台线程异步更新

3. 缓存雪崩

问题: 大量 key 同时过期,或 Redis 宕机,请求全部打到数据库。

解决方案:

Java
// 方案 1:随机过期时间
int expireTime = 3600 + new Random().nextInt(600);
redis.setex(key, expireTime, value);

// 方案 2:多级缓存
// 本地缓存 + Redis + 数据库

// 方案 3:高可用架构
// 主从复制 + 哨兵 + 集群

🔄 数据一致性

缓存更新策略

方案 1:先删缓存,再更新数据库

Java
// ❌ 有问题
redis.delete(key);
db.update(data);

// 问题:更新数据库时,其他请求可能读到旧数据并写入缓存

方案 2:先更新数据库,再删缓存

Java
// ✅ 推荐
db.update(data);
redis.delete(key);

// 问题:极端情况下仍有不一致

方案 3:延迟双删

Java
// ✅ 更可靠
db.update(data);
redis.delete(key);
Thread.sleep(500);
redis.delete(key);

方案 4:监听 binlog 异步删除

Java
// ✅ 最终一致性
// Canal 监听 MySQL binlog
// 异步删除 Redis 缓存

🏗️ 高可用架构

1. 主从复制

Master (写)
  ↓ 复制
Slave (读)

特点:

  • 读写分离
  • 数据备份
  • 从机故障不影响
  • 主机故障需手动切换

2. 哨兵模式

Sentinel 1
Sentinel 2
Sentinel 3
    ↓ 监控
Master + Slaves

特点:

  • 自动故障转移
  • 监控告警
  • 至少 3 个哨兵
  • 推荐 1 主 2 从 3 哨兵

3. 集群模式

Node 1 (0-5460)
Node 2 (5461-10922)
Node 3 (10923-16383)

特点:

  • 数据分片
  • 高可用
  • 水平扩展
  • Redis 3.0+ 支持

💡 实战场景

场景 1:热点数据缓存

Java
@Cacheable(value = "user", key = "#id", 
           unless = "#result == null",
           cacheManager = "redisCacheManager")
public User getUser(String id) {
    return userRepository.findById(id);
}

场景 2:分布式锁

Java
// Redisson 实现
RLock lock = redisson.getLock("order:" + orderId);
if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
    try {
        // 业务逻辑
        createOrder(order);
    } finally {
        lock.unlock();
    }
}

场景 3:限流

Java
// 固定窗口
String key = "limit:" + userId + ":" + System.currentTimeMillis() / 60000;
long count = redis.incr(key);
if (count == 1) {
    redis.expire(key, 60);
}
if (count > 100) {
    throw new RateLimitException();
}

// 滑动窗口(推荐)
// 使用 ZSet 实现

场景 4:排行榜

Java
// 添加分数
redis.zadd("leaderboard", score, userId);

// 获取前 10 名
Set<String> top10 = redis.zrevrange("leaderboard", 0, 9);

// 获取用户排名
Long rank = redis.zrank("leaderboard", userId);

场景 5:消息队列

Java
// 生产者
redis.lpush("queue", message);

// 消费者
String message = redis.brpop(0, "queue");

// 发布订阅
redis.publish("channel", message);
redis.subscribe("channel");

⚠️ 常见问题

1. 内存溢出

原因:

  • 缓存太多数据
  • 没有设置过期时间
  • 大 key 问题

解决方案:

Bash
# 设置内存淘汰策略
CONFIG SET maxmemory-policy allkeys-lru

# 查找大 key
redis-cli --bigkeys

# 定期清理
UNLINK key  # 异步删除

2. 热 key 问题

识别:

Bash
# 监控命令
MONITOR
SLOWLOG GET 10

解决:

  • 本地缓存
  • 复制多份(热 key_1, 热 key_2)
  • 限流

3. 大 key 问题

识别:

Bash
redis-cli --bigkeys

解决:

  • 拆分大 key
  • 异步删除
  • 避免一次性操作

🎯 最佳实践

命名规范

Bash
# 格式:业务:类型:ID
user:info:123
order:detail:456
product:stock:789

# 使用冒号分隔
# 便于理解和监控

过期时间

Java
// 基础时间 + 随机值
int baseTime = 3600;
int randomTime = new Random().nextInt(600);
redis.setex(key, baseTime + randomTime, value);

监控告警

Bash
# 关键指标
- 内存使用率
- QPS
- 命中率
- 连接数
- 慢查询

# 工具
- Redis Monitor
- Prometheus + Grafana

🎁 总结

核心要点:

✅ 理解五大数据类型
✅ 掌握缓存三大问题解决方案
✅ 了解高可用架构
✅ 注意数据一致性
✅ 做好监控告警

架构选型:

小型项目:单机 + 持久化
中型项目:主从 + 哨兵
大型项目:集群 + 多活

最后建议:

缓存是银弹,但不是万能药。

理解场景,合理设计,持续优化。


你有什么 Redis 实战经验? 欢迎在评论区分享!👇

📚 参考资源

分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...