Redis 双刃剑:缓存与分布式锁的本质区别

Redis 是后端开发中最常用的中间件之一,但很多开发者对它的两个核心用途 ------ 缓存和分布式锁 ------ 经常混淆。本文从问题本质出发,清晰拆解二者的定义、原理、适用场景和协作方式,帮你建立准确的认知。

一、它们解决的是完全不同的问题

在多用户、多服务器的后端系统中,有两类高频问题:

问题一:读太慢 同一个商品详情页每秒被 1000 个用户访问,每次都去磁盘数据库查询,数据库扛不住,接口越来越慢。

问题二:写冲突 两个用户同时下单抢最后一件商品,两个请求同时查到库存为 1,都扣减成功,库存变成 -1,超卖了。

Redis 缓存解决第一个问题 ------ 加速读。 Redis 分布式锁解决第二个问题 ------ 保护写。

读太慢 ──▶ Redis 缓存 ──▶ 热点数据存内存,下次直接读内存 写冲突 ──▶ Redis 分布式锁 ──▶ 全局互斥,同一时刻只允许一个人写

二、Redis 缓存:加速读取

2.1 核心思想

把高频查询的数据从慢速磁盘数据库复制一份到快速内存中。下次查询时先查内存,命中就直接返回,不再访问数据库。

2.2 工作流程

复制代码
客户端:查询商品 1001 的详情
    │
    ▼
┌──────────────┐
│ 查 Redis 缓存 │
└──────┬───────┘
       │
  ┌────┴────┐
  │  命中?  │
  └────┬────┘
   是  │  否
   ▼   ▼
直接返回  查磁盘数据库
内存数据      │
              ▼
       结果写入 Redis 缓存
       (设置过期时间)
              │
              ▼
         返回数据

2.3 核心特征

特征 说明
操作类型 读操作
数据流向 数据库 → Redis → 客户端
失效方式 过期时间自动失效,或手动删除
一致性 允许短暂不一致(缓存和数据库之间有时间窗口)
性能收益 微秒级响应,比磁盘数据库快 50~5000 倍

2.4 适用场景

  • 商品详情、系统配置、字典数据等读多写少的数据
  • 复杂报表统计、多表 JOIN 汇总等耗时计算结果
  • 会话、Token、验证码等短期临时数据
  • 多实例服务器共享同一份查询结果

三、Redis 分布式锁:保护写入

3.1 核心思想

在 Redis 中创建一个 key 作为 "锁"。谁创建成功,谁就获得操作权;其他请求看到 key 已存在,就知道有人在操作,必须等待。操作完成后删除 key,释放锁。

3.2 工作流程

复制代码
请求 A                           请求 B
   │                               │
   ▼                               ▼
SET lock:order  NX EX 30     SET lock:order  NX EX 30
   │                               │
   ▼ 成功(key 不存在,创建成功)     ▼ 失败(key 已存在,被 A 持有)
获得锁                              等待 / 重试
   │
   ▼
执行业务逻辑(扣减库存、写入订单)
   │
   ▼
DELETE lock:order(释放锁)
   │                               │
   ▼                               ▼
                                 SET lock:order  NX EX 30
                                    │
                                    ▼ 成功
                                 获得锁,执行业务逻辑

3.3 关键命令解析

复制代码
SET lock:order:1001 unique_id NX EX 30
参数 含义
lock:order:1001 锁的 key,标识被锁定的资源
unique_id 客户端唯一标识,防止释放别人的锁
NX 只在 key 不存在时才创建,保证互斥
EX 30 设置 30 秒过期,防止进程崩溃后锁永远不释放

3.4 核心特征

特征 说明
操作类型 写操作
数据流向 客户端 → 抢锁 → 写数据库 → 释放锁
失效方式 操作完成后主动释放,或超时自动过期
一致性 强互斥,同一时刻只有一个操作者
性能收益 不加速单次操作,但防止并发写入导致数据错乱

3.5 适用场景

  • 库存扣减、余额变更等高争抢资源的写操作
  • 订单创建,防止重复下单
  • 定时任务防重复执行
  • 分布式系统中多节点写同一份数据

四、完整对比

维度 Redis 缓存 Redis 分布式锁
解决的问题 读太慢、数据库压力大 并发写冲突、脏数据
作用对象 读操作 写操作
核心原理 热点数据存内存,加速读取 全局互斥,同一时刻只有一个写者
数据流向 数据库 → Redis → 客户端 客户端 → 抢锁 → 写库 → 释放
失效方式 过期时间自动失效 主动释放 / 超时自动过期
一致性要求 允许短暂不一致 强互斥,不允许并发
性能影响 直接提升读取速度 不提升速度,防止数据错乱
存储内容 业务数据本身 只存一个锁标识(key)
典型命令 GET / SET / SETEX SET NX EX / DELETE

