fix(lock script): fix bug of lock srcipt
1.fix bug of reset time wrong with ReentrantRLock in RLock script 2.fix bug of write lock failing to lock for the first time 3.fix bug of unlock failed with ReentrantWLock in UnWLock script test(lock script): add new test of RLock and WLock 1.add refresh test of RLock 2.add new test of ReentrantWLock#
This commit is contained in:
parent
d962462c42
commit
7b282c49f7
|
|
@ -11,6 +11,7 @@ const (
|
|||
UnLockSuccess = RedisCode(1)
|
||||
RefreshLockSuccess = RedisCode(1)
|
||||
UnRLockSuccess = RedisCode(0)
|
||||
UnWLockSuccess = RedisCode(0)
|
||||
RLockFailureWithWLockOccupancy = RedisCode(-1)
|
||||
UnRLockFailureWithWLockOccupancy = RedisCode(-2)
|
||||
WLockFailureWithRLockOccupancy = RedisCode(-3)
|
||||
|
|
@ -28,6 +29,8 @@ type RedisLockType int
|
|||
|
||||
const (
|
||||
LockType = RedisLockType(iota)
|
||||
UnRLockType
|
||||
UnWLockType
|
||||
UnLockType
|
||||
RefreshLockType
|
||||
)
|
||||
|
|
@ -55,13 +58,17 @@ func NewRedisResult(res RedisCode, lockType RedisLockType, redisMsg string) erro
|
|||
case 1:
|
||||
if lockType == LockType {
|
||||
return &RedisResult{Code: res, Message: "redis lock success"}
|
||||
} else if lockType == UnLockType {
|
||||
} else if (lockType == UnRLockType) || (lockType == UnWLockType) || (lockType == UnLockType) {
|
||||
return &RedisResult{Code: res, Message: "redis unlock success"}
|
||||
} else {
|
||||
return &RedisResult{Code: res, Message: "redis refresh lock success"}
|
||||
}
|
||||
case 0:
|
||||
return &RedisResult{Code: res, Message: "redis unlock read lock success, the lock is still occupied by other processes read lock"}
|
||||
if lockType == UnRLockType {
|
||||
return &RedisResult{Code: res, Message: "redis unlock read lock success, the lock is still occupied by other processes read lock"}
|
||||
} else {
|
||||
return &RedisResult{Code: res, Message: "redis unlock write lock success, the lock is still occupied by other processes write lock"}
|
||||
}
|
||||
case -1:
|
||||
return &RedisResult{Code: res, Message: "redis lock read lock failure,the lock is already occupied by another processes write lock"}
|
||||
case -2:
|
||||
|
|
@ -97,13 +104,17 @@ func TranslateResultToStr(res RedisCode, lockType RedisLockType) string {
|
|||
case 1:
|
||||
if lockType == LockType {
|
||||
return "redis lock success"
|
||||
} else if lockType == UnLockType {
|
||||
} else if (lockType == UnRLockType) || (lockType == UnWLockType) || (lockType == UnLockType) {
|
||||
return "redis unlock success"
|
||||
} else {
|
||||
return "redis refresh lock success"
|
||||
}
|
||||
case 0:
|
||||
return "redis unlock read lock success, the lock is still occupied by other processes read lock"
|
||||
if lockType == UnRLockType {
|
||||
return "redis unlock read lock success, the lock is still occupied by other processes read lock"
|
||||
} else {
|
||||
return "redis unlock write lock success, the lock is still occupied by other processes write lock"
|
||||
}
|
||||
case -1:
|
||||
return "redis lock read lock failure,the lock is already occupied by another processes write lock"
|
||||
case -2:
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ local mode = redis.call('hget', KEYS[1], 'mode');
|
|||
if (mode == false) then
|
||||
local writeWait = KEYS[1] .. ':write';
|
||||
-- 优先写锁加锁,无写锁的情况通知读锁加锁
|
||||
local counter = redis.call('llen',writeWait)
|
||||
local counter = redis.call('llen',writeWait);
|
||||
if (counter >= 1) then
|
||||
redis.call('publish', KEYS[4], ARGV[1]);
|
||||
end;
|
||||
|
|
@ -86,6 +86,8 @@ if ((mode == 'read') and (lockExists == 0)) then
|
|||
end;
|
||||
|
||||
local counter = redis.call('hincrby', KEYS[1], lockKey, -1);
|
||||
local delTTLs = redis.call('httl', KEYS[1], 'fields', '1', lockKey);
|
||||
local delTTL = tonumber(delTTLs[1]);
|
||||
if (counter == 0) then
|
||||
redis.call('hdel', KEYS[1], lockKey);
|
||||
end;
|
||||
|
|
@ -107,20 +109,24 @@ if (redis.call('hlen', KEYS[1]) > 1) then
|
|||
until cursor == 0;
|
||||
|
||||
if (maxRemainTime > 0) then
|
||||
local remainTime = redis.call('ttl', KEYS[1]);
|
||||
redis.call('expire', KEYS[1], math.max(tonumber(remainTime),maxRemainTime));
|
||||
if (delTTL > maxRemainTime) then
|
||||
redis.call('expire', KEYS[1], maxRemainTime);
|
||||
else
|
||||
local remainTime = redis.call('ttl', KEYS[1]);
|
||||
redis.call('expire', KEYS[1], math.max(tonumber(remainTime),maxRemainTime));
|
||||
end;
|
||||
end;
|
||||
else
|
||||
redis.call('del', KEYS[1]);
|
||||
local writeWait = KEYS[1] .. ':write';
|
||||
-- 优先写锁加锁,无写锁的情况通知读锁加锁
|
||||
local counter = redis.call('llen',writeWait)
|
||||
local counter = redis.call('llen',writeWait);
|
||||
if (counter >= 1) then
|
||||
redis.call('publish', KEYS[4], ARGV[1]);
|
||||
else
|
||||
redis.call('publish', KEYS[3], ARGV[1]);
|
||||
end;
|
||||
return 1;
|
||||
return 1;
|
||||
end;
|
||||
`
|
||||
|
||||
|
|
@ -136,15 +142,18 @@ local mode = redis.call('hget', KEYS[1], 'mode');
|
|||
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
||||
local waitKey = KEYS[1] .. ':write';
|
||||
if (mode == false) then
|
||||
local firstToken = redis.call('lindex', waitKey,'0')
|
||||
if (firstToken ~= ARGV[2]) then
|
||||
return -7;
|
||||
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;
|
||||
end;
|
||||
redis.call('hset', KEYS[1], 'mode', 'write');
|
||||
redis.call('hset', KEYS[1], lockKey, 1);
|
||||
redis.call('hexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
||||
redis.call('expire', KEYS[1], ARGV[1]);
|
||||
redis.call('lpop', waitKey, '1')
|
||||
redis.call('lpop', waitKey, '1');
|
||||
return 1;
|
||||
elseif (mode == 'read') then
|
||||
-- 放到 list 中等待读锁释放后再次尝试加锁并且订阅读锁释放的消息
|
||||
|
|
@ -152,8 +161,8 @@ elseif (mode == 'read') then
|
|||
return -3;
|
||||
else
|
||||
-- 可重入写锁逻辑
|
||||
local lockKey = KEYS[2] .. ':' .. ARGV[2]
|
||||
local lockExists = redis.call('hexists', KEYS[1], lockKey)
|
||||
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
||||
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
||||
if (lockExists == 1) then
|
||||
redis.call('hincrby', KEYS[1], lockKey, 1);
|
||||
redis.call('hexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
||||
|
|
@ -180,7 +189,7 @@ local mode = redis.call('hget', KEYS[1], 'mode');
|
|||
local writeWait = KEYS[1] .. ':write';
|
||||
if (mode == false) then
|
||||
-- 优先写锁加锁,无写锁的情况通知读锁加锁
|
||||
local counter = redis.call('llen',writeWait)
|
||||
local counter = redis.call('llen',writeWait);
|
||||
if (counter >= 1) then
|
||||
redis.call('publish', KEYS[3], ARGV[1]);
|
||||
end;
|
||||
|
|
@ -188,23 +197,25 @@ if (mode == false) then
|
|||
elseif (mode == 'read') then
|
||||
return -5;
|
||||
else
|
||||
-- 可重入写锁逻辑
|
||||
local lockKey = KEYS[2] .. ':' .. ARGV[2]
|
||||
local lockExists = redis.call('hexists', KEYS[1], lockKey)
|
||||
if (lockExists == 1) then
|
||||
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
||||
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
||||
if (lockExists >= 1) then
|
||||
-- 可重入写锁逻辑
|
||||
local incrRes = redis.call('hincrby', KEYS[1], lockKey, -1);
|
||||
if (incrRes == 0) then
|
||||
redis.call('del', KEYS[1]);
|
||||
local counter = redis.call('llen',writeWait)
|
||||
local counter = redis.call('llen',writeWait);
|
||||
if (counter >= 1) then
|
||||
redis.call('publish', KEYS[4], ARGV[1]);
|
||||
else
|
||||
redis.call('publish', KEYS[3], ARGV[1]);
|
||||
end;
|
||||
return 1
|
||||
return 1;
|
||||
end;
|
||||
return 0;
|
||||
else
|
||||
return -6;
|
||||
end;
|
||||
return -6;
|
||||
end;
|
||||
`
|
||||
|
||||
|
|
@ -216,9 +227,9 @@ ARGV[1]:锁的过期时间(lockLeaseTime),单位为秒。
|
|||
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
||||
*/
|
||||
var RefreshRWLockScript = `
|
||||
local lockKey = KEYS[2] .. ':' .. ARGV[2]
|
||||
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
||||
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
||||
local mode = redis.call('hget', KEYS[1], 'mode')
|
||||
local mode = redis.call('hget', KEYS[1], 'mode');
|
||||
local maxRemainTime = tonumber(ARGV[1]);
|
||||
if (lockExists == 1) then
|
||||
redis.call('hexpire', KEYS[1], ARGV[1], 'fields', '1', lockKey);
|
||||
|
|
|
|||
|
|
@ -130,11 +130,11 @@ func (rl *RedissionRWLocker) UnRLock() error {
|
|||
val, err := res.Int()
|
||||
if err != redis.Nil && err != nil {
|
||||
rl.logger.Info("unlock read lock failed", zap.String("token", rl.token), zap.String("key", rl.key), zap.Error(err))
|
||||
return fmt.Errorf("unlock read lock failed:%w", constant.NewRedisResult(constant.UnknownInternalError, constant.UnLockType, err.Error()))
|
||||
return fmt.Errorf("unlock read lock failed:%w", constant.NewRedisResult(constant.UnknownInternalError, constant.UnRLockType, err.Error()))
|
||||
}
|
||||
|
||||
if (constant.RedisCode(val) == constant.UnLockSuccess) || (constant.RedisCode(val) == constant.UnRLockSuccess) {
|
||||
if rl.needRefresh {
|
||||
if rl.needRefresh && (constant.RedisCode(val) == constant.UnLockSuccess) {
|
||||
rl.cancelRefreshLockTime()
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ func (rl *RedissionRWLocker) UnRLock() error {
|
|||
|
||||
if constant.RedisCode(val) == constant.UnRLockFailureWithWLockOccupancy {
|
||||
rl.logger.Info("unlock read lock failed", zap.String("token", rl.token), zap.String("key", rl.key))
|
||||
return fmt.Errorf("unlock read lock failed:%w", constant.NewRedisResult(constant.UnRLockFailureWithWLockOccupancy, constant.UnLockType, ""))
|
||||
return fmt.Errorf("unlock read lock failed:%w", constant.NewRedisResult(constant.UnRLockFailureWithWLockOccupancy, constant.UnRLockType, ""))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -221,11 +221,11 @@ func (rl *RedissionRWLocker) UnWLock() error {
|
|||
val, err := res.Int()
|
||||
if err != redis.Nil && err != nil {
|
||||
rl.logger.Info("unlock write lock failed", zap.String("token", rl.token), zap.String("key", rl.key), zap.Error(err))
|
||||
return fmt.Errorf("unlock write lock failed:%w", constant.NewRedisResult(constant.UnknownInternalError, constant.UnLockType, err.Error()))
|
||||
return fmt.Errorf("unlock write lock failed:%w", constant.NewRedisResult(constant.UnknownInternalError, constant.UnWLockType, err.Error()))
|
||||
}
|
||||
|
||||
if constant.RedisCode(val) == constant.UnLockSuccess {
|
||||
if rl.needRefresh {
|
||||
if (constant.RedisCode(val) == constant.UnLockSuccess) || constant.RedisCode(val) == constant.UnWLockSuccess {
|
||||
if rl.needRefresh && (constant.RedisCode(val) == constant.UnLockSuccess) {
|
||||
rl.cancelRefreshLockTime()
|
||||
}
|
||||
rl.logger.Info("unlock write lock success", zap.String("token", rl.token), zap.String("key", rl.key))
|
||||
|
|
@ -234,7 +234,7 @@ func (rl *RedissionRWLocker) UnWLock() error {
|
|||
|
||||
if (constant.RedisCode(val) == constant.UnWLockFailureWithRLockOccupancy) || (constant.RedisCode(val) == constant.UnWLockFailureWithWLockOccupancy) {
|
||||
rl.logger.Info("unlock write lock failed", zap.String("token", rl.token), zap.String("key", rl.key))
|
||||
return fmt.Errorf("unlock write lock failed:%w", constant.NewRedisResult(constant.RedisCode(val), constant.UnLockType, ""))
|
||||
return fmt.Errorf("unlock write lock failed:%w", constant.NewRedisResult(constant.RedisCode(val), constant.UnWLockType, ""))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,12 +49,11 @@ func TestRWLockRLockAndUnRLock(t *testing.T) {
|
|||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, redis.Nil, err)
|
||||
assert.Equal(t, 0, num)
|
||||
t.Log("test success")
|
||||
t.Log("rwLock rlock and unrlock test success")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 实现可重入读锁测试
|
||||
func TestRWLockReentrantLock(t *testing.T) {
|
||||
func TestRWLockReentrantRLock(t *testing.T) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Network: "tcp",
|
||||
Addr: "192.168.2.103:6379",
|
||||
|
|
@ -89,19 +88,164 @@ func TestRWLockReentrantLock(t *testing.T) {
|
|||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 2, num)
|
||||
|
||||
// 第一次解读锁
|
||||
err = rwLocker.UnRLock()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
// 第二次解读锁
|
||||
err = rwLocker.UnRLock()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, redis.Nil, err)
|
||||
assert.Equal(t, 0, num)
|
||||
t.Log("rwLock reentrant lock test success")
|
||||
return
|
||||
}
|
||||
|
||||
func TestRWLockRefreshRLock(t *testing.T) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Network: "tcp",
|
||||
Addr: "192.168.2.103:6379",
|
||||
Password: "cnstar",
|
||||
PoolSize: 50,
|
||||
DialTimeout: 10 * time.Second,
|
||||
})
|
||||
|
||||
rwLocker := GetRWLocker(rdb, &RedissionLockConfig{
|
||||
LockLeaseTime: 10,
|
||||
NeedRefresh: true,
|
||||
Key: "component",
|
||||
Token: "fd348a84-e07c-4a61-8c19-f753e6bc556a",
|
||||
})
|
||||
rwLocker.logger = log
|
||||
|
||||
duration := 10 * time.Second
|
||||
// 第一次加读锁
|
||||
err := rwLocker.RLock(duration)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
tokenKey := strings.Join([]string{rwLocker.rwTokenTimeoutPrefix, rwLocker.token}, ":")
|
||||
num, err := rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
script := `return redis.call('httl', KEYS[1], 'fields', '1', ARGV[1]);`
|
||||
result, err := rdb.Eval(script, []string{rwLocker.key}, tokenKey).Result()
|
||||
assert.Equal(t, nil, err)
|
||||
ttls, ok := result.([]interface{})
|
||||
assert.Equal(t, true, ok)
|
||||
ttl, ok := ttls[0].(int64)
|
||||
assert.Equal(t, true, ok)
|
||||
compareValue := int64(8)
|
||||
assert.Greater(t, ttl, compareValue)
|
||||
|
||||
err = rwLocker.UnRLock()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, redis.Nil, err)
|
||||
assert.Equal(t, 0, num)
|
||||
t.Log("test success")
|
||||
t.Log("rwLock refresh lock test success")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 设计两个客户端分别加读锁,测试是否可以加锁成功
|
||||
// TODO 设计两个客户端分别加时间不同的读锁,测试ttl时间在有一个key删除后是否可以变换成功
|
||||
|
||||
func TestRWLockWLockAndUnWLock(t *testing.T) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Network: "tcp",
|
||||
Addr: "192.168.2.103:6379",
|
||||
Password: "cnstar",
|
||||
PoolSize: 50,
|
||||
DialTimeout: 10 * time.Second,
|
||||
})
|
||||
|
||||
rwLocker := GetRWLocker(rdb, &RedissionLockConfig{
|
||||
LockLeaseTime: 120,
|
||||
NeedRefresh: true,
|
||||
Key: "component",
|
||||
Token: "fd348a84-e07c-4a61-8c19-f753e6bc556a",
|
||||
})
|
||||
rwLocker.logger = log
|
||||
|
||||
duration := 10 * time.Second
|
||||
// 第一次加读锁
|
||||
err := rwLocker.WLock(duration)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
tokenKey := strings.Join([]string{rwLocker.rwTokenTimeoutPrefix, rwLocker.token}, ":")
|
||||
num, err := rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
err = rwLocker.UnWLock()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, redis.Nil, err)
|
||||
assert.Equal(t, 0, num)
|
||||
t.Log("rwLock rlock and unrlock test success")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 完成写锁可重入测试
|
||||
func TestRWLockReentrantWLock(t *testing.T) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Network: "tcp",
|
||||
Addr: "192.168.2.103:6379",
|
||||
Password: "cnstar",
|
||||
PoolSize: 50,
|
||||
DialTimeout: 10 * time.Second,
|
||||
})
|
||||
|
||||
rwLocker := GetRWLocker(rdb, &RedissionLockConfig{
|
||||
LockLeaseTime: 120,
|
||||
NeedRefresh: true,
|
||||
Key: "component",
|
||||
Token: "fd348a84-e07c-4a61-8c19-f753e6bc556a",
|
||||
})
|
||||
rwLocker.logger = log
|
||||
|
||||
duration := 10 * time.Second
|
||||
// 第一次加写锁
|
||||
err := rwLocker.WLock(duration)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
tokenKey := strings.Join([]string{rwLocker.rwTokenTimeoutPrefix, rwLocker.token}, ":")
|
||||
num, err := rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
// 第二次加写锁
|
||||
err = rwLocker.WLock(duration)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 2, num)
|
||||
|
||||
// 第一次解写锁
|
||||
err = rwLocker.UnWLock()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
// 第二次解写锁
|
||||
err = rwLocker.UnWLock()
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
num, err = rdb.HGet(rwLocker.key, tokenKey).Int()
|
||||
assert.Equal(t, redis.Nil, err)
|
||||
assert.Equal(t, 0, num)
|
||||
t.Log("rwLock reentrant lock test success")
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue