2025-02-28 16:00:16 +08:00
|
|
|
|
// Package luascript defines the lua script used for redis distributed lock
|
|
|
|
|
|
package luascript
|
|
|
|
|
|
|
2025-03-04 16:33:35 +08:00
|
|
|
|
// RLockScript is the lua script for the lock read lock command
|
2025-02-28 16:00:16 +08:00
|
|
|
|
/*
|
2025-03-07 16:16:26 +08:00
|
|
|
|
KEYS[1]:锁的键名(key),通常是锁的唯一标识。
|
|
|
|
|
|
KEYS[2]:锁的超时键名前缀(rwTimeoutPrefix),用于存储每个读锁的超时键。
|
|
|
|
|
|
ARGV[1]:锁的过期时间(lockLeaseTime),单位为秒。
|
|
|
|
|
|
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
2025-02-28 16:00:16 +08:00
|
|
|
|
*/
|
2025-03-04 16:33:35 +08:00
|
|
|
|
var RLockScript = `
|
|
|
|
|
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
2025-03-05 16:42:59 +08:00
|
|
|
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
2025-02-28 16:00:16 +08:00
|
|
|
|
if (mode == false) then
|
|
|
|
|
|
redis.call('hset', KEYS[1], 'mode', 'read');
|
|
|
|
|
|
redis.call('hset', KEYS[1], lockKey, '1');
|
2025-04-18 15:17:51 +08:00
|
|
|
|
redis.call('hpexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
|
|
|
|
|
redis.call('pexpire', KEYS[1], ARGV[1]);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
return 1;
|
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
if (mode == 'write') then
|
2025-03-06 16:35:36 +08:00
|
|
|
|
-- 放到 list 中等待写锁释放后再次尝试加锁并且订阅写锁释放的消息
|
|
|
|
|
|
local waitKey = KEYS[1] .. ':read';
|
|
|
|
|
|
redis.call('rpush', waitKey, ARGV[2]);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
return -1;
|
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
if (mode == 'read') then
|
|
|
|
|
|
if (redis.call('exists', KEYS[1], ARGV[2]) == 1) then
|
|
|
|
|
|
redis.call('hincrby', KEYS[1], lockKey, '1');
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local remainTime = redis.call('hpttl', KEYS[1], 'fields', '1', lockKey);
|
|
|
|
|
|
redis.call('hpexpire', KEYS[1], math.max(tonumber(remainTime[1]), ARGV[1]), 'fields', '1', lockKey);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
else
|
|
|
|
|
|
redis.call('hset', KEYS[1], lockKey, '1');
|
2025-04-18 15:17:51 +08:00
|
|
|
|
redis.call('hpexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
|
|
|
|
|
local cursor = 0;
|
|
|
|
|
|
local maxRemainTime = tonumber(ARGV[1]);
|
|
|
|
|
|
local pattern = KEYS[2] .. ':*';
|
|
|
|
|
|
repeat
|
|
|
|
|
|
local hscanResult = redis.call('hscan', KEYS[1], cursor, 'match', pattern, 'count', '100');
|
|
|
|
|
|
cursor = tonumber(hscanResult[1]);
|
|
|
|
|
|
local fields = hscanResult[2];
|
|
|
|
|
|
|
|
|
|
|
|
for i = 1, #fields,2 do
|
|
|
|
|
|
local field = fields[i];
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local remainTime = redis.call('hpttl', KEYS[1], 'fields', '1', field);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
maxRemainTime = math.max(tonumber(remainTime[1]), maxRemainTime);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
|
|
|
|
|
until cursor == 0;
|
|
|
|
|
|
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local remainTime = redis.call('pttl', KEYS[1]);
|
|
|
|
|
|
redis.call('pexpire', KEYS[1], math.max(tonumber(remainTime),maxRemainTime));
|
2025-02-28 16:00:16 +08:00
|
|
|
|
return 1;
|
|
|
|
|
|
end;
|
|
|
|
|
|
`
|
|
|
|
|
|
|
2025-03-04 16:33:35 +08:00
|
|
|
|
// UnRLockScript is the lua script for the unlock read lock command
|
2025-02-28 16:00:16 +08:00
|
|
|
|
/*
|
|
|
|
|
|
KEYS[1]:锁的键名(key),通常是锁的唯一标识。
|
2025-03-04 16:33:35 +08:00
|
|
|
|
KEYS[2]:锁的超时键名前缀(rwTimeoutPrefix),用于存储每个读锁的超时键。
|
2025-04-03 17:22:40 +08:00
|
|
|
|
KEYS[3]:锁的释放通知写频道(chankey),用于通知其他写等待客户端锁已释放。
|
2025-02-28 16:00:16 +08:00
|
|
|
|
ARGV[1]:解锁消息(unlockMessage),用于通知其他客户端锁已释放。
|
|
|
|
|
|
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
|
|
|
|
|
*/
|
2025-03-04 16:33:35 +08:00
|
|
|
|
var UnRLockScript = `
|
2025-03-05 16:42:59 +08:00
|
|
|
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
2025-02-28 16:00:16 +08:00
|
|
|
|
if (mode == false) then
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local writeWait = KEYS[1] .. ':write';
|
2025-04-03 17:22:40 +08:00
|
|
|
|
-- 优先写锁加锁
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local counter = redis.call('llen',writeWait);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
if (counter >= 1) then
|
2025-04-03 17:22:40 +08:00
|
|
|
|
redis.call('publish', KEYS[3], ARGV[1]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
end;
|
2025-02-28 16:00:16 +08:00
|
|
|
|
return 1;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
elseif (mode == 'write') then
|
|
|
|
|
|
return -2;
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
|
2025-04-11 16:36:54 +08:00
|
|
|
|
-- 判断当前的确是读模式但是当前 token 并没有加读锁的情况,返回 0
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
|
|
|
|
|
if ((mode == 'read') and (lockExists == 0)) then
|
2025-04-11 16:36:54 +08:00
|
|
|
|
return 0;
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
|
|
|
|
|
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local counter = redis.call('hincrby', KEYS[1], lockKey, -1);
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local delTTLs = redis.call('hpttl', KEYS[1], 'fields', '1', lockKey);
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local delTTL = tonumber(delTTLs[1]);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
if (counter == 0) then
|
2025-03-04 16:33:35 +08:00
|
|
|
|
redis.call('hdel', KEYS[1], lockKey);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
if (redis.call('hlen', KEYS[1]) > 1) then
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local cursor = 0;
|
|
|
|
|
|
local maxRemainTime = 0;
|
|
|
|
|
|
local pattern = KEYS[2] .. ':*';
|
|
|
|
|
|
repeat
|
|
|
|
|
|
local hscanResult = redis.call('hscan', KEYS[1], cursor, 'match', pattern, 'count', '100');
|
|
|
|
|
|
cursor = tonumber(hscanResult[1]);
|
|
|
|
|
|
local fields = hscanResult[2];
|
|
|
|
|
|
|
|
|
|
|
|
for i = 1, #fields,2 do
|
|
|
|
|
|
local field = fields[i];
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local remainTime = redis.call('hpttl', KEYS[1], 'fields', '1', field);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
maxRemainTime = math.max(tonumber(remainTime[1]), maxRemainTime);
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
until cursor == 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (maxRemainTime > 0) then
|
2025-03-13 16:51:50 +08:00
|
|
|
|
if (delTTL > maxRemainTime) then
|
2025-04-18 15:17:51 +08:00
|
|
|
|
redis.call('pexpire', KEYS[1], maxRemainTime);
|
2025-03-13 16:51:50 +08:00
|
|
|
|
else
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local remainTime = redis.call('pttl', KEYS[1]);
|
|
|
|
|
|
redis.call('pexpire', KEYS[1], math.max(tonumber(remainTime),maxRemainTime));
|
2025-03-13 16:51:50 +08:00
|
|
|
|
end;
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
else
|
|
|
|
|
|
redis.call('del', KEYS[1]);
|
|
|
|
|
|
local writeWait = KEYS[1] .. ':write';
|
2025-04-03 17:22:40 +08:00
|
|
|
|
-- 优先写锁加锁
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local counter = redis.call('llen',writeWait);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
if (counter >= 1) then
|
|
|
|
|
|
redis.call('publish', KEYS[3], ARGV[1]);
|
|
|
|
|
|
end;
|
2025-03-13 16:51:50 +08:00
|
|
|
|
return 1;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
end;
|
|
|
|
|
|
`
|
2025-02-28 16:00:16 +08:00
|
|
|
|
|
2025-03-04 16:33:35 +08:00
|
|
|
|
// WLockScript is the lua script for the lock write lock command
|
|
|
|
|
|
/*
|
2025-03-07 16:16:26 +08:00
|
|
|
|
KEYS[1]:锁的键名(key),通常是锁的唯一标识。
|
|
|
|
|
|
KEYS[2]:锁的超时键名前缀(rwTimeoutPrefix),用于存储每个读锁的超时键。
|
|
|
|
|
|
ARGV[1]:锁的过期时间(lockLeaseTime),单位为秒。
|
|
|
|
|
|
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
2025-03-04 16:33:35 +08:00
|
|
|
|
*/
|
|
|
|
|
|
var WLockScript = `
|
|
|
|
|
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
2025-03-05 16:42:59 +08:00
|
|
|
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local waitKey = KEYS[1] .. ':write';
|
|
|
|
|
|
if (mode == false) then
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local waitListLen = redis.call('llen', waitKey);
|
|
|
|
|
|
if (waitListLen > 0) then
|
|
|
|
|
|
local firstToken = redis.call('lindex', waitKey,'0');
|
|
|
|
|
|
if (firstToken ~= ARGV[2]) then
|
|
|
|
|
|
return -7;
|
|
|
|
|
|
end;
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
redis.call('hset', KEYS[1], 'mode', 'write');
|
|
|
|
|
|
redis.call('hset', KEYS[1], lockKey, 1);
|
2025-04-18 15:17:51 +08:00
|
|
|
|
redis.call('hpexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
|
|
|
|
|
redis.call('pexpire', KEYS[1], ARGV[1]);
|
2025-03-13 16:51:50 +08:00
|
|
|
|
redis.call('lpop', waitKey, '1');
|
2025-03-04 16:33:35 +08:00
|
|
|
|
return 1;
|
|
|
|
|
|
elseif (mode == 'read') then
|
2025-03-06 16:35:36 +08:00
|
|
|
|
-- 放到 list 中等待读锁释放后再次尝试加锁并且订阅读锁释放的消息
|
2025-04-02 16:47:51 +08:00
|
|
|
|
redis.call('rpush', waitKey, ARGV[2]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
return -3;
|
|
|
|
|
|
else
|
2025-03-05 16:42:59 +08:00
|
|
|
|
-- 可重入写锁逻辑
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
|
|
|
|
|
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
if (lockExists == 1) then
|
|
|
|
|
|
redis.call('hincrby', KEYS[1], lockKey, 1);
|
2025-04-18 15:17:51 +08:00
|
|
|
|
redis.call('hpexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
|
|
|
|
|
redis.call('pexpire', KEYS[1], ARGV[1]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
return 1;
|
|
|
|
|
|
end;
|
|
|
|
|
|
-- 放到 list 中等待写锁释放后再次尝试加锁并且订阅写锁释放的消息
|
|
|
|
|
|
local key = KEYS[1] .. ':write';
|
|
|
|
|
|
redis.call('rpush', key, ARGV[2]);
|
|
|
|
|
|
return -4;
|
|
|
|
|
|
end;
|
|
|
|
|
|
`
|
2025-02-28 16:00:16 +08:00
|
|
|
|
|
2025-03-04 16:33:35 +08:00
|
|
|
|
// UnWLockScript is the lua script for the unlock write lock command
|
|
|
|
|
|
/*
|
2025-03-07 16:16:26 +08:00
|
|
|
|
KEYS[1]:锁的键名(key),通常是锁的唯一标识。
|
|
|
|
|
|
KEYS[2]:锁的超时键名前缀(rwTimeoutPrefix),用于存储每个读锁的超时键。
|
2025-04-03 17:22:40 +08:00
|
|
|
|
KEYS[3]:锁的释放通知写频道(writeChankey),用于通知其他写等待客户端锁已释放。
|
|
|
|
|
|
KEYS[4]:锁的释放通知读频道(readChankey),用于通知其他读等待客户端锁已释放。
|
2025-03-04 16:33:35 +08:00
|
|
|
|
ARGV[1]:解锁消息(unlockMessage),用于通知其他客户端锁已释放。
|
2025-03-07 16:16:26 +08:00
|
|
|
|
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
2025-03-04 16:33:35 +08:00
|
|
|
|
*/
|
|
|
|
|
|
var UnWLockScript = `
|
|
|
|
|
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
|
|
|
|
|
local writeWait = KEYS[1] .. ':write';
|
|
|
|
|
|
if (mode == false) then
|
|
|
|
|
|
-- 优先写锁加锁,无写锁的情况通知读锁加锁
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local counter = redis.call('llen',writeWait);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
if (counter >= 1) then
|
|
|
|
|
|
redis.call('publish', KEYS[3], ARGV[1]);
|
2025-04-03 17:22:40 +08:00
|
|
|
|
else
|
|
|
|
|
|
redis.call('publish', KEYS[4], ARGV[1]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
end;
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
elseif (mode == 'read') then
|
|
|
|
|
|
return -5;
|
|
|
|
|
|
else
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
|
|
|
|
|
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
|
|
|
|
|
if (lockExists >= 1) then
|
|
|
|
|
|
-- 可重入写锁逻辑
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local incrRes = redis.call('hincrby', KEYS[1], lockKey, -1);
|
|
|
|
|
|
if (incrRes == 0) then
|
|
|
|
|
|
redis.call('del', KEYS[1]);
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local counter = redis.call('llen',writeWait);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
if (counter >= 1) then
|
|
|
|
|
|
redis.call('publish', KEYS[3], ARGV[1]);
|
2025-04-03 17:22:40 +08:00
|
|
|
|
else
|
|
|
|
|
|
redis.call('publish', KEYS[4], ARGV[1]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
end;
|
2025-03-13 16:51:50 +08:00
|
|
|
|
return 1;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
end;
|
2025-03-13 16:51:50 +08:00
|
|
|
|
return 0;
|
|
|
|
|
|
else
|
|
|
|
|
|
return -6;
|
2025-02-28 16:00:16 +08:00
|
|
|
|
end;
|
|
|
|
|
|
end;
|
2025-03-04 16:33:35 +08:00
|
|
|
|
`
|
|
|
|
|
|
|
2025-03-07 16:16:26 +08:00
|
|
|
|
// RefreshRWLockScript is the lua script for the refresh lock command
|
2025-03-04 16:33:35 +08:00
|
|
|
|
/*
|
2025-03-07 16:16:26 +08:00
|
|
|
|
KEYS[1]:锁的键名(key),通常是锁的唯一标识。
|
|
|
|
|
|
KEYS[2]:锁的超时键名前缀(rwTimeoutPrefix),用于存储每个读锁的超时键。
|
|
|
|
|
|
ARGV[1]:锁的过期时间(lockLeaseTime),单位为秒。
|
|
|
|
|
|
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
2025-03-04 16:33:35 +08:00
|
|
|
|
*/
|
2025-03-07 16:16:26 +08:00
|
|
|
|
var RefreshRWLockScript = `
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
2025-03-13 16:51:50 +08:00
|
|
|
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
2025-03-11 15:35:15 +08:00
|
|
|
|
local maxRemainTime = tonumber(ARGV[1]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
if (lockExists == 1) then
|
2025-04-18 15:17:51 +08:00
|
|
|
|
redis.call('hpexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
2025-03-12 16:24:28 +08:00
|
|
|
|
if (mode == 'read') then
|
2025-03-04 16:33:35 +08:00
|
|
|
|
local cursor = 0;
|
|
|
|
|
|
local pattern = KEYS[2] .. ':*';
|
|
|
|
|
|
repeat
|
|
|
|
|
|
local hscanResult = redis.call('hscan', KEYS[1], cursor, 'match', pattern, 'count', '100');
|
|
|
|
|
|
cursor = tonumber(hscanResult[1]);
|
|
|
|
|
|
local fields = hscanResult[2];
|
2025-02-28 16:00:16 +08:00
|
|
|
|
|
2025-03-04 16:33:35 +08:00
|
|
|
|
for i = 1, #fields,2 do
|
|
|
|
|
|
local field = fields[i];
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local remainTime = redis.call('hpttl', KEYS[1], 'fields', '1', field);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
maxRemainTime = math.max(tonumber(remainTime[1]), maxRemainTime);
|
|
|
|
|
|
end;
|
|
|
|
|
|
until cursor == 0;
|
2025-03-11 15:35:15 +08:00
|
|
|
|
|
2025-03-04 16:33:35 +08:00
|
|
|
|
if (maxRemainTime > 0) then
|
2025-04-18 15:17:51 +08:00
|
|
|
|
local remainTime = redis.call('pttl', KEYS[1]);
|
|
|
|
|
|
redis.call('pexpire', KEYS[1], math.max(tonumber(remainTime),maxRemainTime));
|
2025-03-04 16:33:35 +08:00
|
|
|
|
end;
|
2025-03-11 15:35:15 +08:00
|
|
|
|
elseif (mode == 'write') then
|
2025-04-18 15:17:51 +08:00
|
|
|
|
redis.call('pexpire', KEYS[1], ARGV[1]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
end;
|
2025-04-18 15:17:51 +08:00
|
|
|
|
-- return redis.call('pttl',KEYS[1]);
|
2025-03-04 16:33:35 +08:00
|
|
|
|
return 1;
|
|
|
|
|
|
end;
|
|
|
|
|
|
return -8;
|
2025-02-28 16:00:16 +08:00
|
|
|
|
`
|