目录
一、分布式锁核心概念
1.1 什么是分布式锁?
分布式锁是在分布式系统中,用于控制多个进程/线程对共享资源进行互斥访问的机制。
单机锁 vs 分布式锁:
┌────────────────────────────────────────────────────┐
│ 单机锁(JVM内) │
├────────────────────────────────────────────────────┤
│ Thread-1 ──┐ │
│ ├──> [synchronized / ReentrantLock] │
│ Thread-2 ──┘ │
│ │
│ 特点: 只在一个JVM内有效 │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ 分布式锁(跨JVM) │
├────────────────────────────────────────────────────┤
│ JVM-1 ──┐ │
│ ├──> [分布式锁服务] ──┐ │
│ JVM-2 ──┤ ├──> 共享资源 │
│ │ │ │
│ JVM-3 ──┘ ┘ │
│ │
│ 特点: 跨多个JVM/服务器有效 │
└────────────────────────────────────────────────────┘
1.2 分布式锁的核心特性
分布式锁必须满足的特性:
1. 互斥性(Mutual Exclusion)
✅ 同一时刻只有一个进程/线程能持有锁
2. 可重入性(Reentrant)
✅ 同一个进程/线程可以多次获取同一把锁
3. 锁超时(Timeout)
✅ 防止死锁,锁必须有过期时间
4. 高可用性(High Availability)
✅ 锁服务本身必须高可用,不能单点故障
5. 非阻塞性(Non-blocking)
✅ 获取锁失败时,应该快速失败,而不是无限等待
6. 公平性(Fairness)
✅ (可选)按照请求顺序获取锁
1.3 分布式锁的应用场景
典型应用场景:
1. 防止重复操作
- 用户重复提交订单
- 定时任务重复执行
- 消息重复消费
2. 资源访问控制
- 库存扣减
- 账户余额修改
- 文件上传处理
3. 分布式任务调度
- 定时任务只在一个节点执行
- 分布式计算任务分配
4. 缓存更新控制
- 防止缓存击穿(缓存重建)
- 防止缓存雪崩
二、分布式锁实现方式对比
2.1 实现方式概览
分布式锁实现方式对比表:
| 实现方式 | 性能 | 可靠性 | 复杂度 | 推荐度 |
|---|---|---|---|---|
| Redis | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Redisson | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| ZooKeeper | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 数据库 | ⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐⭐ |
| etcd | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
2.2 详细对比
| 特性 | Redis | Redisson | ZooKeeper | 数据库 | etcd |
|---|---|---|---|---|---|
| 性能 | 极高(10万+ QPS) | 极高(10万+ QPS) | 中等(1万+ QPS) | 低(1000 QPS) | 高(5万+ QPS) |
| 可靠性 | 中等(主从复制) | 高(RedLock) | 极高(ZAB协议) | 高(ACID) | 极高(Raft) |
| 实现复杂度 | 简单 | 简单 | 中等 | 简单 | 中等 |
| 锁超时 | 支持 | 支持 | 支持 | 支持 | 支持 |
| 可重入 | 需实现 | 原生支持 | 需实现 | 需实现 | 需实现 |
| 公平锁 | 需实现 | 支持 | 支持 | 不支持 | 需实现 |
| Watch机制 | 需实现 | 支持 | 原生支持 | 不支持 | 原生支持 |
| 适用场景 | 高并发、低一致性要求 | 高并发、中等一致性要求 | 高一致性要求 | 低并发、高一致性 | 高一致性要求 |
三、Redis 分布式锁实现
3.1 基础实现(SET NX EX)
3.1.1 简单实现
/**
* Redis 分布式锁(基础版)
* 问题: 存在死锁风险(进程崩溃无法释放锁)
*/
@Service
@Slf4j
public class SimpleRedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 获取锁
* @param key 锁的key
* @param value 锁的值(用于释放锁时验证)
* @param expireTime 过期时间(秒)
* @return 是否获取成功
*/
public boolean tryLock(String key, String value, long expireTime) {
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
/**
* 释放锁
* @param key 锁的key
* @param value 锁的值(必须匹配才能释放)
*/
public void unlock(String key, String value) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
);
}
}
3.1.2 问题分析
基础实现的问题:
1. ❌ 死锁风险
场景: 进程获取锁后崩溃,锁无法释放
解决: 必须设置过期时间
2. ❌ 误释放锁
场景: 进程A获取锁(30s),执行50s,进程B获取锁,进程A释放了B的锁
解决: 释放锁时验证value是否匹配
3. ❌ 锁续期问题
场景: 业务执行时间超过锁过期时间
解决: 使用看门狗机制自动续期
3.2 改进实现(带看门狗)
/**
* Redis 分布式锁(改进版 - 带看门狗)
*/
@Service
@Slf4j
public class ImprovedRedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
// 锁续期线程池
private final ScheduledExecutorService watchDogExecutor =
Executors.newScheduledThreadPool(10);
// 存储锁的续期任务
private final Map<String, ScheduledFuture<?>> watchDogTasks = new ConcurrentHashMap<>();
/**
* 获取锁(带看门狗)
* @param key 锁的key
* @param expireTime 过期时间(秒)
* @param watchDogInterval 看门狗续期间隔(秒)
* @return 锁的value(用于释放锁)
*/
public String tryLock(String key, long expireTime, long watchDogInterval) {
String value = UUID.randomUUID().toString();
// 1. 尝试获取锁
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(result)) {
// 2. 启动看门狗(自动续期)
startWatchDog(key, value, expireTime, watchDogInterval);
return value;
}
return null;
}
/**
* 启动看门狗(自动续期)
*/
private void startWatchDog(String key, String value, long expireTime, long interval) {
ScheduledFuture<?> future = watchDogExecutor.scheduleAtFixedRate(() -> {
try {
// 检查锁是否还存在且value匹配
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('expire', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value,
String.valueOf(expireTime)
);
if (result == null || result == 0) {
// 锁已不存在,停止续期
stopWatchDog(key);
}
} catch (Exception e) {
log.error("Watch dog error for key: {}", key, e);
stopWatchDog(key);
}
}, interval, interval, TimeUnit.SECONDS);
watchDogTasks.put(key, future);
}
/**
* 停止看门狗
*/
private void stopWatchDog(String key) {
ScheduledFuture<?> future = watchDogTasks.remove(key);
if (future != null) {
future.cancel(false);
}
}
/**
* 释放锁
*/
public void unlock(String key, String value) {
// 1. 停止看门狗
stopWatchDog(key);
// 2. 释放锁(Lua脚本保证原子性)
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
);
}
}
3.3 可重入锁实现
/**
* Redis 可重入分布式锁
*/
@Service
@Slf4j
public class ReentrantRedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
// ThreadLocal存储当前线程的锁信息
private final ThreadLocal<Map<String, Integer>> lockCount = ThreadLocal.withInitial(HashMap::new);
/**
* 获取可重入锁
*/
public boolean tryLock(String key, String value, long expireTime) {
Map<String, Integer> counts = lockCount.get();
Integer count = counts.get(key);
if (count != null && count > 0) {
// 重入:增加计数
counts.put(key, count + 1);
return true;
}
// 首次获取锁
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(result)) {
counts.put(key, 1);
return true;
}
return false;
}
/**
* 释放可重入锁
*/
public void unlock(String key, String value) {
Map<String, Integer> counts = lockCount.get();
Integer count = counts.get(key);
if (count == null || count <= 0) {
throw new IllegalStateException("Lock not held by current thread");
}
if (count == 1) {
// 最后一次释放,删除锁
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
);
counts.remove(key);
} else {
// 减少计数
counts.put(key, count - 1);
}
}
}
3.4 公平锁实现(基于List + Set)
/**
* Redis 公平分布式锁(FIFO)
*
* 实现原理:
* 1. 使用 List 维护等待队列(FIFO)
* 2. 使用 Set 存储已获取锁的线程
* 3. 使用 String 存储当前持有锁的线程
*/
@Service
@Slf4j
public class FairRedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_KEY_PREFIX = "fair:lock:";
private static final String QUEUE_KEY_PREFIX = "fair:queue:";
private static final String SET_KEY_PREFIX = "fair:set:";
/**
* 获取公平锁
*/
public boolean tryLock(String key, String value, long expireTime, long waitTime) {
String lockKey = LOCK_KEY_PREFIX + key;
String queueKey = QUEUE_KEY_PREFIX + key;
String setKey = SET_KEY_PREFIX + key;
long startTime = System.currentTimeMillis();
while (true) {
// 1. 尝试获取锁
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(result)) {
// 2. 从等待队列中移除
redisTemplate.opsForList().remove(queueKey, 1, value);
redisTemplate.opsForSet().remove(setKey, value);
return true;
}
// 3. 检查是否超时
if (System.currentTimeMillis() - startTime > waitTime) {
// 超时,从等待队列中移除
redisTemplate.opsForList().remove(queueKey, 1, value);
redisTemplate.opsForSet().remove(setKey, value);
return false;
}
// 4. 加入等待队列(如果不在队列中)
Boolean inQueue = redisTemplate.opsForSet().isMember(setKey, value);
if (Boolean.FALSE.equals(inQueue)) {
redisTemplate.opsForList().rightPush(queueKey, value);
redisTemplate.opsForSet().add(setKey, value);
}
// 5. 检查是否轮到当前线程
String first = redisTemplate.opsForList().index(queueKey, 0);
if (value.equals(first)) {
// 轮到我了,再次尝试获取锁
continue;
}
// 6. 等待一小段时间后重试
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
/**
* 释放公平锁
*/
public void unlock(String key, String value) {
String lockKey = LOCK_KEY_PREFIX + key;
String queueKey = QUEUE_KEY_PREFIX + key;
String setKey = SET_KEY_PREFIX + key;
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" redis.call('del', KEYS[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList(lockKey),
value
);
}
}
四、ZooKeeper 分布式锁实现
4.1 ZooKeeper 锁原理
ZooKeeper 分布式锁原理:
1. 临时顺序节点(Ephemeral Sequential Node)
/lock/resource_0000000001
/lock/resource_0000000002
/lock/resource_0000000003
2. 最小序号获取锁
- 节点序号最小的获得锁
- 其他节点监听前一个节点的删除事件
3. 锁释放
- 节点删除(临时节点,会话断开自动删除)
- 下一个节点被通知,尝试获取锁
4.2 基础实现
/**
* ZooKeeper 分布式锁
*/
@Service
@Slf4j
public class ZooKeeperLock {
private final CuratorFramework client;
private static final String LOCK_PATH = "/locks";
public ZooKeeperLock(CuratorFramework client) {
this.client = client;
}
/**
* 获取锁
*/
public InterProcessMutex tryLock(String resource) throws Exception {
String lockPath = LOCK_PATH + "/" + resource;
InterProcessMutex lock = new InterProcessMutex(client, lockPath);
// 尝试获取锁(最多等待10秒)
boolean acquired = lock.acquire(10, TimeUnit.SECONDS);
if (acquired) {
return lock;
}
return null;
}
/**
* 释放锁
*/
public void unlock(InterProcessMutex lock) {
try {
if (lock != null && lock.isAcquiredInThisProcess()) {
lock.release();
}
} catch (Exception e) {
log.error("Failed to release lock", e);
}
}
}
4.3 可重入锁实现
/**
* ZooKeeper 可重入分布式锁
*/
@Service
@Slf4j
public class ReentrantZooKeeperLock {
private final CuratorFramework client;
private static final String LOCK_PATH = "/locks";
// ThreadLocal存储当前线程的锁信息
private final ThreadLocal<Map<String, InterProcessMutex>> locks =
ThreadLocal.withInitial(HashMap::new);
public ReentrantZooKeeperLock(CuratorFramework client) {
this.client = client;
}
/**
* 获取可重入锁
*/
public boolean tryLock(String resource) throws Exception {
Map<String, InterProcessMutex> lockMap = locks.get();
InterProcessMutex lock = lockMap.get(resource);
if (lock != null) {
// 重入:直接返回true(Curator的InterProcessMutex本身支持可重入)
return true;
}
// 首次获取锁
String lockPath = LOCK_PATH + "/" + resource;
lock = new InterProcessMutex(client, lockPath);
boolean acquired = lock.acquire(10, TimeUnit.SECONDS);
if (acquired) {
lockMap.put(resource, lock);
return true;
}
return false;
}
/**
* 释放可重入锁
*/
public void unlock(String resource) {
Map<String, InterProcessMutex> lockMap = locks.get();
InterProcessMutex lock = lockMap.get(resource);
if (lock != null) {
try {
lock.release();
lockMap.remove(resource);
} catch (Exception e) {
log.error("Failed to release lock: {}", resource, e);
}
}
}
}
4.4 ZooKeeper 配置
/**
* ZooKeeper 配置
*/
@Configuration
public class ZooKeeperConfig {
@Value("${zookeeper.connect-string:localhost:2181}")
private String connectString;
@Value("${zookeeper.session-timeout:30000}")
private int sessionTimeout;
@Bean
public CuratorFramework curatorFramework() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(connectString)
.sessionTimeoutMs(sessionTimeout)
.retryPolicy(retryPolicy)
.build();
client.start();
return client;
}
}
五、数据库分布式锁实现
5.1 基于唯一索引实现
/**
* 数据库分布式锁(基于唯一索引)
*/
@Service
@Slf4j
public class DatabaseLock {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 获取锁
*/
public boolean tryLock(String lockName, String owner, long expireTime) {
String sql = "INSERT INTO distributed_lock (lock_name, owner, expire_time, create_time) " +
"VALUES (?, ?, ?, NOW()) " +
"ON DUPLICATE KEY UPDATE " +
" owner = IF(expire_time < NOW(), VALUES(owner), owner), " +
" expire_time = IF(expire_time < NOW(), VALUES(expire_time), expire_time)";
try {
int rows = jdbcTemplate.update(sql, lockName, owner,
new Timestamp(System.currentTimeMillis() + expireTime));
return rows > 0;
} catch (DuplicateKeyException e) {
// 锁已被其他进程持有
return false;
}
}
/**
* 释放锁
*/
public void unlock(String lockName, String owner) {
String sql = "DELETE FROM distributed_lock WHERE lock_name = ? AND owner = ?";
jdbcTemplate.update(sql, lockName, owner);
}
/**
* 锁续期
*/
public boolean renewLock(String lockName, String owner, long expireTime) {
String sql = "UPDATE distributed_lock " +
"SET expire_time = DATE_ADD(NOW(), INTERVAL ? SECOND) " +
"WHERE lock_name = ? AND owner = ? AND expire_time > NOW()";
int rows = jdbcTemplate.update(sql, expireTime / 1000, lockName, owner);
return rows > 0;
}
}
5.2 数据库表结构
CREATE TABLE distributed_lock (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
lock_name VARCHAR(128) NOT NULL UNIQUE COMMENT '锁名称',
owner VARCHAR(128) NOT NULL COMMENT '锁持有者',
expire_time TIMESTAMP NOT NULL COMMENT '过期时间',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_expire_time (expire_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁表';
5.3 定时清理过期锁
/**
* 定时清理过期锁
*/
@Component
@Slf4j
public class LockCleanupTask {
@Autowired
private JdbcTemplate jdbcTemplate;
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void cleanupExpiredLocks() {
String sql = "DELETE FROM distributed_lock WHERE expire_time < NOW()";
int deleted = jdbcTemplate.update(sql);
if (deleted > 0) {
log.info("Cleaned up {} expired locks", deleted);
}
}
}
六、Redisson 分布式锁实现
6.1 Redisson 简介
Redisson 是 Redis 的 Java 客户端,提供了丰富的分布式对象和服务,包括分布式锁。
Redisson 分布式锁特性:
✅ 可重入锁(ReentrantLock)
✅ 公平锁(FairLock)
✅ 读写锁(ReadWriteLock)
✅ 信号量(Semaphore)
✅ 闭锁(CountDownLatch)
✅ 看门狗机制(自动续期)
✅ 异步支持
6.2 Redisson 配置
/**
* Redisson 配置
*/
@Configuration
public class RedissonConfig {
@Value("${redis.host:localhost}")
private String host;
@Value("${redis.port:6379}")
private int port;
@Value("${redis.password:}")
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 单节点模式
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setPassword(password)
.setConnectionPoolSize(10)
.setConnectionMinimumIdleSize(5);
// 集群模式(可选)
// config.useClusterServers()
// .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001");
return Redisson.create(config);
}
}
6.3 可重入锁使用
/**
* Redisson 可重入锁使用示例
*/
@Service
@Slf4j
public class RedissonLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 可重入锁示例
*/
public void doWithLock(String resource) {
RLock lock = redissonClient.getLock("lock:" + resource);
try {
// 尝试获取锁,最多等待10秒,锁定后30秒自动解锁
boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (acquired) {
// 业务逻辑
doBusinessLogic(resource);
} else {
log.warn("Failed to acquire lock: {}", resource);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Interrupted while acquiring lock", e);
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 公平锁示例
*/
public void doWithFairLock(String resource) {
RLock lock = redissonClient.getFairLock("fair:lock:" + resource);
try {
boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (acquired) {
doBusinessLogic(resource);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 读写锁示例
*/
public void doWithReadWriteLock(String resource) {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw:lock:" + resource);
RLock readLock = readWriteLock.readLock();
RLock writeLock = readWriteLock.writeLock();
// 读锁(共享)
readLock.lock();
try {
// 读操作
readData(resource);
} finally {
readLock.unlock();
}
// 写锁(独占)
writeLock.lock();
try {
// 写操作
writeData(resource);
} finally {
writeLock.unlock();
}
}
/**
* 信号量示例(限流)
*/
public void doWithSemaphore(String resource, int permits) {
RSemaphore semaphore = redissonClient.getSemaphore("semaphore:" + resource);
try {
// 尝试获取许可
boolean acquired = semaphore.tryAcquire(permits, 10, TimeUnit.SECONDS);
if (acquired) {
// 执行业务逻辑
doBusinessLogic(resource);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放许可
semaphore.release(permits);
}
}
private void doBusinessLogic(String resource) {
// 业务逻辑
}
private void readData(String resource) {
// 读操作
}
private void writeData(String resource) {
// 写操作
}
}
6.4 RedLock 实现(多Redis实例)
/**
* RedLock 实现(多Redis实例,提高可靠性)
*/
@Service
@Slf4j
public class RedLockService {
@Autowired
private RedissonClient redissonClient1;
@Autowired
private RedissonClient redissonClient2;
@Autowired
private RedissonClient redissonClient3;
/**
* RedLock 获取锁(需要大多数节点成功)
*/
public boolean tryRedLock(String resource, long waitTime, long leaseTime) {
RLock lock1 = redissonClient1.getLock("lock:" + resource);
RLock lock2 = redissonClient2.getLock("lock:" + resource);
RLock lock3 = redissonClient3.getLock("lock:" + resource);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
boolean acquired = redLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
if (acquired) {
// 业务逻辑
doBusinessLogic(resource);
return true;
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
if (redLock.isHeldByCurrentThread()) {
redLock.unlock();
}
}
}
private void doBusinessLogic(String resource) {
// 业务逻辑
}
}
七、常见问题与解决方案
7.1 死锁问题
问题: 进程获取锁后崩溃,锁无法释放
解决方案:
1. 设置锁过期时间(TTL)
2. 使用看门狗机制自动续期
3. 使用临时节点(ZooKeeper)
7.2 锁误释放问题
问题: 进程A的锁过期,进程B获取锁,进程A释放了B的锁
解决方案:
1. 释放锁时验证value是否匹配(Redis)
2. 使用唯一标识(UUID)作为锁的值
3. 使用Lua脚本保证原子性
7.3 锁续期问题
问题: 业务执行时间超过锁过期时间
解决方案:
1. 使用看门狗机制(Redisson)
2. 合理设置锁过期时间
3. 异步续期任务
7.4 时钟漂移问题
问题: 不同服务器时钟不同步,导致锁过期时间不准确
解决方案:
1. 使用NTP同步时钟
2. 使用相对时间而非绝对时间
3. 增加锁过期时间的缓冲时间
7.5 网络分区问题
问题: 网络分区导致锁状态不一致
解决方案:
1. 使用RedLock(多Redis实例)
2. 使用ZooKeeper(ZAB协议保证一致性)
3. 使用etcd(Raft协议保证一致性)
八、最佳实践
8.1 锁命名规范
/**
* 锁命名规范
*/
public class LockNaming {
/**
* 格式: {业务模块}:{资源类型}:{资源ID}
*
* 示例:
* order:create:user123
* inventory:deduct:product456
* payment:process:order789
*/
public static String generateLockKey(String module, String resourceType, String resourceId) {
return String.format("%s:%s:%s", module, resourceType, resourceId);
}
}
8.2 锁超时时间设置
/**
* 锁超时时间设置建议
*/
public class LockTimeout {
/**
* 根据业务场景设置合理的超时时间
*
* 快速操作: 5-10秒
* 一般操作: 30-60秒
* 慢速操作: 5-10分钟
*/
public static long getTimeout(String operationType) {
switch (operationType) {
case "QUICK": return 10; // 10秒
case "NORMAL": return 60; // 60秒
case "SLOW": return 600; // 10分钟
default: return 30; // 默认30秒
}
}
}
8.3 锁使用模板
/**
* 分布式锁使用模板
*/
@Component
@Slf4j
public class LockTemplate {
@Autowired
private RedissonClient redissonClient;
/**
* 执行带锁的业务逻辑
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
if (!acquired) {
throw new LockAcquisitionException("Failed to acquire lock: " + lockKey);
}
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockAcquisitionException("Interrupted while acquiring lock", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 执行带锁的业务逻辑(无返回值)
*/
public void executeWithLock(String lockKey, long waitTime, long leaseTime,
Runnable runnable) {
executeWithLock(lockKey, waitTime, leaseTime, () -> {
runnable.run();
return null;
});
}
}
8.4 使用示例
/**
* 分布式锁使用示例
*/
@Service
@Slf4j
public class OrderService {
@Autowired
private LockTemplate lockTemplate;
/**
* 创建订单(防止重复提交)
*/
public Order createOrder(CreateOrderRequest request) {
String lockKey = "order:create:" + request.getUserId();
return lockTemplate.executeWithLock(
lockKey,
5, // 等待5秒
30, // 锁定30秒
() -> {
// 检查是否重复提交
Order existingOrder = orderMapper.selectByRequestId(request.getRequestId());
if (existingOrder != null) {
return existingOrder;
}
// 创建订单
Order order = new Order();
// ... 设置订单信息
orderMapper.insert(order);
return order;
}
);
}
/**
* 扣减库存(防止超卖)
*/
public void deductInventory(String productId, int quantity) {
String lockKey = "inventory:deduct:" + productId;
lockTemplate.executeWithLock(
lockKey,
10, // 等待10秒
60, // 锁定60秒
() -> {
// 查询库存
Inventory inventory = inventoryMapper.selectByProductId(productId);
// 检查库存是否充足
if (inventory.getStock() < quantity) {
throw new InsufficientStockException("库存不足");
}
// 扣减库存
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
}
);
}
}
8.5 监控和告警
/**
* 分布式锁监控
*/
@Component
@Slf4j
public class LockMonitor {
@Autowired
private MeterRegistry meterRegistry;
private final Counter lockAcquireCounter;
private final Counter lockReleaseCounter;
private final Counter lockTimeoutCounter;
private final Timer lockHoldTimer;
public LockMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.lockAcquireCounter = Counter.builder("distributed.lock.acquire.total")
.description("分布式锁获取总数")
.register(meterRegistry);
this.lockReleaseCounter = Counter.builder("distributed.lock.release.total")
.description("分布式锁释放总数")
.register(meterRegistry);
this.lockTimeoutCounter = Counter.builder("distributed.lock.timeout.total")
.description("分布式锁超时总数")
.register(meterRegistry);
this.lockHoldTimer = Timer.builder("distributed.lock.hold.duration")
.description("分布式锁持有时间")
.register(meterRegistry);
}
/**
* 记录锁获取
*/
public void recordLockAcquire(String lockKey, boolean success) {
lockAcquireCounter.increment(Tags.of("lock", lockKey, "success", String.valueOf(success)));
if (!success) {
lockTimeoutCounter.increment(Tags.of("lock", lockKey));
}
}
/**
* 记录锁释放
*/
public void recordLockRelease(String lockKey) {
lockReleaseCounter.increment(Tags.of("lock", lockKey));
}
/**
* 记录锁持有时间
*/
public void recordLockHoldDuration(String lockKey, long durationMs) {
lockHoldTimer.record(durationMs, TimeUnit.MILLISECONDS, Tags.of("lock", lockKey));
}
}
九、常见疑问与解答
9.1 基础概念疑问
Q1: 为什么单机锁(synchronized/ReentrantLock)在分布式环境下不够用?
疑问: 既然单机锁已经能保证线程安全,为什么还需要分布式锁?
解答:
单机锁的局限性:
场景示例:
┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│ 服务器A │ │ 服务器B │ │ 服务器C │
│ JVM-1 │ │ JVM-2 │ │ JVM-3 │
│ │ │ │ │ │
│ synchronized │ │ synchronized │ │ synchronized │
│ 锁A │ │ 锁B │ │ 锁C │
└──────────────┘ └─────────────┘ └─────────────┘
│ │ │
└─────────────────────┴─────────────────────┘
│
┌─────────▼─────────┐
│ 共享数据库 │
│ 库存: 100 │
└──────────────────┘
问题:
1. 每个JVM的锁是独立的,无法感知其他JVM的锁
2. 三个服务器同时扣减库存,可能导致超卖
3. 需要跨JVM的协调机制
解决方案:
使用分布式锁,所有服务器共享同一个锁状态
代码示例:
// ❌ 错误:单机锁无法防止分布式环境下的并发问题
public class InventoryService {
private int stock = 100;
private final Object lock = new Object();
public void deductStock(int quantity) {
synchronized (lock) { // 只在当前JVM有效
if (stock >= quantity) {
stock -= quantity;
}
}
}
}
// ✅ 正确:使用分布式锁
public class InventoryService {
@Autowired
private RedissonClient redissonClient;
public void deductStock(int quantity) {
RLock lock = redissonClient.getLock("inventory:product:123");
try {
lock.lock();
// 从数据库查询库存
Inventory inventory = inventoryMapper.selectById(123);
if (inventory.getStock() >= quantity) {
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
}
} finally {
lock.unlock();
}
}
}
Q2: 分布式锁和数据库事务有什么区别?什么时候用哪个?
疑问: 数据库事务已经能保证ACID,为什么还需要分布式锁?
解答:
分布式锁 vs 数据库事务:
┌─────────────────────────────────────────────────────────┐
│ 数据库事务 │
├─────────────────────────────────────────────────────────┤
│ 作用范围: 单个数据库连接内 │
│ 保证: ACID(原子性、一致性、隔离性、持久性) │
│ 粒度: 数据库操作级别 │
│ 场景: 单个请求内的多个数据库操作 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 分布式锁 │
├─────────────────────────────────────────────────────────┤
│ 作用范围: 跨进程、跨服务器 │
│ 保证: 互斥访问 │
│ 粒度: 业务逻辑级别 │
│ 场景: 跨请求的业务逻辑互斥 │
└─────────────────────────────────────────────────────────┘
使用场景对比:
// 场景1: 数据库事务适用(单个请求内的操作)
@Transactional
public void transferMoney(Long fromAccount, Long toAccount, BigDecimal amount) {
// 这些操作在同一个事务中,要么全部成功,要么全部回滚
accountMapper.deduct(fromAccount, amount);
accountMapper.add(toAccount, amount);
}
// 场景2: 分布式锁适用(跨请求的业务逻辑)
public void createOrder(CreateOrderRequest request) {
String lockKey = "order:create:" + request.getUserId();
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
// 防止用户重复提交订单(跨请求)
Order existingOrder = orderMapper.selectByRequestId(request.getRequestId());
if (existingOrder != null) {
return existingOrder;
}
// 创建订单(内部可能包含事务)
return createOrderInternal(request);
} finally {
lock.unlock();
}
}
选择原则:
- 数据库事务: 单个请求内的多个数据库操作需要原子性
- 分布式锁: 跨请求的业务逻辑需要互斥访问
9.2 实现细节疑问
Q3: Redis分布式锁为什么要用Lua脚本?直接用Redis命令不行吗?
疑问: 为什么释放锁要用Lua脚本,直接用DEL命令不行吗?
解答:
问题场景:
时间线:
T1: 进程A获取锁(过期时间30秒)
T2: 进程A执行业务逻辑(耗时40秒)
T3: 锁过期,进程B获取锁
T4: 进程A执行完成,释放锁(误释放了B的锁)
T5: 进程C获取锁(此时B和C都认为持有锁)
❌ 错误实现:
public void unlock(String key) {
redisTemplate.delete(key); // 可能删除其他进程的锁
}
✅ 正确实现(Lua脚本保证原子性):
public void unlock(String key, String value) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
);
}
为什么需要Lua脚本:
非原子操作的问题:
步骤1: GET key → 返回 "value-A"
步骤2: 判断 value == "value-A" → true
步骤3: DEL key → 删除锁
问题:
在步骤2和步骤3之间,如果锁被其他进程修改,步骤3可能删除错误的锁
Lua脚本的优势:
✅ 原子性:整个脚本作为一个原子操作执行
✅ 一致性:执行过程中不会被其他命令打断
✅ 性能:减少网络往返次数
Q4: 看门狗机制是怎么工作的?为什么需要它?
疑问: 既然设置了锁过期时间,为什么还需要看门狗自动续期?
解答:
看门狗机制的必要性:
场景1: 锁过期时间设置过长
❌ 问题: 如果进程崩溃,锁要等很久才能自动释放
✅ 解决: 设置较短的过期时间(如30秒)
场景2: 锁过期时间设置过短
❌ 问题: 业务逻辑执行时间超过锁过期时间,锁被提前释放
✅ 解决: 使用看门狗自动续期
看门狗工作原理:
时间线:
T0: 获取锁,设置过期时间30秒,启动看门狗(每10秒续期一次)
T10: 看门狗检查锁是否还存在,如果存在则续期30秒
T20: 看门狗再次续期30秒
T30: 业务逻辑执行完成,释放锁,停止看门狗
代码示例:
/**
* 看门狗机制实现
*/
public class WatchDogLock {
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private ScheduledFuture<?> watchDogTask;
public void lock(String key, String value, long expireTime) {
// 1. 获取锁
redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
// 2. 启动看门狗(在过期时间的1/3时续期)
long renewInterval = expireTime / 3;
watchDogTask = executor.scheduleAtFixedRate(() -> {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('expire', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value,
String.valueOf(expireTime)
);
}, renewInterval, renewInterval, TimeUnit.SECONDS);
}
public void unlock(String key, String value) {
// 停止看门狗
if (watchDogTask != null) {
watchDogTask.cancel(false);
}
// 释放锁
// ...
}
}
看门狗的优势:
- ✅ 自动续期,避免锁提前过期
- ✅ 进程崩溃时,看门狗停止,锁自动过期
- ✅ 无需手动估算业务执行时间
Q5: RedLock算法真的能解决所有问题吗?有什么局限性?
疑问: RedLock号称能解决Redis单点故障问题,它真的完美吗?
解答:
RedLock算法原理:
1. 客户端向N个Redis实例发送加锁请求
2. 如果大多数(N/2+1)实例加锁成功,则认为获取锁成功
3. 释放锁时,向所有实例发送释放请求
示例(5个Redis实例):
Redis-1: ✅ 加锁成功
Redis-2: ✅ 加锁成功
Redis-3: ✅ 加锁成功
Redis-4: ❌ 加锁失败
Redis-5: ❌ 加锁失败
结果: 3/5 > 50%,获取锁成功
RedLock的局限性:
1. 时钟同步问题
问题: 如果不同Redis服务器时钟不同步,可能导致锁提前过期
场景:
- Redis-1时钟快2秒,锁在T+28秒过期
- Redis-2时钟正常,锁在T+30秒过期
- 客户端认为锁还有效,但Redis-1的锁已过期
解决: 使用NTP同步时钟,增加锁过期时间的缓冲
2. 网络延迟问题
问题: 网络延迟可能导致锁状态不一致
场景:
- T1: 客户端向5个Redis发送加锁请求
- T2: 3个Redis响应成功(网络快)
- T3: 客户端认为获取锁成功
- T4: 另外2个Redis响应成功(网络慢)
- 结果: 实际上5个Redis都加锁成功,但客户端只收到3个响应
解决: 设置合理的超时时间,等待所有响应
3. 性能开销
问题: 需要向多个Redis实例发送请求,性能开销大
场景: 5个Redis实例,每次加锁需要5次网络请求
解决: 权衡可靠性和性能,选择合适的实例数量(通常3-5个)
4. 脑裂问题
问题: 网络分区可能导致多个客户端同时持有锁
场景:
- 网络分区1: Redis-1, Redis-2, Redis-3, 客户端A
- 网络分区2: Redis-4, Redis-5, 客户端B
- 客户端A在分区1中获取锁(3/5)
- 客户端B在分区2中获取锁(2/5,但分区2认为自己是多数)
解决: 使用ZooKeeper等强一致性系统
Martin Kleppmann的质疑:
Martin Kleppmann(《数据密集型应用系统设计》作者)对RedLock的质疑:
1. RedLock假设所有Redis实例时钟同步
→ 实际环境中很难保证
2. RedLock假设网络延迟可预测
→ 实际网络延迟波动很大
3. RedLock无法保证锁的安全性
→ 在某些故障场景下,可能多个客户端同时持有锁
建议:
- 如果只需要效率(efficiency),使用单Redis实例即可
- 如果需要正确性(correctness),使用ZooKeeper等强一致性系统
9.3 性能与可靠性疑问
Q6: Redis分布式锁在高并发场景下性能如何?会不会成为瓶颈?
疑问: 如果所有请求都要获取锁,Redis会不会成为性能瓶颈?
解答:
Redis性能分析:
单Redis实例性能:
- SET命令: ~100,000 QPS
- GET命令: ~100,000 QPS
- Lua脚本: ~80,000 QPS
分布式锁操作:
1. SET NX EX(获取锁): ~100,000 QPS
2. Lua脚本(释放锁): ~80,000 QPS
实际场景分析:
场景: 电商秒杀活动
- 并发用户: 10,000
- 每个用户请求: 1次锁操作(获取锁 + 释放锁)
- 总QPS: 10,000 × 2 = 20,000 QPS
结论: Redis单实例可以轻松应对(100,000 QPS >> 20,000 QPS)
场景: 高频交易系统
- 并发请求: 100,000
- 每个请求: 1次锁操作
- 总QPS: 100,000 × 2 = 200,000 QPS
结论: 需要Redis集群或优化锁粒度
性能优化策略:
/**
* 性能优化示例
*/
public class OptimizedLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 优化1: 减少锁粒度
*/
public void deductInventory(String productId, int quantity) {
// ❌ 粗粒度锁(锁整个库存表)
// RLock lock = redissonClient.getLock("inventory:all");
// ✅ 细粒度锁(只锁单个商品)
RLock lock = redissonClient.getLock("inventory:product:" + productId);
try {
lock.lock();
// 业务逻辑
} finally {
lock.unlock();
}
}
/**
* 优化2: 使用读写锁(读多写少场景)
*/
public Inventory getInventory(String productId) {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("inventory:product:" + productId);
RLock readLock = readWriteLock.readLock();
try {
readLock.lock();
// 读操作(多个读可以并发)
return inventoryMapper.selectById(productId);
} finally {
readLock.unlock();
}
}
/**
* 优化3: 锁超时快速失败
*/
public void quickFailLock(String key) {
RLock lock = redissonClient.getLock(key);
try {
// 快速失败,不等待
boolean acquired = lock.tryLock(0, 30, TimeUnit.SECONDS);
if (!acquired) {
throw new LockAcquisitionException("Lock busy");
}
// 业务逻辑
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
性能对比:
锁粒度对性能的影响:
粗粒度锁(锁整个表):
- 并发度: 1(所有请求串行)
- QPS: ~1,000
细粒度锁(锁单条记录):
- 并发度: N(N条记录可以并发)
- QPS: ~10,000(假设100条记录)
读写锁(读多写少):
- 读并发度: 无限(多个读可以并发)
- 写并发度: 1(写操作串行)
- QPS: ~50,000(假设读:写 = 10:1)
Q7: ZooKeeper分布式锁为什么比Redis更可靠?代价是什么?
疑问: 为什么说ZooKeeper分布式锁更可靠?它有什么代价?
解答:
可靠性对比:
┌────────────────────────────────────────────────────┐
│ Redis分布式锁 │
├────────────────────────────────────────────────────┤
│ 可靠性机制: 主从复制(异步) │
│ 一致性保证: 最终一致性 │
│ 故障场景: │
│ - 主节点崩溃 → 从节点提升为主(可能丢失部分数据) │
│ - 网络分区 → 可能出现脑裂 │
│ 适用场景: 高并发、可容忍短暂不一致 │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ ZooKeeper分布式锁 │
├────────────────────────────────────────────────────┤
│ 可靠性机制: ZAB协议(强一致性) │
│ 一致性保证: 强一致性(线性一致性) │
│ 故障场景: │
│ - 节点崩溃 → 自动故障转移(不丢失数据) │
│ - 网络分区 → 少数派节点停止服务(保证一致性) │
│ 适用场景: 高一致性要求、可容忍较低性能 │
└────────────────────────────────────────────────────┘
ZooKeeper的代价:
1. 性能代价
Redis: ~100,000 QPS
ZooKeeper: ~10,000 QPS(性能低10倍)
2. 复杂度代价
Redis: 简单(SET NX EX)
ZooKeeper: 复杂(临时顺序节点、Watch机制)
3. 资源代价
Redis: 内存占用小
ZooKeeper: 需要更多内存和磁盘空间
4. 运维代价
Redis: 运维简单
ZooKeeper: 需要维护集群,配置复杂
选择建议:
选择Redis分布式锁:
✅ 高并发场景(QPS > 10,000)
✅ 可容忍短暂不一致
✅ 性能优先
✅ 简单易用
选择ZooKeeper分布式锁:
✅ 高一致性要求
✅ 可容忍较低性能
✅ 需要强一致性保证
✅ 已有ZooKeeper基础设施
9.4 实际应用疑问
Q8: 分布式锁会不会导致性能下降?如何评估是否需要使用?
疑问: 使用分布式锁会不会让系统变慢?什么时候该用,什么时候不该用?
解答:
性能影响分析:
无锁场景:
- 操作耗时: 10ms
- QPS: 100
使用分布式锁场景:
- 获取锁: 2ms
- 业务操作: 10ms
- 释放锁: 1ms
- 总耗时: 13ms
- QPS: 77(下降23%)
结论: 分布式锁确实会带来性能开销,但通常是可接受的
是否需要分布式锁的判断标准:
/**
* 判断是否需要分布式锁的决策树
*/
public class LockDecisionTree {
/**
* 场景1: 单机应用 → 不需要分布式锁
*/
public void singleMachineScenario() {
// 使用synchronized或ReentrantLock即可
synchronized (this) {
// 业务逻辑
}
}
/**
* 场景2: 分布式应用 + 无共享资源竞争 → 不需要分布式锁
*/
public void noResourceContention() {
// 每个请求处理不同的资源,无竞争
// 例如: 用户A修改自己的信息,用户B修改自己的信息
// → 不需要锁
}
/**
* 场景3: 分布式应用 + 有共享资源竞争 → 需要分布式锁
*/
public void hasResourceContention() {
// 多个请求竞争同一资源
// 例如: 多个用户同时购买同一商品(库存竞争)
// → 需要分布式锁
}
/**
* 场景4: 数据库唯一约束可以解决 → 不需要分布式锁
*/
public void databaseConstraint() {
// 例如: 防止重复订单
// 方案1: 使用分布式锁
// 方案2: 使用数据库唯一索引(更简单)
// ✅ 推荐: 使用数据库唯一索引
try {
orderMapper.insert(order); // 唯一索引防止重复
} catch (DuplicateKeyException e) {
// 重复订单,返回已有订单
return orderMapper.selectByRequestId(request.getRequestId());
}
}
}
性能优化建议:
/**
* 减少分布式锁的使用
*/
public class LockOptimization {
/**
* 优化1: 使用数据库唯一约束代替分布式锁
*/
public void useDatabaseConstraint() {
// ❌ 使用分布式锁防止重复订单
// RLock lock = redissonClient.getLock("order:create:" + userId);
// ✅ 使用数据库唯一索引
// ALTER TABLE orders ADD UNIQUE KEY uk_request_id (request_id);
try {
orderMapper.insert(order);
} catch (DuplicateKeyException e) {
// 处理重复订单
}
}
/**
* 优化2: 使用乐观锁代替分布式锁
*/
public void useOptimisticLock() {
// ❌ 使用分布式锁
// RLock lock = redissonClient.getLock("inventory:product:" + productId);
// ✅ 使用数据库乐观锁(版本号)
Inventory inventory = inventoryMapper.selectById(productId);
int oldVersion = inventory.getVersion();
inventory.setStock(inventory.getStock() - quantity);
int updated = inventoryMapper.updateByIdAndVersion(
inventory, oldVersion
);
if (updated == 0) {
// 版本冲突,重试
throw new OptimisticLockException();
}
}
/**
* 优化3: 减少锁持有时间
*/
public void minimizeLockTime() {
RLock lock = redissonClient.getLock("resource:123");
try {
lock.lock();
// ❌ 在锁内执行耗时操作
// heavyComputation();
// networkCall();
// ✅ 只锁关键操作
Resource resource = resourceMapper.selectById(123);
resource.setStatus("PROCESSING");
resourceMapper.updateById(resource);
} finally {
lock.unlock();
}
// 在锁外执行耗时操作
heavyComputation();
networkCall();
}
}
Q9: 分布式锁在微服务架构中如何设计?有哪些最佳实践?
疑问: 在微服务架构中,分布式锁应该如何设计和使用?
解答:
微服务架构中的分布式锁设计:
┌─────────────────────────────────────────────────────────┐
│ 微服务架构示例 │
├─────────────────────────────────────────────────────────┤
│ │
│ 服务A ──┐ │
│ │ │
│ 服务B ──┼──> [分布式锁服务] ──┐ │
│ │ │ │
│ 服务C ──┘ ├──> 共享资源(数据库) │
│ │ │
│ 服务D ───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
最佳实践:
/**
* 微服务架构中的分布式锁最佳实践
*/
@Service
@Slf4j
public class MicroserviceLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 实践1: 锁命名规范(服务名:资源类型:资源ID)
*/
public void lockNamingConvention(String serviceName, String resourceType, String resourceId) {
String lockKey = String.format("%s:%s:%s", serviceName, resourceType, resourceId);
// 示例: "order-service:create:user123"
// 示例: "inventory-service:deduct:product456"
}
/**
* 实践2: 锁超时时间根据业务场景设置
*/
public void lockTimeoutByScenario(String operationType) {
long timeout;
switch (operationType) {
case "QUICK": timeout = 5; break; // 5秒(快速操作)
case "NORMAL": timeout = 30; break; // 30秒(一般操作)
case "SLOW": timeout = 300; break; // 5分钟(慢速操作)
default: timeout = 30; break;
}
RLock lock = redissonClient.getLock("lock:key");
try {
lock.tryLock(timeout, TimeUnit.SECONDS);
// 业务逻辑
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 实践3: 使用锁模板减少重复代码
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
if (!acquired) {
throw new LockAcquisitionException("Failed to acquire lock: " + lockKey);
}
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockAcquisitionException("Interrupted", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 实践4: 分布式锁与事务结合使用
*/
@Transactional
public void lockWithTransaction(String resourceId) {
String lockKey = "resource:" + resourceId;
// 在事务外获取锁
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
// 在事务内执行数据库操作
Resource resource = resourceMapper.selectById(resourceId);
resource.setStatus("PROCESSING");
resourceMapper.updateById(resource);
// 事务提交后释放锁(在finally中)
} finally {
lock.unlock();
}
}
/**
* 实践5: 监控和告警
*/
public void lockWithMonitoring(String lockKey) {
long startTime = System.currentTimeMillis();
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (!acquired) {
// 记录锁获取失败
log.warn("Failed to acquire lock: {}", lockKey);
meterRegistry.counter("lock.acquire.failed", "lock", lockKey).increment();
return;
}
// 记录锁获取成功
meterRegistry.counter("lock.acquire.success", "lock", lockKey).increment();
// 业务逻辑
doBusinessLogic();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
long holdTime = System.currentTimeMillis() - startTime;
meterRegistry.timer("lock.hold.duration", "lock", lockKey)
.record(holdTime, TimeUnit.MILLISECONDS);
lock.unlock();
}
}
}
}
微服务架构中的注意事项:
1. 锁的粒度要合理
✅ 细粒度: 锁单个资源(如单个商品库存)
❌ 粗粒度: 锁整个服务(影响性能)
2. 避免跨服务锁
✅ 锁在服务内部使用
❌ 跨多个服务使用同一把锁(增加耦合)
3. 锁超时时间要合理
✅ 根据业务执行时间设置
❌ 设置过长(进程崩溃后锁很久才释放)
4. 监控锁的使用情况
✅ 记录锁获取成功率、持有时间
❌ 不监控(无法发现性能问题)
5. 异常处理要完善
✅ 确保锁在finally中释放
❌ 异常时锁未释放(导致死锁)
9.5 深入技术疑问
Q10: 分布式锁的CAP理论权衡是什么?如何选择?
疑问: 分布式锁在CAP理论中如何权衡一致性、可用性和分区容错性?
解答:
CAP理论在分布式锁中的应用:
┌─────────────────────────────────────────────────────────┐
│ CAP理论 │
├─────────────────────────────────────────────────────────┤
│ C (Consistency): 一致性 │
│ 所有节点在同一时刻看到相同的数据 │
│ │
│ A (Availability): 可用性 │
│ 系统持续可用,每个请求都能得到响应 │
│ │
│ P (Partition Tolerance): 分区容错性 │
│ 系统在网络分区时仍能继续工作 │
└─────────────────────────────────────────────────────────┘
分布式锁的CAP权衡:
Redis分布式锁:
- 选择: AP(可用性 + 分区容错性)
- 牺牲: C(一致性)
- 特点: 高可用、高性能,但可能出现不一致
ZooKeeper分布式锁:
- 选择: CP(一致性 + 分区容错性)
- 牺牲: A(可用性)
- 特点: 强一致性,但网络分区时可能不可用
实际场景选择:
/**
* CAP理论在分布式锁选择中的应用
*/
public class CAPTradeOff {
/**
* 场景1: 电商库存扣减(选择AP)
*
* 需求:
* - 高并发(10万+ QPS)
* - 可容忍短暂不一致(最终一致性)
* - 高可用(不能因为锁服务故障导致整个系统不可用)
*
* 选择: Redis分布式锁(AP)
*/
public void ecommerceInventory() {
// 使用Redis分布式锁
// 即使出现短暂不一致,也可以通过最终一致性保证正确性
}
/**
* 场景2: 金融交易(选择CP)
*
* 需求:
* - 强一致性(不能出现数据不一致)
* - 可容忍短暂不可用(一致性优先)
* - 并发量相对较低(1万+ QPS)
*
* 选择: ZooKeeper分布式锁(CP)
*/
public void financialTransaction() {
// 使用ZooKeeper分布式锁
// 即使网络分区导致不可用,也要保证一致性
}
/**
* 场景3: 配置管理(选择CP)
*
* 需求:
* - 配置必须一致(所有节点看到相同配置)
* - 可容忍短暂不可用
*
* 选择: ZooKeeper分布式锁(CP)
*/
public void configurationManagement() {
// 使用ZooKeeper分布式锁
}
/**
* 场景4: 缓存更新(选择AP)
*
* 需求:
* - 高可用(缓存服务不能影响主业务)
* - 可容忍短暂不一致(缓存可以重建)
*
* 选择: Redis分布式锁(AP)
*/
public void cacheUpdate() {
// 使用Redis分布式锁
}
}
Q11: 分布式锁和消息队列的区别是什么?什么时候用哪个?
疑问: 分布式锁和消息队列都能保证顺序执行,它们有什么区别?
解答:
分布式锁 vs 消息队列:
┌─────────────────────────────────────────────────────────┐
│ 分布式锁 │
├─────────────────────────────────────────────────────────┤
│ 用途: 互斥访问(同一时刻只有一个进程能执行) │
│ 模式: 同步阻塞 │
│ 场景: 防止重复操作、资源竞争 │
│ 示例: 防止重复订单、库存扣减 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 消息队列 │
├─────────────────────────────────────────────────────────┤
│ 用途: 异步处理、解耦、削峰填谷 │
│ 模式: 异步非阻塞 │
│ 场景: 任务队列、事件驱动、流量控制 │
│ 示例: 订单处理、邮件发送、日志收集 │
└─────────────────────────────────────────────────────────┘
使用场景对比:
/**
* 分布式锁 vs 消息队列使用场景
*/
public class LockVsQueue {
/**
* 场景1: 防止重复订单(使用分布式锁)
*
* 需求: 同一用户不能同时创建多个订单
* 特点: 需要立即返回结果,同步处理
*/
public Order createOrder(CreateOrderRequest request) {
String lockKey = "order:create:" + request.getUserId();
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
// 检查是否已存在订单
Order existingOrder = orderMapper.selectByRequestId(request.getRequestId());
if (existingOrder != null) {
return existingOrder;
}
// 创建订单
return createOrderInternal(request);
} finally {
lock.unlock();
}
}
/**
* 场景2: 订单处理(使用消息队列)
*
* 需求: 订单创建后异步处理(发送邮件、更新库存等)
* 特点: 不需要立即返回结果,异步处理
*/
public void processOrder(Order order) {
// 发送到消息队列
kafkaTemplate.send("order-created", order);
// 立即返回,不等待处理完成
}
/**
* 场景3: 库存扣减(使用分布式锁)
*
* 需求: 防止超卖,需要立即返回结果
*/
public void deductInventory(String productId, int quantity) {
String lockKey = "inventory:product:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
// 扣减库存,立即返回结果
Inventory inventory = inventoryMapper.selectById(productId);
if (inventory.getStock() >= quantity) {
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
}
} finally {
lock.unlock();
}
}
/**
* 场景4: 批量处理订单(使用消息队列)
*
* 需求: 批量处理订单,削峰填谷
*/
public void batchProcessOrders() {
// 订单发送到消息队列
List<Order> orders = getPendingOrders();
for (Order order : orders) {
kafkaTemplate.send("order-processing", order);
}
// 消费者批量处理
// @KafkaListener(topics = "order-processing")
// public void processOrder(Order order) { ... }
}
}
选择原则:
使用分布式锁:
✅ 需要互斥访问(同一时刻只有一个进程能执行)
✅ 需要立即返回结果
✅ 同步处理
✅ 防止重复操作
使用消息队列:
✅ 需要异步处理
✅ 需要解耦
✅ 需要削峰填谷
✅ 不需要立即返回结果
✅ 批量处理
总结
常见疑问快速参考
Q1: 为什么需要分布式锁?
A1: 单机锁无法跨JVM,分布式环境下需要跨进程协调
Q2: 分布式锁和数据库事务的区别?
A2: 事务保证单个请求内的原子性,分布式锁保证跨请求的互斥
Q3: 为什么用Lua脚本?
A3: 保证原子性,防止误释放其他进程的锁
Q4: 为什么需要看门狗?
A4: 自动续期,避免业务执行时间超过锁过期时间
Q5: RedLock真的完美吗?
A5: 不完美,存在时钟同步、网络延迟等问题
Q6: Redis锁会成为瓶颈吗?
A6: 通常不会,但需要合理设计锁粒度
Q7: ZooKeeper为什么更可靠?
A7: 强一致性保证,但性能较低
Q8: 什么时候该用分布式锁?
A8: 有共享资源竞争时,但要评估性能影响
Q9: 微服务中如何设计?
A9: 合理命名、设置超时、监控告警、异常处理
Q10: CAP理论如何权衡?
A10: Redis选择AP,ZooKeeper选择CP
Q11: 锁和消息队列的区别?
A11: 锁用于互斥访问,队列用于异步处理
希望这些疑问和解答能帮助你更深入地理解分布式锁!
分布式锁选择建议
选择建议:
1. 高并发、低一致性要求 → Redis / Redisson
2. 高一致性要求 → ZooKeeper / etcd
3. 简单场景、低并发 → 数据库锁
4. 需要可重入、公平锁 → Redisson
5. 需要读写锁 → Redisson
6. 需要高可用 → RedLock / ZooKeeper集群
核心要点
✅ 必须设置锁过期时间(防止死锁)
✅ 释放锁时验证value(防止误释放)
✅ 使用Lua脚本保证原子性
✅ 合理设置锁超时时间
✅ 监控锁的使用情况
✅ 异常情况下确保锁释放
参考资源:
804

被折叠的 条评论
为什么被折叠?



