Redis实战指南

Redis 实战指南:缓存、分布式锁、持久化

Redis 是 CSDN 上后端技术流量最高的主题之一,也是大厂面试必考。本文从数据结构讲起,覆盖缓存三大问题(穿透/击穿/雪崩)、分布式锁、持久化、集群、性能调优,全部带可运行代码。


一、Redis 是什么

Redis 是内存数据库------数据存在内存里,读写纳秒级。相比 MySQL(磁盘),它快几个数量级,代价是数据不是主要存储。

用途:

  • 缓存(加速读);
  • 分布式锁(并发控制);
  • 计数器(点赞、库存);
  • 消息队列(List/Stream);
  • 排行榜(ZSet);
  • 会话共享(Session 存 Redis)。

核心特点:

  • 单线程执行命令(6.0 后多线程处理网络 IO,但命令执行仍单线程)------所以命令都是原子的;
  • 丰富的数据结构;
  • 支持持久化(RDB/AOF);
  • 支持主从、哨兵、集群。

二、五种基本数据结构

2.1 String

最常用。存 JSON、计数、分布式锁。

bash 复制代码
SET user:1001 '{"name":"张三","age":25}'
GET user:1001
INCR visit:count          # 自增
SETNX lock:order 1        # 不存在才设置(分布式锁用)
EXPIRE user:1001 3600     # 设置过期

2.2 Hash

适合存对象字段,可单独更新某个字段。

bash 复制代码
HSET user:1001 name 张三 age 25
HGET user:1001 name
HINCRBY user:1001 score 10

为什么比 String 好:对象场景下,Hash 只改一个字段不用整体反序列化,内存占用也低。

2.3 List

双向链表。消息队列、时间线。

bash 复制代码
LPUSH news:tech "Spring Boot 3 发布"
LRANGE news:tech 0 -1
LPOP news:tech

2.4 Set

去重、集合运算。关注关系、标签。

bash 复制代码
SADD user:1001:following 2001 2002
SISMEMBER user:1001:following 2001    # 是否关注
SINTER user:1001:following user:1002:following   # 共同关注

2.5 ZSet(有序集合)

带分数的集合。排行榜、延迟队列。

bash 复制代码
ZADD rank:game 1000 "playerA"
ZADD rank:game 800  "playerB"
ZRANGE rank:game 0 -1 WITHSCORES   # 升序
ZREVRANGE rank:game 0 -1            # 降序(排行榜)
ZINCRBY rank:game 100 "playerA"     # 加分数

三、缓存三大经典问题

这是面试必问、生产必踩的三座山。

3.1 缓存穿透

现象 :查一个不存在的 key------缓存没有,数据库也没有。每次请求都打到数据库。

危害:恶意请求用不存在的 id 刷接口,数据库直接被打挂。

解决:

  1. 缓存空值:查询结果为空也缓存,过期时间设短(如 5 分钟);
  2. 布隆过滤器:请求先过 Bloom Filter,不存在的 key 直接拦截(不用查库)。
java 复制代码
// 方案一:缓存空值
public User getUser(Long id) {
    String key = "user:" + id;
    User user = redis.get(key);
    if (user != null) return user;

    user = userMapper.selectById(id);
    if (user == null) {
        redis.set(key, "", 300);  // 缓存空值 5 分钟
        return null;
    }
    redis.set(key, user, 3600);
    return user;
}

3.2 缓存击穿

现象 :某个热点 key 过期的瞬间,大量请求同时打到数据库。

区别:穿透是"查不存在",击穿是"查一个存在但缓存刚好过期"。

解决:

  1. 互斥锁:只有一个线程去查库,其他线程等待;
  2. 逻辑过期:key 永不过期,value 里带过期时间,异步刷新。
java 复制代码
// 方案:互斥锁
public User getUser(Long id) {
    User user = redis.get("user:" + id);
    if (user != null) return user;

    String lockKey = "lock:user:" + id;
    boolean locked = redis.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
    if (!locked) {
        Thread.sleep(50);
        return getUser(id);  // 递归等待
    }
    try {
        user = userMapper.selectById(id);  // 只有一个线程进来
        redis.set("user:" + id, user, 3600);
        return user;
    } finally {
        redis.delete(lockKey);
    }
}

3.3 缓存雪崩

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

解决:

  1. 过期时间加随机值 :set key val (3600 + random(0,600)),避免同时过期;
  2. 多级缓存:本地缓存(Caffeine)+ Redis;
  3. Redis 高可用:主从 + 哨兵;
  4. 限流降级:数据库扛不住时直接降级。

