Redis 双刃剑:缓存与分布式锁的本质区别
2026/8/18 10:44:14 网站建设 项目流程

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询