分布式锁是分布式系统中保证多进程/多实例互斥访问共享资源的核心组件。最经典且生产中最常用的分布式锁实现是基于 Redis 的(RedLock 算法或单节点简化版)。
关键点:过期时间,等待时间,lock key 、lock value、重试时间、重试间隔、续期
| 参数 | 推荐值 / 说明 | 为什么重要 |
|---|---|---|
| lock_key | 业务含义明确的 key,例如 lock:order:12345 |
唯一标识一把锁 |
| lock_value | 随机 UUID(最好是全局唯一,如 hostname + threadId + UUID.randomUUID()) |
用于安全释放锁,防止误删他人锁(A-B-A 问题) |
| 过期时间(TTL) | 业务最大执行时间 + 缓冲(如 10~30 秒) | 防止死锁:即使客户端崩溃,锁也能自动过期 |
| 获取锁超时时间 | 通常等于或略大于 TTL(如 15~35 秒) | 防止无限等待锁,快速失败 |
| 重试次数 | 3~10 次(视业务对延迟敏感度) | 提高获取锁成功率 |
| 重试间隔(backoff) | 指数退避 + 随机 jitter(如 100ms → 200ms → 400ms + random(0~100ms)) | 防止惊群效应(thundering herd),避免大量客户端同时重试 |
| 续期时间(watchdog) | 每 TTL/3 时间续期一次(例如 TTL=30s,每 10s 续期一次) | 防止业务处理时间 > TTL 导致锁被别人抢走 |
# 1. 获取锁(推荐使用 Lua 脚本原子性)
SET lock_key lock_value NX PX 30000
# NX:只有 key 不存在时才设置成功
# PX:毫秒级自动过期
# 2. 释放锁(必须用 Lua 脚本,保证检查 value + 删除 原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
# 3. 续期锁(同样用 Lua)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("pexpire", KEYS[1], ARGV[2])
else
return 0
end
String lockValue = UUID.randomUUID().toString() + "-" + Thread.currentThread().getId();
long expireMs = 30_000L;
long acquireTimeout = 35_000L;
int retryTimes = 6;
long start = System.currentTimeMillis();
while (true) {
// 尝试获取锁
Boolean success = redisTemplate.execute(setNxPxScript,
Collections.singletonList(lockKey), lockValue, expireMs);
if (success) {
// 获取成功,开启自动续期线程(watchdog)
startWatchDog(lockKey, lockValue, expireMs);
return true;
}
// 重试判断
if (System.currentTimeMillis() - start > acquireTimeout) {
return false; // 超时失败
}
// 指数退避 + jitter
long sleepMs = Math.min(100L << retryTimes, 1000) + random.nextInt(100);
Thread.sleep(sleepMs);
retryTimes--;
if (retryTimes <= 0) return false;
}
private void startWatchDog(String key, String value, long ttl) {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
try {
Long result = redisTemplate.execute(extendScript,
Collections.singletonList(key), value, ttl);
if (result == null || result == 0) {
// 续期失败,说明锁已不属于自己,停止续期
executor.shutdown();
}
} catch (Exception e) {
// 记录日志,继续尝试续期
}
}, ttl / 3, ttl / 3, TimeUnit.MILLISECONDS);
// 在 finally 中 shutdown(释放锁时也要停止续期)
}
public void unlock() {
try {
redisTemplate.execute(releaseScript, Collections.singletonList(lockKey), lockValue);
} finally {
if (watchDogExecutor != null) {
watchDogExecutor.shutdownNow();
}
}
}