三者对比:

问题 场景 核心解决
穿透 查不存在的 key 空值缓存 / 布隆过滤器
击穿 热点 key 过期瞬间 互斥锁 / 逻辑过期
雪崩 大量 key 同时过期 / Redis 宕机 随机过期 / 高可用 / 多级缓存

四、分布式锁

4.1 为什么需要分布式锁

单体时代用 synchronized,多实例部署后(两个 JVM 各持一把锁)就不管用了------需要跨进程的锁。

场景:防止重复下单、库存扣减、定时任务重复执行。

4.2 基于 Redis 的分布式锁

版本一:简单版(不推荐生产用)

java 复制代码
// 加锁
Boolean ok = redis.setIfAbsent("lock:order:1001", "1", 30, TimeUnit.SECONDS);
if (ok) {
    try {
        doBiz();
    } finally {
        redis.delete("lock:order:1001");
    }
}

问题:

  • 误删锁:线程 A 的锁过期了,线程 B 拿到锁,A 执行完把 B 的锁删了------要校验 value 是自己的;
  • 过期时间太短:业务没执行完锁就过期了。

版本二:校验 value + 续期

java 复制代码
String token = UUID.randomUUID().toString();
Boolean ok = redis.setIfAbsent(lockKey, token, 30, TimeUnit.SECONDS);
if (ok) {
    // 看门狗续期(Redisson 自动做)
    try {
        doBiz();
    } finally {
        // 只删自己的锁
        if (token.equals(redis.get(lockKey))) {
            redis.delete(lockKey);
        }
    }
}

生产建议 :直接用 Redisson,它实现了看门狗自动续期、可重入锁、公平锁,别自己造轮子:

java 复制代码
RLock lock = redisson.getLock("lock:order:" + orderId);
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
    try {
        doBiz();
    } finally {
        lock.unlock();
    }
}

4.3 Redlock 与主从问题

Redis 主从切换时,锁可能丢(主挂了,从没同步到锁)。极端场景用 Redlock(多节点加锁),但复杂且有争议。大多数业务场景:单节点 Redis + 哨兵 + 合理过期时间就够,别过度设计。


五、持久化

Redis 数据在内存,重启会丢------持久化解决这个问题。

5.1 RDB(快照)

定时把内存数据 dump 到磁盘。

conf 复制代码
# redis.conf
save 900 1        # 900秒内1次写就触发
save 300 10
save 60 10000

优点 :文件小、恢复快、适合备份。

缺点:可能丢最后一次快照后的数据。

5.2 AOF(追加日志)

每次写命令追加到日志文件。

conf 复制代码
appendonly yes
appendfsync everysec    # 每秒刷盘(推荐)

优点 :最多丢 1 秒数据。

缺点:文件大、恢复慢(可 AOF rewrite 压缩)。

5.3 怎么选

  • 能丢 1 分钟数据:RDB 够;
  • 不能丢数据(订单、支付):AOF everysec;
  • 生产标配:RDB + AOF 都开。

六、高可用架构

6.1 主从复制

一个主节点写,多个从节点读(读写分离)。

bash 复制代码
# 从节点配置
replicaof 192.168.1.10 6379
  • 主节点写命令同步到从节点;
  • 读压力大时,读请求走从节点;
  • 缺点:主挂了要手动切换(用哨兵解决)。

6.2 哨兵(Sentinel)

监控主节点,挂了自动选新主:

复制代码
Sentinel 集群(3 个以上)
   ↓ 监控
Redis 主节点 ←→ 从节点1 ←→ 从节点2
bash 复制代码
redis-sentinel sentinel.conf

作用:故障自动切换、通知客户端新主地址。

6.3 Cluster 集群

数据量大 + 写量大,用 Cluster 分片:

  • 16384 个 hash slot,key 按 CRC16(key) % 16384 分配到节点;
  • 每个节点管一部分 slot;
  • 支持自动扩容/缩容。

选型建议:

规模 方案
单机 < 10G 主从 + 哨兵
读写分离 主从
数据量大/写量大 Cluster
云厂商 直接用云 Redis(自带高可用)

七、Redis 性能调优

7.1 慢查询

bash 复制代码
SLOWLOG GET 10          # 看最近慢命令
config set slowlog-log-slower-than 10000   # 阈值 10ms

常见慢命令 :KEYS *(全量扫)、大 key 的 HGETALL、SMEMBERS。

