分布式红锁的加锁的lua底层设计原理

本文深入探讨分布式红锁的Lua底层设计原理,通过Redis实现的重入锁机制,详细解释了如何通过检查UUID和线程ID来确保锁的唯一性和重入性,以及锁的获取和释放流程。

分布式红锁的加锁的lua底层设计原理

提前做2个动作:
1.先把3台 redis key全部清空(为了不受debug干扰,必须先删除锁)
127.0.0.1:6379> flushdb
OK

2.isLock = redLock.tryLock(1000530, 1000605*30, TimeUnit.MILLISECONDS);

debug的断点 断在 RedissonLock.tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command)

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);

    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
              "if (redis.call('exists', KEYS[1]) == 0) then " + //key不存在,说明前面没人拿到锁,太幸运了
                  "redis.call('hset', KEYS[1], ARGV[2], 1); " + //执行hset MY_REDLOCK UUID+threadId 1 的命令
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " + //执行pexpire MY_REDLOCK设置锁的租约时间即过期时间。
                  "return nil; " +                              //返回nil,代表我已经拿到锁了
              "end; " +
              
              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +//判断是不是我自己拿到了锁
                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +       //如果是我自己就能进来,为我自己加1
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +          //再重新设置过期时间
                  "return nil; " +                                       //返回nil,代表我已经拿到锁了
              "end; " +
              //为以上if这段代码,提出2个疑问?
              //1.为什么是我自己(UUID+threadId,hash存在)就能进去?因为我上次已经拿到了锁,所以我就能进来,即重入锁,故红锁也是一个重入锁。
              //2.为什么我进去后,要给自己加1?这就是重入锁的原理了,要记录锁重入的次数,然后再次return nil,代表再次拿到锁。
              
              //返回key的剩余过期时间,前面2个if都是返回nil代表拿锁成功,这里返回剩余过期时间代表了锁已经被别人拿走,
              //为什么要给剩余时间?就是告诉你锁已经在别人那里了,你按我的是个时间在等待吧
              "return redis.call('pttl', KEYS[1]);",
                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

KEYS[1]:Collections.singletonList(getName()),为分布式锁的key,即MY_REDLOCK
ARGV[1]:internalLockLeaseTime 为锁的租约时间,即过期时间
ARGV[2]:getLockName(threadId)(4218379f-849d-498f-8c28-56f3fce42f71:52)代表了redis连接id+线程id,
即UUID+threadId,组成锁的获取者,就是谁拿到了该锁。

### 三层结构解析:Redis 分布式的实现原理底层机制 Redis 分布式是一种在分布式系统中实现资源互斥访问的机制。其核心目标是确保在多个节点或服务实例同时访问共享资源时,只有一个客户端能够持有,从而避免数据竞争和状态不一致问题。Redis 之所以适合实现分布式,是因为它具备高性能、单线程执行命令的特性,以及支持原子操作,这些都为分布式的实现提供了基础。 #### 基本命令与机制 Redis 实现分布式的核心依赖于 `SETNX`(Set if Not Exists)命令,该命令用于设置一个键值对,但仅在键不存在时才执行成功。这一特性可以用于判断是否能够成功获取。例如,当一个客户端尝试设置一个键(如 `lock:resource`)时,如果该键尚未存在,则表示该客户端成功获取;否则,说明已被其他客户端持有。 此外,为了防止被永久占用(例如持有的客户端崩溃或网络中断),Redis 通常会结合 `EXPIRE` 命令为键设置一个过期时间。这样即使客户端未能主动释放也会在一段时间后自动失效,从而避免死的发生。 Redis 2.6.12 及以上版本引入了 `SET` 命令的扩展形式,支持在单条命令中完成设置键值、设置过期时间以及判断键是否存在,其语法为 `SET key value NX PX milliseconds`。这种方式将设置键和设置过期时间合并为一个原子操作,避免了在 `SETNX` 和 `EXPIRE` 分开执行时可能出现的竞态条件。 #### 可重入性与续期 Redisson 是一个基于 Redis 的 Java 客户端库,它在 Redis 原生功能的基础上封装了更高级的分布式功能,包括可重入续期等特性。可重入性意味着同一个客户端在已经持有的情况下,可以再次获取该而不会被阻塞。Redisson 通过记录的持有次数来实现这一功能。当客户端多次获取同一把时,Redisson 会增加内部计数器;而每次释放时,计数器递减,直到计数器归零时才真正释放续期机制则通过 Watchdog 机制实现。当客户端成功获取后,Redisson 会启动一个后台线程定期检查的状态,并在即将过期时自动延长其过期时间。这种方式有效防止了因业务逻辑执行时间过长而导致的提前释放问题。 #### 主从架构下的问题 尽管 Redisson 在大多数场景下都能提供稳定可靠的分布式实现,但在主从架构下仍存在一些挑战。例如,在 Redis 主从复制模式中,客户端可能在主节点上加锁后,信息尚未同步到从节点时,主节点发生故障并切换到从节点。此时,从节点上并未持有该,其他客户端可能在新的主节点上重新获取该,从而导致的失效和数据竞争问题。 这一问题源于 Redis 的异步复制机制,无法保证信息在主从节点之间的强一致性。为了解决这一问题,可以采用 Redlock 算法,该算法通过在多个独立的 Redis 实例上同时加锁,提高的可靠性和一致性。然而,Redlock 算法也带来了更高的复杂性和性能开销。 #### 示例代码:基于 Redis 的简单分布式实现 以下是一个基于 Redis 原生命令实现的简单分布式示例,使用 Python 的 `redis-py` 客户端库: ```python import redis import time import uuid def acquire_lock(conn, lock_name, acquire_timeout=10): identifier = str(uuid.uuid4()) end = time.time() + acquire_timeout while time.time() < end: if conn.set(lock_name, identifier, nx=True, ex=10): return identifier elif not conn.ttl(lock_name): conn.expire(lock_name, 10) time.sleep(0.001) return False def release_lock(conn, lock_name, identifier): unlock_script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ return conn.eval(unlock_script, 1, lock_name, identifier) # 示例使用 r = redis.StrictRedis(host='localhost', port=6379, db=0) lock_identifier = acquire_lock(r, "lock:resource") if lock_identifier: try: # 执行业务逻辑 print("Lock acquired, doing work...") time.sleep(5) finally: release_lock(r, "lock:resource", lock_identifier) else: print("Could not acquire lock.") ``` 上述代码通过 `SET` 命令实现的获取,并结合 Lua 脚本确保释放时的原子性,防止误删其他客户端的。 #### 高级封装:Redisson 的实现优势 Redisson 在底层仍然基于 Redis 的 `SET` 命令和 Lua 脚本实现机制,但它进一步封装了可重入性、续期、公平、联等高级特性。Redisson 使用 Netty 框架实现非阻塞 I/O 操作,并通过 Redis 的发布/订阅机制处理的自动续期。这种设计不仅简化了开发者的使用成本,也提高了机制的稳定性和性能。 Redisson 的实现还支持多种类型,如: - **可重入(Reentrant Lock)**:支持同一个线程多次获取同一把。 - **公平(Fair Lock)**:按照请求顺序分配。 - **读写(ReadWriteLock)**:支持多个读操作同时进行,但写操作互斥。 - **联(MultiLock)**:跨多个 Redisson 实例的组合。 这些高级特性使得 Redisson 成为构建高并发分布式系统时的理想选择。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值