返回

解锁分布式锁之谜:从零打造你的 Java 分布式锁

见解分享

分布式系统中,并发访问和数据一致性带来的挑战,让锁机制显得尤为重要。然而,传统锁在分布式环境中却显得力不从心,分布式锁应运而生。在这篇教程中,我们将深入浅出地剖析分布式锁的原理,并手把手带你从零实现属于你自己的 Java 分布式锁。

分布式锁简介

分布式锁是一种确保在分布式系统中,同一时刻只有一个线程或进程能访问临界区(critical section)的机制。它与传统锁相似,但其复杂度和挑战性更高,涉及到网络延迟、节点故障和分布式共识等问题。

Redis 分布式锁

Redis 作为一种高性能的分布式键值存储数据库,凭借其 SETNX(Set if Not eXists)命令和 EXPIRE 命令,为实现分布式锁提供了坚实的基础。

原理剖析

  1. 使用 SETNX 命令设置一个唯一的键,并将值设置为当前时间戳加锁的过期时间。如果成功设置,则获取锁。
  2. 在临界区执行操作后,使用 EXPIRE 命令为锁设置一个过期时间,保证在锁过期后自动释放。
  3. 在获取锁之前,需要使用 EVAL 命令评估一个 Lua 脚本,该脚本实现了 CAS(比较并交换)操作,以确保只有一个线程或进程能成功获取锁。

Java 代码实现

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.UUID;

public class RedisDistributedLock {

    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_EXPIRE_TIME = 30000; // 30 秒

    private JedisPool jedisPool;

    public RedisDistributedLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public boolean lock(String key) {
        return lock(key, DEFAULT_EXPIRE_TIME);
    }

    public boolean lock(String key, long expireTime) {
        String value = UUID.randomUUID().toString();
        try (Jedis jedis = jedisPool.getResource()) {
            long startTime = System.currentTimeMillis();
            String script = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
                    "redis.call('expire', KEYS[1], ARGV[2]); " +
                    "return 1; " +
                    "else " +
                    "return 0; " +
                    "end;";
            while ((System.currentTimeMillis() - startTime) < expireTime) {
                Object result = jedis.eval(script, 1, new String[]{LOCK_PREFIX + key, value}, 2, new String[]{String.valueOf(expireTime)});
                if (result != null && (long) result == 1) {
                    return true;
                }
                Thread.sleep(100);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public void unlock(String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "redis.call('del', KEYS[1]); " +
                    "return 1; " +
                    "else " +
                    "return 0; " +
                    "end;";
            Object result = jedis.eval(script, 1, new String[]{LOCK_PREFIX + key}, 2, new String[]{value});
            if (result != null && (long) result == 1) {
                System.out.println("解锁成功");
            } else {
                System.out.println("解锁失败,锁不存在或已过期");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        JedisPool jedisPool = new JedisPool();
        RedisDistributedLock lock = new RedisDistributedLock(jedisPool);

        String key = "my-lock";
        boolean locked = lock.lock(key);
        if (locked) {
            System.out.println("获取锁成功");
            try {
                // 执行临界区操作
                System.out.println("执行临界区操作...");
            } finally {
                lock.unlock(key);
            }
        } else {
            System.out.println("获取锁失败");
        }

        jedisPool.close();
    }
}

注意事项

  • 确保锁的过期时间设置合理,避免死锁。
  • 采用幂等机制,在释放锁之前重新检查锁的归属,避免误释放。
  • 在分布式环境中,需要考虑网络延迟和节点故障的影响,采取容错措施。

结语

通过这篇教程,你已经掌握了从零实现 Java 分布式锁的知识。分布式锁在保证并发安全和数据一致性方面发挥着至关重要的作用。理解其原理并将其应用到实际项目中,将显著提升你的分布式系统开发能力。