分布式锁实战方案,你一定听得懂吗?

摘要:在分布式系统开发中,并发问题是绕不开的坎——当多个服务实例同时操作同一资源(比如库存扣减、订单创建)时,若没有有效的同步机制,很容易出现数据不一致、超卖等严重问题。分布式锁就是解决这类跨服务并发冲突的核心方案,而 Redis 凭借高性能、高
在分布式系统开发中,并发问题是绕不开的坎——当多个服务实例同时操作同一资源(比如库存扣减、订单创建)时,若没有有效的同步机制,很容易出现数据不一致、超卖等严重问题。分布式锁就是解决这类跨服务并发冲突的核心方案,而 Redis 凭借高性能、高可用的特性,成为实现分布式锁的首选中间件。之前在开发电商库存系统时,就因初期实现的 Redis 锁存在漏洞,导致过少量超卖问题,后续经过多次优化才稳定落地。今天就结合实际开发经验,聊聊 Redis 分布式锁的实现原理、核心要点、常见坑点及最优实践,全是经过生产验证的干货。 先明确一个前提:分布式锁的核心需求的是互斥性(同一时间只有一个服务实例能持有锁),在此基础上,还需满足高可用、防死锁、可重入等特性。Redis 实现分布式锁的核心思路的是利用其原子操作,通过键值对的存删来标记锁的占用与释放,看似简单,实则暗藏诸多细节。 一、基础实现:从 Redis 原子操作说起 Redis 实现分布式锁的核心依赖SETNX 命令(SET if Not Exists),即当指定 key 不存在时才设置值,否则不执行操作,该命令是原子性的,能保证并发场景下的互斥性。我们先从最基础的实现入手,再逐步优化漏洞。 1. 基础加锁逻辑 加锁时,我们以业务标识作为 key(比如库存锁用 “lock:stock:1001”,1001 为商品 ID),以随机字符串作为 value(后续释放锁时需验证该 value,避免误删他人持有的锁),同时设置过期时间,防止服务宕机导致锁无法释放而引发死锁。 早期 Redis 版本中,加锁需分两步执行(SETNX + EXPIRE),但这两步并非原子操作,存在并发漏洞(比如执行完 SETNX 后服务宕机,EXPIRE 未执行,锁永久有效)。在 Redis 2.6.12 及以上版本,支持 SET 命令多参数合并,可一次性完成加锁、设值、设过期时间,保证原子性: // 加锁核心命令(Redis CLI) SET lock:stock:1001 random-value NX PX 30000 // 说明: // NX:仅当 key 不存在时才设置,保证互斥性 // PX:设置过期时间(毫秒),这里设为 30 秒,避免死锁 // random-value:随机字符串,需保证全局唯一,用于释放锁时身份校验 在 Java 中(以 Spring Data Redis 为例),加锁代码如下: public boolean lock(String key, String value, long expireMs) { Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireMs, TimeUnit.MILLISECONDS); return Boolean.TRUE.equals(result); } 2. 基础释放锁逻辑 释放锁时,不能直接执行 DEL 命令删除 key,否则会出现“误删锁”问题——比如服务 A 持有锁后因业务耗时过长,锁已过期自动释放,此时服务 B 成功加锁,若服务 A 执行完业务后直接 DEL 锁,会误删掉服务 B 持有的锁。 因此,释放锁需分三步:1. 读取锁的 value;2. 验证 value 是否与自身持有一致;3. 一致则删除 key 释放锁。
阅读全文