前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis从入门到放弃(10):分布式锁

Redis从入门到放弃(10):分布式锁

作者头像
夕阳也是醉了
发布2023-10-16 09:28:17
2990
发布2023-10-16 09:28:17
举报
文章被收录于专栏:夕阳醉了夕阳醉了

在分布式系统中,实现对共享资源的安全访问是一个关键问题。Redis作为一种高性能的内存数据库,提供了多种方式来实现分布式锁,以解决多个节点之间对共享资源的并发访问问题。

本文将介绍五种Redis分布式锁的解决方案及其原理、应用场景以及Java代码的实现步骤。

1、SETNX

SETNX(SET if Not eXists)命令是Redis提供的一种原子操作,用于设置一个键值对,当键不存在时才设置成功。利用该命令可以实现基本的分布式锁。

代码语言:javascript
复制
SETNX lock_name true

1.1、原理

  • 当节点需要获取锁时,它尝试通过 SETNX 命令设置一个特定的键作为锁。
  • 如果设置成功,表示获取到了锁,可以执行访问共享资源的操作。
  • 用完共享资源使用 DEL 命令释放锁。

1.2、代码实现

代码语言:javascript
复制
Jedis jedis = new Jedis("localhost", 6379);
String lockKey = "my_distributed_lock";
String identifier = UUID.randomUUID().toString();

boolean lockAcquired = jedis.setnx(lockKey, identifier) == 1;
if (lockAcquired) {
    // 成功获取锁
    // 执行访问共享资源的操作
    // ...
    jedis.del(lockKey); // 释放锁
} 

1.3、缺点

死锁:可能因网络等原因 DEL 命令执行失败而造成锁无法释放。

2、SET(NX EX)

避免死锁解决方案是为锁设置一个TTL(Time To Live)。

SET 命令支持选项:

NX:表示只在键不存在时才设置。

EX:表示设置键的过期时间(秒)。

代码语言:javascript
复制
SET lock_name arbitrary_lock_value NX EX 10

在上面的命令中,NX 与 SETNX 中的含义相同,而 EX 10 表示 TTL 为 10 秒。

2.1、原理

  • 当节点需要获取锁时,它通过SET命令的(NX EX)选项尝试设置一个特定的键作为锁,并设置锁的过期时间。
  • 如果设置成功,表示获取到了锁,可以执行访问共享资源的操作。

2.2、代码实现

代码语言:javascript
复制
import redis.clients.jedis.Jedis;

public class DistributedLockExample {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        String lockKey = "my_distributed_lock";
        String identifier = "unique_id"; // Replace with a unique identifier for your node
        int lockTimeout = 5000; // Lock expiration time in milliseconds

        // Try to acquire the lock
        String result = jedis.set(lockKey, identifier, "NX", "EX", lockTimeout / 1000);

        if (result != null && result.equals("OK")) {
            try {
                // Successfully acquired the lock
                System.out.println("Lock acquired, performing the task.");
            } finally {
                // Release the lock
                jedis.del(lockKey);
            }
        } else {
            // Failed to acquire the lock
            System.out.println("Failed to acquire the lock.");
        }
        jedis.close();
    }
}

2.3、缺点

线程A获得锁,在执行业务还未完成时TTL过期,线程A锁被释放,线程B获得锁。线程A后续为无锁执行业务,在线程A完成业务后,执行DE删除锁,因为KEY相同则会导致线程B的锁被删除,线程B后续操作则为无锁执行业务

如下图所示:

3、SET(NX EX) + 唯一ID

在设置key时,客户端应将唯一的 ID 添加到 kv 对。在删除key之前,检查这个 ID 以确定它是否仍然持有锁。如果 ID 不匹配,则表示该锁被其他客户端持有,当前客户端不应删除该key。

代码语言:javascript
复制
SET lock_name client_id NX EX 10

3.1、原理

  • 当节点需要获取锁时,它通过 SET 命令的 (NX EX) 选项设置一个特定的键作为锁,并设置锁的过期时间。
  • 在设置锁的同时,还设置一个唯一的标识(如uuid)为锁的值。
  • 当节点释放锁时,只有当锁的值与自己的标识相匹配时才释放锁。

