
在互联网软件开发中,随着微服务架构的普及,一个业务场景往往需要多个服务实例协同工作 —— 列如电商秒杀的库存扣减、分布式任务调度的任务分配、订单系统的 ID 生成等。这些场景的核心痛点的是共享资源竞争:当多个服务实例同时操作同一资源时,很容易出现数据不一致(如超卖、重复下单)、逻辑错乱(如任务重复执行)等问题。
单机系统中,我们可以通过synchronized(Java)、Lock接口或pthread_mutex(C++)等本地锁解决并发问题,但本地锁的作用范围仅限于单个 JVM 进程,无法跨节点控制。此时,分布式锁成为刚需 —— 它是一种跨服务、跨实例的互斥机制,核心目标是保证多个服务对共享资源的 “串行操作”,其可靠性直接决定了分布式系统的稳定性。
而 Redis 凭借高性能(单节点 QPS 可达 10 万 +)、低延迟(毫秒级响应)、高可用(支持主从、集群) 的特性,成为实现分布式锁的主流方案,据统计,80% 以上的中小规模分布式系统均采用 Redis 作为分布式锁的底层存储。
Redis 分布式锁的核心逻辑与关键特性
1. 核心实现思路
Redis 分布式锁的本质是 “用 Redis 键作为锁标识”,利用 Redis 的原子操作保证 “同一时间只有一个客户端能获取锁”。其核心逻辑可概括为:
- 获取锁:向 Redis 写入一个 “锁键”,只有当该键不存在时写入成功(表明获取锁成功);
- 释放锁:完成业务操作后,删除对应的 “锁键”(表明释放锁,其他客户端可竞争);
- 防死锁:为锁键设置过期时间,避免持有锁的客户端崩溃后锁永久占用。
2. 关键技术点:SET 命令的 “隐藏技能”
实现 Redis 分布式锁的核心是SET命令的两个参数,缺一不可:
- NX 参数(Not Exist):仅当锁键不存在时才写入成功,直接保证了 “互斥性”—— 同一时间只有一个客户端能创建锁键;
- PX 参数(毫秒级过期):为锁键设置自动过期时间(如 30 秒),解决 “死锁问题”—— 即使客户端崩溃,Redis 也会在过期后自动删除锁键。
标准获取锁命令示例:
# 锁键:lock:stock(业务标识,如库存锁);值:client123(客户端唯一标识)
# NX:键不存在才设置;PX 30000:30秒后自动过期
SET lock:stock client123 NX PX 30000
- 返回OK:获取锁成功;
- 返回nil:锁已被其他客户端持有,获取失败。
3. 分布式锁的四大核心要求
一个可靠的 Redis 分布式锁必须满足以下特性,否则会出现逻辑漏洞:
|
特性 |
说明 |
|
互斥性 |
同一时间只能有一个客户端持有锁,避免并发操作冲突 |
|
安全性 |
锁只能被持有锁的客户端释放,不能被其他客户端误删 |
|
防死锁 |
客户端崩溃或网络中断时,锁能自动释放(依赖 PX 参数或锁续命机制) |
|
高可用 |
获取 / 释放锁的操作高效,Redis 节点故障时锁机制仍能正常工作(依赖集群) |
从原生实现到 Redisson 进阶(Java 示例)
1. 原生 Redis 实现(理解原理)
需依赖 Jedis 客户端,核心步骤包括 “获取锁→执行任务→释放锁”,释放锁时需用 Lua 脚本保证原子性(避免误删其他客户端的锁)。
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class RedisLockNative {
// Redis连接信息
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
// 锁键(业务标识)
private static final String LOCK_KEY = "lock:stock";
// 锁过期时间(30秒)
private static final int LOCK_EXPIRE = 30000;
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
String clientId = UUID.randomUUID().toString(); // 客户端唯一标识
try {
// 1. 获取锁
Boolean locked = jedis.set(LOCK_KEY, clientId, "NX", "PX", LOCK_EXPIRE);
if (Boolean.TRUE.equals(locked)) {
System.out.println("获取锁成功,执行核心任务...");
// 2. 执行业务任务(如扣减库存)
executeStockDeduction(jedis);
} else {
System.out.println("获取锁失败,当前锁已被占用");
}
} finally {
// 3. 释放锁(Lua脚本保证原子性:先判断是否为自己的锁,再删除)
String releaseScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(releaseScript, 1, LOCK_KEY, clientId);
jedis.close();
}
}
// 模拟库存扣减任务
private static void executeStockDeduction(Jedis jedis) {
String stockKey = "stock:product123";
Long stock = jedis.decr(stockKey); // 原子扣减
if (stock >= 0) {
System.out.println("库存扣减成功,剩余库存:" + stock);
} else {
System.out.println("库存不足,扣减失败");
jedis.incr(stockKey); // 回滚库存
}
}
}
2. 生产环境推荐:Redisson 实现(简化开发)
原生实现需手动处理锁续命、集群兼容等问题,生产环境提议使用 Redisson 框架 —— 它封装了所有细节,支持可重入锁、公平锁、锁续命等高级特性,且兼容 Redis 集群 / 哨兵模式。
步骤 1:引入依赖(Maven)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.25.0</version> <!-- 最新稳定版 -->
</dependency>
步骤 2:Redisson 分布式锁实战代码
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedissonLockDemo {
public static void main(String[] args) {
// 1. 初始化Redisson客户端(支持集群/哨兵,此处以单节点为例)
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
RedissonClient redisson = Redisson.create(config);
// 2. 获取分布式锁(可重入锁,锁键与业务绑定)
RLock lock = redisson.getLock("lock:stock");
try {
// 3. 尝试获取锁:最多等待10秒,获取成功后锁自动过期30秒
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (locked) {
System.out.println("Redisson获取锁成功,执行库存扣减任务...");
// 执行核心业务(如订单创建、库存更新)
executeBusinessTask();
} else {
System.out.println("获取锁超时,请稍后重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("线程中断,获取锁失败");
} finally {
// 4. 释放锁(仅当前线程持有锁时才释放,避免误删)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("锁释放成功");
}
// 关闭客户端(实际项目中提议单例复用,避免频繁创建连接)
redisson.shutdown();
}
}
private static void executeBusinessTask() {
// 模拟业务执行(如调用库存服务、订单服务)
try {
Thread.sleep(5000); // 模拟耗时5秒
System.out.println("业务任务执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Redis 分布式锁的避坑指南
1. 核心坑点与解决方案
|
坑点 |
危害 |
解决方案 |
|
锁过期时间过短 |
任务未完成锁已释放,导致并发冲突 |
1. 合理设置过期时间(略长于任务最大耗时);2. 用 Redisson 看门狗自动续命 |
|
释放锁时未校验客户端标识 |
误删其他客户端的锁 |
1. 锁值设置为客户端唯一标识(如 UUID);2. 释放锁用 Lua 脚本原子校验 + 删除 |
|
Redis 主从切换导致锁丢失 |
主节点宕机后,从节点未同步锁键 |
1. 用 Redis 集群 + 哨兵模式;2. 高安全性场景使用 Redlock 算法(多节点确认) |
|
长时间持有锁 |
降低系统吞吐量 |
1. 拆分长任务为短任务;2. 避免在锁内执行远程调用(如 HTTP 请求) |
2. 最佳实践提议
- 锁键命名规范:采用lock:业务模块:资源标识格式(如lock:order:user123),避免锁键冲突;
- 过期时间设置:根据业务耗时合理预估,提议设置为 “最大任务耗时 ×1.5”(如任务最长 20 秒,过期时间设 30 秒);
- 客户端选择:非特殊需求直接使用 Redisson,避免重复造轮子;
- 集群部署:Redis 提议部署主从 + 哨兵模式,至少 3 个节点,保证高可用;
- 避免滥用锁:仅在共享资源竞争场景使用,非竞争场景无需加锁(如查询操作)。
总结:Redis 分布式锁的适用场景与选型提议
Redis 分布式锁的核心优势是高性能、易实现,但并非万能方案,需根据业务场景合理选型:
1. 适用场景
- 高并发短任务(如秒杀、限流、库存扣减);
- 对数据一致性要求中等(允许极低概率的锁丢失,如非金融场景);
- 分布式任务调度(如定时任务避免重复执行);
- 分布式缓存更新(如缓存击穿防护)。
2. 不适用场景
- 金融级强一致性场景(如转账、支付):提议使用 ZooKeeper 分布式锁或数据库悲观锁;
- 长任务场景(任务耗时超过 10 分钟):提议使用消息队列 + 状态机替代;
- 无共享资源竞争的场景:无需加锁,避免不必要的性能开销。
最后记住:分布式锁的本质是 “平衡安全性与可用性”。大部分互联网业务场景中,Redisson + Redis 主从哨兵的组合足以满足需求,既保证了性能,又降低了开发复杂度。如果你的业务对一致性要求极高,再思考引入 ZooKeeper 或 Redlock 等更复杂的方案。




