Skip to content

分布式锁

分布式锁应满足

  1. 互斥性:同一时刻只有一个客户端能持有锁
  2. 可重入:同一客户端可多次获取锁
  3. 死锁避免:客户端崩溃后能自动释放锁
  4. 公平性:按请求顺序获取锁

Redis 实现

简单实现

java
public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public boolean lock(String key, String value, long expireTime) {
        return Boolean.TRUE.equals(
            redisTemplate.opsForValue().setIfAbsent(key, value,
                expireTime, TimeUnit.SECONDS));
    }

    public void unlock(String key, String value) {
        String currentValue = (String) redisTemplate.opsForValue().get(key);
        if (value.equals(currentValue)) {
            redisTemplate.delete(key);
        }
    }
}

Lua 脚本保证原子性

java
public class RedisLock {
    private static final String UNLOCK_SCRIPT =
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "    return redis.call('del', KEYS[1]) " +
        "else " +
        "    return 0 " +
        "end";

    public void unlock(String key, String value) {
        redisTemplate.execute(
            new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class),
            Collections.singletonList(key),
            value
        );
    }
}

完整实现

java
@Service
public class DistributedLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public String lock(String key, long expireTime, long retryTimes, long retryInterval) {
        String value = UUID.randomUUID().toString();
        for (int i = 0; i < retryTimes; i++) {
            Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(success)) {
                return value;
            }
            try {
                Thread.sleep(retryInterval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return null;
    }

    public boolean 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";
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(key), value
        );
        return result != null && result == 1;
    }
}

ZooKeeper 实现

java
public class ZkLock {
    private static final String LOCK_PATH = "/dist-lock";

    public String acquireLock(String lockName) {
        String path = LOCK_PATH + "/" + lockName;
        try {
            String shortUUID = UUID.randomUUID().toString();
            String fullPath = zkClient.createEphemeralSequential(path, shortUUID);
            return fullPath;
        } catch (Exception e) {
            return null;
        }
    }

    public boolean tryLock(String lockName, long timeout) {
        String lockPath = acquireLock(lockName);
        if (lockPath == null) return false;

        // 获取所有子节点并排序
        List<String> children = zkClient.getChildren(LOCK_PATH);
        Collections.sort(children);

        String shortPath = lockPath.substring(lockPath.lastIndexOf("/") + 1);
        int index = children.indexOf(shortPath);

        // 如果是最小节点,获取锁成功
        if (index == 0) {
            return true;
        }

        // 等待前一个节点删除
        String waitPath = children.get(index - 1);
        CountDownLatch latch = new CountDownLatch(1);

        zkClient.subscribeChildChanges(LOCK_PATH + "/" + waitPath,
            (parentPath, currentChilds) -> {
                if (!currentChilds.contains(waitPath)) {
                    latch.countDown();
                }
            });

        latch.await(timeout, TimeUnit.MILLISECONDS);
        zkClient.unsubscribeChildChanges(LOCK_PATH + "/" + waitPath, null);
        return true;
    }

    public void unlock(String lockPath) {
        zkClient.delete(lockPath);
    }
}

Redisson 分布式锁

java
@Autowired
private RedissonClient redisson;

public void doSomething() {
    RLock lock = redisson.getLock("my-lock");
    try {
        // 等待锁最多10秒,锁定后10分钟自动释放
        if (lock.tryLock(10, 600, TimeUnit.SECONDS)) {
            // 业务逻辑
        }
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

总结

实现方式优点缺点
Redis性能高锁过期时间难确定
ZooKeeper可靠性高性能较低
Redisson功能完善依赖额外组件

基于 VitePress 构建