3.2、代码实现

代码语言:javascript
复制
import redis.clients.jedis.Jedis;

public class DistributedLockExample {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        String lockKey = "my_distributed_lock";
        String identifier = UUID.randomUUID().toString();
        int lockTimeout = 30000; // Lock expiration time in milliseconds

        // Try to acquire the lock
        String result = jedis.set(lockKey, identifier, "NX", "EX", lockTimeout / 1000);

        if (result != null && result.equals("OK")) {
            try {
                // Successfully acquired the lock
                System.out.println("Lock acquired, performing the task.");
            } finally {
                // Release the lock (if acquired by the same identifier)
                String currentValue = jedis.get(lockKey);
                if (currentValue != null && currentValue.equals(identifier)) {
                    jedis.del(lockKey);
                }
            }
        } else {
            // Failed to acquire the lock
            System.out.println("Failed to acquire the lock.");
        }

        jedis.close();
    }
}

3.3、缺点

代码语言:javascript
复制
if (currentValue != null && currentValue.equals(identifier)) {
	jedis.del(lockKey);
}

IF 条件和 DEL 是两个独立的操作。极端情况下,IF 条件判定可以释放锁,在执行删除锁操作前刚好TTL过期,其他线程获取锁执行,前者线程删除锁删除的依然是别的线程的锁。

4、SET(NX EX) + 唯一ID + Lua脚本

为了确保释放锁的原子性,可以使用Lua脚本来执行判断+删除操作(lua脚本的执行的原子的)。

lua脚本:

代码语言:javascript
复制
// 如果来自 Redis GET 操作的值等于传入的值,则删除键
if redis.call("get", "lock_name") == ARGV[1]
  then
    return redis.call("del", "lock_name")
  else
    return 0
end

4.1、代码实现

代码语言:javascript
复制
String lockKey = "my_distributed_lock";
String identifier = UUID.randomUUID().toString();

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = (Long) jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(identifier));

if (result == 1) {
    // 成功释放锁
} else {
    // 锁的标识不匹配,可能已被其他节点获取
}

5、Redisson

以上的方案还存在一个问题:不优雅。

若执行业务完成时间不固定,可能小于过期时间也可能大于过期时间,在代码中只能设置可能完成的预估最长时间。

5.1、开源解决方案: Redisson

一旦客户端持有锁,它就会启动一个WatchDog守护线程来定期检查锁是否存在。如果存在,守护线程将重置 TTL 以防止锁自动释放。这种策略被称为租赁策略。防止先于业务完成就释放锁。

5.2、代码实现

代码语言:javascript
复制
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

RLock lock = redisson.getLock("my_distributed_lock");

try {
    boolean lockAcquired = lock.tryLock(acquireTimeout, lockTimeout, TimeUnit.MILLISECONDS);
    if (lockAcquired) {
        // 成功获取锁
        // 执行访问共享资源的操作
        // ...
    } else {
        // 获取锁失败,处理重试逻辑
    }
} finally {
    lock.unlock(); // 释放锁
}

6、总结

Redis分布式锁提供了多种解决方案,可以根据具体的应用场景和需求选择合适的方案。无论选择哪种方案,都需要考虑锁的安全性、原子性、续约和释放等问题,以确保在分布式环境中共享资源的安全访问。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-08-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、SETNX
    • 1.1、原理
      • 1.2、代码实现
        • 1.3、缺点
        • 2、SET(NX EX)
          • 2.1、原理
            • 2.2、代码实现
              • 2.3、缺点
              • 3、SET(NX EX) + 唯一ID
                • 3.1、原理
                  • 3.2、代码实现
                    • 3.3、缺点
                    • 4、SET(NX EX) + 唯一ID + Lua脚本
                      • 4.1、代码实现
                      • 5、Redisson
                        • 5.1、开源解决方案: Redisson
                          • 5.2、代码实现
                          • 6、总结
                          相关产品与服务
                          云数据库 Redis
                          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档