Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis分布式锁实现方式

基于Redis实现分布式锁的三种方式

作者:断手当码农

在现代分布式系统中,多个节点同时操作共享资源是常见的情况,因此,分布式锁成为了确保分布式系统中各节点协调一致、避免资源冲突的一个重要工具,本文将介绍三种常用的分布式锁实现方式,需要的朋友可以参考下

引言

在现代分布式系统中,多个节点同时操作共享资源是常见的情况。这种并发访问如果不加以控制,可能会导致数据不一致、业务异常等问题。因此,分布式锁成为了确保分布式系统中各节点协调一致、避免资源冲突的一个重要工具。本文将介绍三种常用的分布式锁实现方式:基于 Redis 的 SETNX 锁实现基于 Redisson 实现的分布式锁以及 使用 Redis Lua 脚本的分布式锁实现

什么是分布式锁?

分布式锁是一种在分布式系统中,确保同一时刻只有一个节点能够访问共享资源的机制。与传统的单机锁(如 synchronized)不同,分布式锁跨越多个机器、节点,通过一个外部协调者来管理锁。常见的分布式锁实现工具包括 RedisZooKeeperConsul 等。

分布式锁广泛应用于以下场景:

1. 基于 RedisSETNX实现分布式锁

Redis 是最常见的分布式锁实现工具之一。我们可以通过 Redis 的 SETNX 命令来实现一个基本的分布式锁。SETNX 命令的作用是 只有在键不存在时设置该键的值,因此可以用来确保在同一时刻只有一个节点能够获取锁。

实现步骤:

  1. 使用 SETNX 命令尝试获取锁(设置某个键值对)。
  2. 如果获取成功,表示该节点获得了锁,可以进行后续操作。
  3. 如果获取失败,表示锁已被其他节点占用,当前节点需要等待或重试。
  4. 设置锁的过期时间,防止死锁的发生。

基于SETNX实现代码(Java)

import redis.clients.jedis.Jedis;
import java.util.UUID;
 
public class RedisDistributedLock {
    private static final String LOCK_KEY = "lock:resource";  // 锁的唯一标识
    private static final int EXPIRE_TIME = 10;  // 锁的超时时间,单位:秒
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
 
    private Jedis jedis;
 
    public RedisDistributedLock() {
        jedis = new Jedis(REDIS_HOST, REDIS_PORT);
    }
 
    // 获取锁
    public boolean acquireLock() {
        String lockValue = UUID.randomUUID().toString();  // 唯一的锁值,防止锁被误释放
 
        // 使用 SETNX 命令获取锁
        Long result = jedis.setnx(LOCK_KEY, lockValue);
        
        if (result == 1) {
            // 锁获取成功,设置过期时间
            jedis.expire(LOCK_KEY, EXPIRE_TIME);
            return true;
        }
 
        // 锁未获取成功
        return false;
    }
 
    // 释放锁
    public boolean releaseLock() {
        String lockValue = jedis.get(LOCK_KEY);
 
        if (lockValue != null && lockValue.equals(jedis.get(LOCK_KEY))) {
            // 确保当前锁是自己持有的
            jedis.del(LOCK_KEY);
            return true;
        }
 
        return false;
    }
 
    // 关闭连接
    public void close() {
        jedis.close();
    }
 
    public static void main(String[] args) {
        RedisDistributedLock lock = new RedisDistributedLock();
        
        // 尝试获取锁
        if (lock.acquireLock()) {
            try {
                System.out.println("Lock acquired, performing task.");
                // 执行任务...
                Thread.sleep(5000);  // 模拟任务处理时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.releaseLock();  // 完成任务后释放锁
                System.out.println("Lock released.");
            }
        } else {
            System.out.println("Failed to acquire lock, try again later.");
        }
 
        lock.close();
    }
}

关键点:

2. 使用 Redisson 实现分布式锁

Redisson 是基于 Redis 提供的高层次 Java 客户端,它简化了分布式锁的实现。Redisson 提供了 RLock 接口来管理分布式锁,使得开发者无需手动处理底层的细节。

Redisson 的优势在于其高效性和易用性,能够自动处理锁的过期、重试、续期等功能。

Redisson 分布式锁代码示例

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.api.Redisson;
import org.redisson.config.Config;
 
public class RedissonLockExample {
    public static void main(String[] args) {
        // 配置 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379"); // 设置 Redis 地址
        RedissonClient redisson = Redisson.create(config);  // 创建 Redisson 客户端
 
        // 获取分布式锁
        RLock lock = redisson.getLock("lock:resource");
 
        try {
            // 尝试获取锁
            if (lock.tryLock()) {
                System.out.println("Lock acquired, performing task.");
                // 执行任务...
                Thread.sleep(5000);  // 模拟任务处理
            } else {
                System.out.println("Failed to acquire lock, try again later.");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
            redisson.shutdown();  // 关闭 Redisson 客户端
            System.out.println("Lock released.");
        }
    }
}

关键点:

3. 使用 Lua 脚本实现分布式锁

在 Redis 中,Lua 脚本能够确保多条 Redis 命令的原子性执行。通过 Lua 脚本,我们可以把获取锁、设置过期时间和释放锁等操作合并成一个原子操作,避免了竞态条件问题。

import redis.clients.jedis.Jedis;
 
public class RedisDistributedLockWithLua {
    private static final String LOCK_KEY = "lock:resource";
    private static final int EXPIRE_TIME = 10;  // 锁的超时时间,单位:秒
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
 
