ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

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 / SETEXSET NX EX / DELETE五、它们如何协作在真实系统中缓存和分布式锁经常配合使用各司其职┌─────────────────────────────────────────────────────────┐ │ 读请求 │ │ 客户端查询 ──▶ 查 Redis 缓存 ──▶ 命中直接返回 │ │ ──▶ 未命中查数据库 → 回填缓存 │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ 写请求 │ │ 客户端下单 ──▶ 获取 Redis 分布式锁 │ │ ──▶ 成功执行写操作 → 更新数据库 → 删缓存 │ │ → 释放锁 │ │ ──▶ 失败等待重试或返回系统繁忙 │ └─────────────────────────────────────────────────────────┘关键细节写操作完成后除了更新数据库还要删除对应的缓存。否则下次读请求会读到缓存中的旧数据。写操作的完整流程async def update_product(product_id, new_data): lock_key flock:product:{product_id} # 1. 获取分布式锁 if not await redis.set(lock_key, 1, nxTrue, ex30): raise Exception(系统繁忙请稍后重试) try: # 2. 更新数据库 await db.execute( update(Product).where(Product.id product_id).values(**new_data) ) # 3. 删除缓存下次读取时会从数据库重新加载并回填 await redis.delete(fcache: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, ex30)锁的 key 会在操作完成后被删除数据会丢失。业务数据应该存在独立的缓存 key 中。误区三忘记释放锁# ❌ 危险异常时不释放锁其他请求永远等待 await redis.set(lock_key, 1, nxTrue, ex30) await do_something() # 如果这里抛异常锁不会被释放 await redis.delete(lock_key) # ✅ 正确用 try-finally 保证释放 await redis.set(lock_key, 1, nxTrue, ex30) try: await do_something() finally: await redis.delete(lock_key) # 无论成功失败都释放七、总结概念一句话定义Redis 缓存把数据存到 Redis 里下次读的时候直接从内存取加速读Redis 分布式锁在 Redis 里占一个坑别人看到坑被占了就等着保护写选型决策你的问题是读太慢 ──▶ 用 Redis 缓存你的问题是写冲突 ──▶ 用 Redis 分布式锁两个问题都有 ──▶ 两个都用缓存加速读锁保护写二者功能独立、互不替代但在同一个系统中经常搭配使用。理解它们各自解决的问题才能在架构设计中做出正确的选择。
返回列表