禁止 KEYS :生产环境 KEYS * 会阻塞 Redis 几秒。用 SCAN 游标式遍历。

7.2 大 Key 问题

单个 key 值超过 10KB 或集合元素超过 1 万个叫大 key:

  • 读写慢、阻塞;
  • 删除时卡顿。

处理 :拆分 key、压缩值、删除用 UNLINK(异步删除)。

7.3 热点 Key

某个 key 被超高并发访问,单节点扛不住:

  • 本地缓存:JVM 里缓存热点数据,Redis 再挂也是走本地;
  • 读写分离:读走多个从节点;
  • key 分片 :key:1、key:2...分散到不同节点。

7.4 内存淘汰策略

conf 复制代码
maxmemory 4gb
maxmemory-policy allkeys-lru    # 淘汰最近最少使用

策略:

  • allkeys-lru:所有 key 按 LRU 淘汰(常用);
  • volatile-lru:只淘汰有过期时间的;
  • allkeys-lfu:按访问频率(热数据稳定场景)。

八、Spring Boot 集成 Redis

8.1 依赖与配置

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yaml 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379
      lettuce:
        pool:
          max-active: 20

8.2 封装工具类

java 复制代码
@Component
public class RedisUtil {

    @Autowired
    private StringRedisTemplate redis;

    public void set(String key, String value, long timeout) {
        redis.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    public String get(String key) {
        return redis.opsForValue().get(key);
    }

    public boolean setIfAbsent(String key, String value, long timeout) {
        return redis.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
    }

    public void delete(String key) {
        redis.delete(key);
    }
}

坑 :RedisTemplate 默认 JDK 序列化,存进去的 key 带乱码前缀。用 StringRedisTemplate 或配置 JSON 序列化器。


九、实战案例:商品详情缓存

java 复制代码
@Service
public class ProductService {

    // 商品详情:本地缓存 → Redis → 数据库
    public ProductDetail getDetail(Long productId) {
        // 1. 本地缓存
        ProductDetail local = localCache.getIfPresent(productId);
        if (local != null) return local;

        // 2. Redis(带逻辑过期防击穿)
        String key = "product:detail:" + productId;
        ProductDetail redisData = redis.get(key);
        if (redisData != null) {
            if (redisData.isExpired()) {
                // 逻辑过期:异步刷新,先返回旧数据
                asyncRefresh(productId, key);
            }
            localCache.put(productId, redisData);
            return redisData;
        }

        // 3. 互斥锁查库
        String lockKey = "lock:product:" + productId;
        boolean locked = redis.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
        if (!locked) {
            Thread.sleep(50);
            return getDetail(productId);
        }
        try {
            ProductDetail detail = productMapper.selectDetail(productId);
            if (detail == null) {
                redis.set(key, "", 300);  // 缓存空值防穿透
                return null;
            }
            redis.set(key, detail, 3600);
            localCache.put(productId, detail);
            return detail;
        } finally {
            redis.delete(lockKey);
        }
    }
}

这一套组合拳同时防了穿透、击穿、雪崩:空值缓存防穿透,互斥锁防击穿,随机过期+本地缓存防雪崩。


本章小结

Redis 看似简单(set/get),但生产级的难点全在"缓存一致性、高可用、性能边界"这些细节上。掌握三大问题 + 分布式锁 + 持久化 + 集群,就掌握了 Redis 的核心战场。

下一篇讲 Docker------部署的标准化,和 Redis 一样是 CSDN 热门。

(全文完)

相关推荐
frjc3 小时前
数据库选型:如何从众多数据库中选出最理想的那一个
redis·mysql·clickhouse·elasticsearch
lupai3 小时前
维修保养记录精准版 API 对接实战指南
数据库·python
panpan16195 小时前
Redis命令大全
redis
2601_952196366 小时前
计算机科学与技术专业应届生投商业分析岗,需要哪些额外能力?
数据库·oracle
皮皮学姐分享-ppx6 小时前
地级市、省级人才政策强度测算(2000-2025)
大数据·数据库·人工智能·百度·高考
晚安日记wanna6 小时前
大厂禁 JOIN 的真正原因,拆到第四层才清楚
数据库·后端·面试
java1234_小锋7 小时前
Redis 宣布正式接入 AI
数据库·人工智能·redis
行百里er7 小时前
Redis 持久化——RDB 快照 vs AOF 日志
redis·后端
这个DBA有点耶7 小时前
数据融合平台的下一代形态:数据库内核自己就能融合,为什么还要ETL?
数据库·架构·aigc