    private Jedis jedis;
 
    public RedisDistributedLockWithLua() {
        jedis = new Jedis(REDIS_HOST, REDIS_PORT);
    }
 
    // 获取锁
    public boolean acquireLock(String lockValue) {
        // Lua 脚本:尝试获取锁,如果成功则设置锁的过期时间
        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";
 
        // 使用 EVAL 命令执行 Lua 脚本
        Object result = jedis.eval(script, 1, LOCK_KEY, lockValue, String.valueOf(EXPIRE_TIME));
 
        return "1".equals(result.toString());
    }
 
    // 释放锁
    public boolean releaseLock(String lockValue) {
        // Lua 脚本:确保当前锁的持有者才会释放锁
        String script = 
            "if redis.call('GET', KEYS[1]) == ARGV[1] then " +
            "   return redis.call('DEL', KEYS[1]) " +
            "else " +
            "   return 0 " +
            "end";
 
        // 使用 EVAL 命令执行 Lua 脚本
        Object result = jedis.eval(script, 1, LOCK_KEY, lockValue);
        
        return "1".equals(result.toString());
    }
 
    // 关闭连接
    public void close() {
        jedis.close();
    }
 
    public static void main(String[] args) {
        RedisDistributedLockWithLua lock = new RedisDistributedLockWithLua();
        String lockValue = "unique-lock-value";  // 唯一的锁值,用于标识当前锁的拥有者
 
        // 尝试获取锁
        if (lock.acquireLock(lockValue)) {
            try {
                System.out.println("Lock acquired, performing task.");
                // 执行任务...
                Thread.sleep(5000);  // 模拟任务处理
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.releaseLock(lockValue);  // 完成任务后释放锁
                System.out.println("Lock released.");
            }
        } else {
            System.out.println("Failed to acquire lock, try again later.");
        }
 
        lock.close();
    }
}

解释 Lua 脚本:

  1. 获取锁SETNX 命令保证只有一个客户端能成功设置锁,同时使用 EXPIRE 设置锁的过期时间。
  2. 释放锁:通过 Lua 脚本确认当前客户端持有锁,防止其他客户端误释放锁。

对比三种 Redis 分布式锁实现方式

在分布式系统中,我们可以通过 Redis 实现分布式锁,保证多个节点在访问共享资源时的互斥性。本文介绍了三种常见的分布式锁实现方式:基于 Redis SETNX 命令的锁基于 Redisson 实现的锁、以及 基于 Redis Lua 脚本的锁。每种实现方式有其优缺点,根据系统的不同需求,开发者可以选择合适的方式。接下来,我们将对这三种方式进行对比,帮助大家做出更明智的选择。

1. 基于 RedisSETNX命令实现分布式锁

实现原理:

使用 Redis 的 SETNX(SET if Not eXists)命令来设置一个唯一的锁键,如果该键不存在,表示成功获取锁;如果该键已经存在,表示锁已被占用。然后,使用 EXPIRE 命令为锁设置过期时间,防止死锁。

优点:

缺点:

2. 使用 Redisson 实现分布式锁

实现原理:

Redisson 是基于 Redis 提供的高层次 Java 客户端,它通过 RLock 接口来管理分布式锁。Redisson 提供了更高层次的 API,自动处理锁的超时、重试、续期等问题。

优点:

缺点:

3. 使用 Redis Lua 脚本实现分布式锁

实现原理:

Lua 脚本可以在 Redis 服务器端原子地执行多个命令。通过 Lua 脚本,我们将获取锁、设置过期时间和释放锁的操作合并为一个原子操作,避免了竞争条件的发生。

优点:

缺点:

三者对比

特性SETNX 锁实现Redisson 锁实现Lua 脚本锁实现
实现复杂度简单简单,依赖 Redisson稍复杂,需编写 Lua 脚本
原子性低(需要分两步操作)高(自动处理锁的生命周期)高(所有操作在 Redis 上原子执行)
支持锁续期否(需手动在脚本中实现续期)
锁释放保障需要手动确认锁值自动释放,且可靠性高需要手动确认锁值
性能较高较高,但有额外封装性能开销非常高(减少了网络延迟)
依赖仅需 Redis 客户端需要引入 Redisson 库仅需 Redis 客户端,使用 Lua 脚本
适用场景简单场景,低并发需求高并发,自动续期,可靠性需求高性能、低延迟需求,且能接受脚本复杂性
错误处理容易处理易于使用且错误处理简洁错误处理较为复杂

总结

根据实际需求,开发者可以选择不同的分布式锁实现方式:

选择合适的分布式锁实现方式,能够有效保证系统的一致性和高可用性。

以上就是基于Redis实现分布式锁的三种方式的详细内容,更多关于Redis分布式锁实现方式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文