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 分布式锁
  • 两个问题都有? ──▶ 两个都用,缓存加速读,锁保护写

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

相关推荐
醉颜凉2 小时前
Redis网络层解析:高性能服务器架构设计与实现
redis·epoll·网络架构·i/o多路复用·事件驱动
醉颜凉3 小时前
Redis 与 Pika 对比:大容量冷热数据落盘方案与混合存储架构
redis·pika·混合存储·存储架构·冷热数据分离
刃神太酷啦6 小时前
Redis 进阶核心:持久化 (RDB/AOF)、事务与主从复制全解析----《Hello Redis!》(5)
linux·c语言·数据库·c++·redis·缓存·bootstrap
三8446 小时前
SSRF 打内网 Redis:gopher 原理与实操
redis·web安全·bootstrap·ssrf
youm20036 小时前
认识Redis
redis·笔记·学习
CDN3609 小时前
CDN 缓存命中率优化实战:解决出海业务回源过多、跨洋延迟高,缓存调优完整指南
缓存·cdn加速
互联网叫兽9 小时前
redis深入学习二
数据库·redis·学习
咖啡星人k10 小时前
2026 上下文缓存进阶:把前缀契约写进SPEC,MonkeyCode 云端跑通
人工智能·机器学习·缓存
hweiyu0010 小时前
Redis命令:TTL
redis·缓存
深蓝电商API12 小时前
Redis 在分布式爬虫中的作用
redis·分布式·爬虫