五、它们如何协作

在真实系统中,缓存和分布式锁经常配合使用,各司其职:

复制代码
┌─────────────────────────────────────────────────────────┐
│                    读请求                                 │
│  客户端查询 ──▶ 查 Redis 缓存 ──▶ 命中直接返回              │
│                              ──▶ 未命中查数据库 → 回填缓存  │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                    写请求                                 │
│  客户端下单 ──▶ 获取 Redis 分布式锁                        │
│              ──▶ 成功:执行写操作 → 更新数据库 → 删缓存     │
│                         → 释放锁                         │
│              ──▶ 失败:等待重试或返回"系统繁忙"            │
└─────────────────────────────────────────────────────────┘

关键细节:写操作完成后,除了更新数据库,还要删除对应的缓存。否则下次读请求会读到缓存中的旧数据。

写操作的完整流程

复制代码
async def update_product(product_id, new_data):
    lock_key = f"lock:product:{product_id}"

    # 1. 获取分布式锁
    if not await redis.set(lock_key, "1", nx=True, ex=30):
        raise Exception("系统繁忙,请稍后重试")

    try:
        # 2. 更新数据库
        await db.execute(
            update(Product).where(Product.id == product_id).values(**new_data)
        )

        # 3. 删除缓存(下次读取时会从数据库重新加载并回填)
        await redis.delete(f"cache:product:{product_id}")

    finally:
        # 4. 释放锁
        await redis.delete(lock_key)

六、常见误区

误区一:把缓存当锁用

复制代码
# ❌ 错误:用缓存 key 判断是否有人在操作
if await redis.get("processing:order:1001"):
    return "有人在处理"
await redis.set("processing:order:1001", "1")  # 有时间窗口,可能两个人都通过

缓存的 GET + SET 不是原子操作,在并发场景下有时间窗口漏洞。分布式锁用的是 SET NX,是原子操作,天然互斥。

误区二:把锁当缓存用

复制代码
# ❌ 错误:把业务数据存在锁的 key 里
await redis.set("lock:product:1001", product_data, ex=30)

锁的 key 会在操作完成后被删除,数据会丢失。业务数据应该存在独立的缓存 key 中。

误区三:忘记释放锁

复制代码
# ❌ 危险:异常时不释放锁,其他请求永远等待
await redis.set(lock_key, "1", nx=True, ex=30)
await do_something()  # 如果这里抛异常,锁不会被释放
await redis.delete(lock_key)

# ✅ 正确:用 try-finally 保证释放
await redis.set(lock_key, "1", nx=True, ex=30)
try:
    await do_something()
finally:
    await redis.delete(lock_key)  # 无论成功失败都释放

七、总结

概念 一句话定义
Redis 缓存 把数据存到 Redis 里,下次读的时候直接从内存取,加速读
Redis 分布式锁 在 Redis 里占一个坑,别人看到坑被占了就等着,保护写

选型决策:

  • 你的问题是读太慢? ──▶ 用 Redis 缓存
  • 你的问题是写冲突? ──▶ 用 Redis 分布式锁
  • 两个问题都有? ──▶ 两个都用,缓存加速读,锁保护写

二者功能独立、互不替代,但在同一个系统中经常搭配使用。理解它们各自解决的问题,才能在架构设计中做出正确的选择。

相关推荐
Escalating_xu1 小时前
【Linux】线程概念与控制:从地址空间、LWP 到 pthread_create、join、detach 与 NPTL
java·linux·redis
InfinitePlus2 小时前
docker redis部署
redis·docker·容器
ltl11 小时前
3D 并行深度:数据 / 张量 / 流水 / 序列 / ZeRO
分布式
ltl11 小时前
Redis 紧凑编码:listpack、quicklist 与 intset
redis
FserSuN19 小时前
回顾Spark的概念与应用
大数据·分布式·spark
野生技术架构师1 天前
Redis 和 MySQL 如何保证数据一致性?先更新数据库还是先删缓存,延迟双删、MQ、Canal 一次讲透
数据库·redis·缓存
Devin~Y1 天前
从内容社区到AI智能客服:Spring Boot + Spring Cloud + Spring AI 全栈实战面试拆解
java·spring boot·redis·elasticsearch·spring cloud·kafka·mybatis
2602_959960921 天前
电商大厂Java面试:从Spring Boot、JPA、微服务到Redis、Kafka、Spring Security与监控,谢飞机的爆笑三轮问答
java·jvm·spring boot·redis·面试题
愈努力俞幸运1 天前
token,context window,top_p,temperature,skill,Harness Engineering
数据库·redis·缓存