optimize read lock acquisition statements of redisRWLock
This commit is contained in:
parent
b894d61b54
commit
c08f4b91f5
|
|
@ -0,0 +1,123 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type RedisResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LockSuccess = RedisResult(1)
|
||||||
|
UnLockSuccess = RedisResult(1)
|
||||||
|
RefreshLockSuccess = RedisResult(1)
|
||||||
|
UnRLockSuccess = RedisResult(0)
|
||||||
|
RLockFailure = RedisResult(-1)
|
||||||
|
UnRLockFailureWithWLockOccupancy = RedisResult(-2)
|
||||||
|
WLockFailureWithRLockOccupancy = RedisResult(-3)
|
||||||
|
WLockFailureWithWLockOccupancy = RedisResult(-4)
|
||||||
|
UnWLockFailureWithRLockOccupancy = RedisResult(-5)
|
||||||
|
UnWLockFailureWithWLockOccupancy = RedisResult(-6)
|
||||||
|
WLockFailureWithNotFirstPriority = RedisResult(-7)
|
||||||
|
RefreshLockFailure = RedisResult(-8)
|
||||||
|
UnknownInternalError = RedisResult(-99)
|
||||||
|
)
|
||||||
|
|
||||||
|
type RedisLockType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LockType = RedisLockType(iota)
|
||||||
|
UnLockType
|
||||||
|
RefreshLockType
|
||||||
|
)
|
||||||
|
|
||||||
|
type RedisError struct {
|
||||||
|
Code RedisResult
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RedisError) Error() string {
|
||||||
|
return fmt.Sprintf("redis execution code:%d,message:%s\n", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RedisError) OutputResultMessage() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RedisError) OutputResultCode() int {
|
||||||
|
return int(e.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedisError(res RedisResult) error {
|
||||||
|
resInt := int(res)
|
||||||
|
switch resInt {
|
||||||
|
case -1:
|
||||||
|
return &RedisError{Code: -1, Message: "redis lock read lock failure,the lock is already occupied by another processes write lock"}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertResultToErr(res RedisResult, lockType RedisLockType, redisMsg string) error {
|
||||||
|
resInt := int(res)
|
||||||
|
switch resInt {
|
||||||
|
case 1:
|
||||||
|
if lockType == LockType {
|
||||||
|
return &RedisError{Code: res, Message: "redis lock success"}
|
||||||
|
} else if lockType == UnLockType {
|
||||||
|
return &RedisError{Code: res, Message: "redis unlock success"}
|
||||||
|
} else {
|
||||||
|
return &RedisError{Code: res, Message: "redis refresh lock success"}
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
return &RedisError{Code: res, Message: "redis unlock read lock success, the lock is still occupied by other processes read lock"}
|
||||||
|
case -1:
|
||||||
|
return &RedisError{Code: res, Message: "redis lock read lock failure,the lock is already occupied by another processes write lock"}
|
||||||
|
case -2:
|
||||||
|
return &RedisError{Code: res, Message: "redis un lock read lock failure,the lock is already occupied by another processes write lock"}
|
||||||
|
case -3:
|
||||||
|
return &RedisError{Code: res, Message: "redis lock write lock failure,the lock is already occupied by anthor processes read lock"}
|
||||||
|
case -4:
|
||||||
|
return &RedisError{Code: res, Message: "redis lock write lock failure,the lock is already occupied by anthor processes write lock"}
|
||||||
|
case -5:
|
||||||
|
return &RedisError{Code: res, Message: "redis un lock write lock failure,the lock is already occupied by another processes read lock"}
|
||||||
|
case -6:
|
||||||
|
return &RedisError{Code: res, Message: "redis un lock write lock failure,the lock is already occupied by another processes write lock"}
|
||||||
|
case -7:
|
||||||
|
return &RedisError{Code: res, Message: "redis lock write lock failure,the first priority in the current process non-waiting queue"}
|
||||||
|
case -8:
|
||||||
|
return &RedisError{Code: res, Message: "redis refresh lock failure,the lock not exist"}
|
||||||
|
default:
|
||||||
|
return &RedisError{Code: res, Message: fmt.Sprintf("unkown redis execution result:%s\n", redisMsg)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TranslateResultToStr(res RedisResult, lockType RedisLockType) string {
|
||||||
|
resInt := int(res)
|
||||||
|
switch resInt {
|
||||||
|
case 1:
|
||||||
|
if lockType == LockType {
|
||||||
|
return "redis lock success"
|
||||||
|
} else if 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"
|
||||||
|
case -1:
|
||||||
|
return "redis lock read lock failure,the lock is already occupied by another processes write lock"
|
||||||
|
case -2:
|
||||||
|
return "redis un lock read lock failure,the lock is already occupied by another processes write lock"
|
||||||
|
case -3:
|
||||||
|
return "redis lock write lock failure,the lock is already occupied by anthor processes read lock"
|
||||||
|
case -4:
|
||||||
|
return "redis lock write lock failure,the lock is already occupied by anthor processes write lock"
|
||||||
|
case -5:
|
||||||
|
return "redis un lock write lock failure,the lock is already occupied by another processes read lock"
|
||||||
|
case -6:
|
||||||
|
return "redis un lock write lock failure,the lock is already occupied by another processes write lock"
|
||||||
|
case -7:
|
||||||
|
return "redis lock write lock failure,the first priority in the current process non-waiting queue"
|
||||||
|
case -8:
|
||||||
|
return "redis refresh lock failure,the lock not exist"
|
||||||
|
}
|
||||||
|
return "unkown redis execution result"
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户
|
||||||
*/
|
*/
|
||||||
var RLockScript = `
|
var RLockScript = `
|
||||||
local mode = redis.call('hget', KEYS[1], 'mode');
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
||||||
local lockKey = KEYS[2] .. ARGV[2];
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
||||||
if (mode == false) then
|
if (mode == false) then
|
||||||
redis.call('hset', KEYS[1], 'mode', 'read');
|
redis.call('hset', KEYS[1], 'mode', 'read');
|
||||||
redis.call('hset', KEYS[1], lockKey, '1');
|
redis.call('hset', KEYS[1], lockKey, '1');
|
||||||
|
|
@ -66,7 +66,7 @@ ARGV[1]:解锁消息(unlockMessage),用于通知其他客户端锁已释放
|
||||||
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
||||||
*/
|
*/
|
||||||
var UnRLockScript = `
|
var UnRLockScript = `
|
||||||
local lockKey = KEYS[2] .. ARGV[2];
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
||||||
local mode = redis.call('hget', KEYS[1], 'mode');
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
||||||
if (mode == false) then
|
if (mode == false) then
|
||||||
local writeWait = KEYS[1] .. ':write';
|
local writeWait = KEYS[1] .. ':write';
|
||||||
|
|
@ -136,7 +136,7 @@ ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户
|
||||||
*/
|
*/
|
||||||
var WLockScript = `
|
var WLockScript = `
|
||||||
local mode = redis.call('hget', KEYS[1], 'mode');
|
local mode = redis.call('hget', KEYS[1], 'mode');
|
||||||
local lockKey = KEYS[2] .. ARGV[2];
|
local lockKey = KEYS[2] .. ':' .. ARGV[2];
|
||||||
local waitKey = KEYS[1] .. ':write';
|
local waitKey = KEYS[1] .. ':write';
|
||||||
if (mode == false) then
|
if (mode == false) then
|
||||||
local firstToken = redis.call('lindex', waitKey,'0')
|
local firstToken = redis.call('lindex', waitKey,'0')
|
||||||
|
|
@ -154,8 +154,8 @@ elseif (mode == 'read') then
|
||||||
redis.call('rpush', waitkey, ARGV[2]);
|
redis.call('rpush', waitkey, ARGV[2]);
|
||||||
return -3;
|
return -3;
|
||||||
else
|
else
|
||||||
// 可重入写锁逻辑
|
-- 可重入写锁逻辑
|
||||||
local lockKey = KEYS[2] .. ARGV[2]
|
local lockKey = KEYS[2] .. ':' .. ARGV[2]
|
||||||
local lockExists = redis.call('hexists', KEYS[1], lockKey)
|
local lockExists = redis.call('hexists', KEYS[1], lockKey)
|
||||||
if (lockExists == 1) then
|
if (lockExists == 1) then
|
||||||
redis.call('hincrby', KEYS[1], lockKey, 1);
|
redis.call('hincrby', KEYS[1], lockKey, 1);
|
||||||
|
|
@ -194,8 +194,8 @@ if (mode == false) then
|
||||||
elseif (mode == 'read') then
|
elseif (mode == 'read') then
|
||||||
return -5;
|
return -5;
|
||||||
else
|
else
|
||||||
// 可重入写锁逻辑
|
-- 可重入写锁逻辑
|
||||||
local lockKey = KEYS[2] .. ARGV[2]
|
local lockKey = KEYS[2] .. ':' .. ARGV[2]
|
||||||
local lockExists = redis.call('hexists', KEYS[1], lockKey)
|
local lockExists = redis.call('hexists', KEYS[1], lockKey)
|
||||||
if (lockExists == 1) then
|
if (lockExists == 1) then
|
||||||
local incrRes = redis.call('hincrby', KEYS[1], lockKey, -1);
|
local incrRes = redis.call('hincrby', KEYS[1], lockKey, -1);
|
||||||
|
|
@ -222,7 +222,7 @@ ARGV[1]:锁的过期时间(lockLeaseTime),单位为秒。
|
||||||
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
ARGV[2]:当前客户端的唯一标识(token),用于区分不同的客户端。
|
||||||
*/
|
*/
|
||||||
var RefreshLockScript = `
|
var RefreshLockScript = `
|
||||||
local lockKey = KEYS[2] .. ARGV[2]
|
local lockKey = KEYS[2] .. ':' .. ARGV[2]
|
||||||
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
local lockExists = redis.call('hexists', KEYS[1], lockKey);
|
||||||
local mode = redis.call('hget', KEYS[1], 'mode')
|
local mode = redis.call('hget', KEYS[1], 'mode')
|
||||||
if (lockExists == 1) then
|
if (lockExists == 1) then
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ type RedissionLockConfig struct {
|
||||||
type redissionLocker struct {
|
type redissionLocker struct {
|
||||||
token string
|
token string
|
||||||
key string
|
key string
|
||||||
chankey string
|
waitChankey string
|
||||||
exit chan struct{}
|
exit chan struct{}
|
||||||
lockLeaseTime uint64
|
lockLeaseTime uint64
|
||||||
client *redis.Client
|
client *redis.Client
|
||||||
|
|
@ -98,7 +98,7 @@ func (rl *redissionLocker) Lock(ctx context.Context, timeout ...time.Duration) {
|
||||||
|
|
||||||
submsg := make(chan struct{}, 1)
|
submsg := make(chan struct{}, 1)
|
||||||
defer close(submsg)
|
defer close(submsg)
|
||||||
sub := rl.client.Subscribe(rl.chankey)
|
sub := rl.client.Subscribe(rl.waitChankey)
|
||||||
defer sub.Close()
|
defer sub.Close()
|
||||||
go rl.subscribeLock(sub, submsg)
|
go rl.subscribeLock(sub, submsg)
|
||||||
// listen := rl.listenManager.Subscribe(rl.key, rl.token)
|
// listen := rl.listenManager.Subscribe(rl.key, rl.token)
|
||||||
|
|
@ -262,7 +262,7 @@ func (rl *redissionLocker) tryLock() (time.Duration, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *redissionLocker) UnLock() {
|
func (rl *redissionLocker) UnLock() {
|
||||||
res := rl.client.Eval(unlockScript, []string{rl.key, rl.chankey}, unlockMessage, rl.lockLeaseTime, rl.token)
|
res := rl.client.Eval(unlockScript, []string{rl.key, rl.waitChankey}, unlockMessage, rl.lockLeaseTime, rl.token)
|
||||||
val, err := res.Result()
|
val, err := res.Result()
|
||||||
if err != redis.Nil && err != nil {
|
if err != redis.Nil && err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -294,6 +294,6 @@ func GetLocker(client *redis.Client, ops *RedissionLockConfig) *redissionLocker
|
||||||
r.lockLeaseTime = internalLockLeaseTime
|
r.lockLeaseTime = internalLockLeaseTime
|
||||||
}
|
}
|
||||||
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
||||||
r.chankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
r.waitChankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,262 +2,98 @@ package distributed_lock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"modelRT/distributedlock/constant"
|
||||||
|
"modelRT/distributedlock/luascript"
|
||||||
|
"modelRT/logger"
|
||||||
|
|
||||||
"github.com/go-redis/redis"
|
"github.com/go-redis/redis"
|
||||||
uuid "github.com/google/uuid"
|
uuid "github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rlockScript string = strings.Join([]string{
|
|
||||||
"local mode = redis.call('hget', KEYS[1], 'mode'); ",
|
|
||||||
"if (mode == false) then ",
|
|
||||||
"redis.call('hset', KEYS[1], 'mode', 'read'); ",
|
|
||||||
"redis.call('hset', KEYS[1], ARGV[2], 1); ",
|
|
||||||
"redis.call('set', KEYS[2] .. ':1', 1); ",
|
|
||||||
"redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); ",
|
|
||||||
"redis.call('pexpire', KEYS[1], ARGV[1]); ",
|
|
||||||
"return nil; ",
|
|
||||||
"end; ",
|
|
||||||
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then ",
|
|
||||||
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); ",
|
|
||||||
"local key = KEYS[2] .. ':' .. ind;",
|
|
||||||
"redis.call('set', key, 1); ",
|
|
||||||
"redis.call('pexpire', key, ARGV[1]); ",
|
|
||||||
"local remainTime = redis.call('pttl', KEYS[1]); ",
|
|
||||||
"redis.call('pexpire', KEYS[1], math.max(remainTime, ARGV[1])); ",
|
|
||||||
"return nil; ",
|
|
||||||
"end;",
|
|
||||||
"return redis.call('pttl', KEYS[1]);",
|
|
||||||
}, "")
|
|
||||||
|
|
||||||
var runlockScript string = strings.Join([]string{
|
|
||||||
"local mode = redis.call('hget', KEYS[1], 'mode'); ",
|
|
||||||
"if (mode == false) then ",
|
|
||||||
"redis.call('publish', KEYS[2], ARGV[1]); ",
|
|
||||||
"return 1; ",
|
|
||||||
"end; ",
|
|
||||||
"local lockExists = redis.call('hexists', KEYS[1], ARGV[2]); ",
|
|
||||||
"if (lockExists == 0) then ",
|
|
||||||
"return nil;",
|
|
||||||
"end; ",
|
|
||||||
|
|
||||||
"local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); ",
|
|
||||||
"if (counter == 0) then ",
|
|
||||||
"redis.call('hdel', KEYS[1], ARGV[2]); ",
|
|
||||||
"end;",
|
|
||||||
"redis.call('del', KEYS[3] .. ':' .. (counter+1)); ",
|
|
||||||
|
|
||||||
"if (redis.call('hlen', KEYS[1]) > 1) then ",
|
|
||||||
"local maxRemainTime = -3; ",
|
|
||||||
"local keys = redis.call('hkeys', KEYS[1]); ",
|
|
||||||
"for n, key in ipairs(keys) do ",
|
|
||||||
"counter = tonumber(redis.call('hget', KEYS[1], key)); ",
|
|
||||||
"if type(counter) == 'number' then ",
|
|
||||||
"for i=counter, 1, -1 do ",
|
|
||||||
"local remainTime = redis.call('pttl', KEYS[4] .. ':' .. key .. ':rwlock_timeout:' .. i); ",
|
|
||||||
"maxRemainTime = math.max(remainTime, maxRemainTime);",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
|
|
||||||
"if maxRemainTime > 0 then ",
|
|
||||||
"redis.call('pexpire', KEYS[1], maxRemainTime); ",
|
|
||||||
"return 0; ",
|
|
||||||
"end;",
|
|
||||||
|
|
||||||
"if mode == 'write' then ",
|
|
||||||
"return 0;",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
|
|
||||||
"redis.call('del', KEYS[1]); ",
|
|
||||||
"redis.call('publish', KEYS[2], ARGV[1]); ",
|
|
||||||
"return 1; ",
|
|
||||||
}, "")
|
|
||||||
|
|
||||||
var rlockrefreshScript = strings.Join([]string{
|
|
||||||
"local counter = redis.call('hget', KEYS[1], ARGV[2]); ",
|
|
||||||
"if (counter ~= false) then ",
|
|
||||||
"redis.call('pexpire', KEYS[1], ARGV[1]); ",
|
|
||||||
|
|
||||||
"if (redis.call('hlen', KEYS[1]) > 1) then ",
|
|
||||||
"local keys = redis.call('hkeys', KEYS[1]); ",
|
|
||||||
"for n, key in ipairs(keys) do ",
|
|
||||||
"counter = tonumber(redis.call('hget', KEYS[1], key)); ",
|
|
||||||
"if type(counter) == 'number' then ",
|
|
||||||
"for i=counter, 1, -1 do ",
|
|
||||||
"redis.call('pexpire', KEYS[2] .. ':' .. key .. ':rwlock_timeout:' .. i, ARGV[1]); ",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
|
|
||||||
"return 1; ",
|
|
||||||
"end; ",
|
|
||||||
"return 0;",
|
|
||||||
}, "")
|
|
||||||
|
|
||||||
var wlockScript string = strings.Join([]string{
|
|
||||||
"local mode = redis.call('hget', KEYS[1], 'mode'); ",
|
|
||||||
"if (mode == false) then ",
|
|
||||||
"redis.call('hset', KEYS[1], 'mode', 'write'); ",
|
|
||||||
"redis.call('hset', KEYS[1], ARGV[2], 1); ",
|
|
||||||
"redis.call('pexpire', KEYS[1], ARGV[1]); ",
|
|
||||||
"return nil; ",
|
|
||||||
"end; ",
|
|
||||||
"if (mode == 'write') then ",
|
|
||||||
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then ",
|
|
||||||
"redis.call('hincrby', KEYS[1], ARGV[2], 1); ",
|
|
||||||
"local currentExpire = redis.call('pttl', KEYS[1]); ",
|
|
||||||
"redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); ",
|
|
||||||
"return nil; ",
|
|
||||||
"end; ",
|
|
||||||
"end;",
|
|
||||||
"return redis.call('pttl', KEYS[1]);",
|
|
||||||
}, "")
|
|
||||||
|
|
||||||
var wunlockScript string = strings.Join([]string{
|
|
||||||
"local mode = redis.call('hget', KEYS[1], 'mode'); ",
|
|
||||||
"if (mode == false) then ",
|
|
||||||
"redis.call('publish', KEYS[2], ARGV[1]); ",
|
|
||||||
"return 1; ",
|
|
||||||
"end;",
|
|
||||||
"if (mode == 'write') then ",
|
|
||||||
"local lockExists = redis.call('hexists', KEYS[1], ARGV[3]); ",
|
|
||||||
"if (lockExists == 0) then ",
|
|
||||||
"return nil;",
|
|
||||||
"else ",
|
|
||||||
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); ",
|
|
||||||
"if (counter > 0) then ",
|
|
||||||
"redis.call('pexpire', KEYS[1], ARGV[2]); ",
|
|
||||||
"return 0; ",
|
|
||||||
"else ",
|
|
||||||
"redis.call('hdel', KEYS[1], ARGV[3]); ",
|
|
||||||
"if (redis.call('hlen', KEYS[1]) == 1) then ",
|
|
||||||
"redis.call('del', KEYS[1]); ",
|
|
||||||
"redis.call('publish', KEYS[2], ARGV[1]); ",
|
|
||||||
"else ",
|
|
||||||
// has unlocked read-locks
|
|
||||||
"redis.call('hset', KEYS[1], 'mode', 'read'); ",
|
|
||||||
"end; ",
|
|
||||||
"return 1; ",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
"end; ",
|
|
||||||
"return nil;",
|
|
||||||
}, "")
|
|
||||||
|
|
||||||
type redissionReadLocker struct {
|
type redissionReadLocker struct {
|
||||||
redissionLocker
|
redissionLocker
|
||||||
rwTimeoutTokenPrefix string
|
rwTimeoutPrefix string
|
||||||
prefixKey string
|
prefixKey string
|
||||||
|
needRefresh bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *redissionReadLocker) Lock(ctx context.Context, timeout ...time.Duration) {
|
// TODO 将参数中的 ctx 优化掉
|
||||||
|
func (rl *redissionReadLocker) Lock(ctx context.Context, timeout ...time.Duration) error {
|
||||||
if rl.exit == nil {
|
if rl.exit == nil {
|
||||||
rl.exit = make(chan struct{})
|
rl.exit = make(chan struct{})
|
||||||
}
|
}
|
||||||
ttl, err := rl.tryLock()
|
|
||||||
if err != nil {
|
resultErr := rl.tryLock().(*constant.RedisError)
|
||||||
panic(err)
|
if resultErr.Code == constant.UnknownInternalError {
|
||||||
|
rl.logger.Error(resultErr.OutputResultMessage())
|
||||||
|
return fmt.Errorf("get read lock failed:%w", resultErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ttl <= 0 {
|
if (resultErr.Code == constant.LockSuccess) && rl.needRefresh {
|
||||||
rl.once.Do(func() {
|
rl.once.Do(func() {
|
||||||
|
// async refresh lock timeout unitl receive exit singal
|
||||||
go rl.refreshLockTimeout()
|
go rl.refreshLockTimeout()
|
||||||
})
|
})
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
submsg := make(chan struct{}, 1)
|
var acquireTimer *time.Timer
|
||||||
defer close(submsg)
|
|
||||||
sub := rl.client.Subscribe(rl.chankey)
|
|
||||||
|
|
||||||
defer sub.Close()
|
|
||||||
go rl.subscribeLock(sub, submsg)
|
|
||||||
// listen := rl.listenManager.Subscribe(rl.key, rl.token)
|
|
||||||
// defer rl.listenManager.UnSubscribe(rl.key, rl.token)
|
|
||||||
|
|
||||||
timer := time.NewTimer(ttl)
|
|
||||||
defer timer.Stop()
|
|
||||||
var outimer *time.Timer
|
|
||||||
if len(timeout) > 0 && timeout[0] > 0 {
|
if len(timeout) > 0 && timeout[0] > 0 {
|
||||||
outimer = time.NewTimer(timeout[0])
|
acquireTimer = time.NewTimer(timeout[0])
|
||||||
}
|
}
|
||||||
LOOP:
|
|
||||||
for {
|
|
||||||
ttl, err = rl.tryLock()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ttl <= 0 {
|
subMsg := make(chan struct{}, 1)
|
||||||
rl.once.Do(func() {
|
defer close(subMsg)
|
||||||
go rl.refreshLockTimeout()
|
sub := rl.client.Subscribe(rl.waitChankey)
|
||||||
})
|
defer sub.Close()
|
||||||
return
|
go rl.subscribeLock(sub, subMsg)
|
||||||
}
|
|
||||||
if outimer != nil {
|
if len(timeout) > 0 && timeout[0] > 0 {
|
||||||
|
acquireTimer = time.NewTimer(timeout[0])
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case _, ok := <-submsg:
|
case _, ok := <-subMsg:
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("lock listen release")
|
err := errors.New("failed to read the read lock waiting for for the channel message")
|
||||||
|
rl.logger.Error("failed to read the read lock waiting for for the channel message")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.Reset(ttl)
|
resultErr := rl.tryLock().(*constant.RedisError)
|
||||||
case <-ctx.Done():
|
if (resultErr.Code == constant.RLockFailure) || (resultErr.Code == constant.UnknownInternalError) {
|
||||||
// break LOOP
|
rl.logger.Info(resultErr.OutputResultMessage())
|
||||||
panic("lock context already release")
|
continue
|
||||||
case <-timer.C:
|
|
||||||
timer.Reset(ttl)
|
|
||||||
case <-outimer.C:
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
break LOOP
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case _, ok := <-submsg:
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if resultErr.Code == constant.LockSuccess {
|
||||||
panic("lock listen release")
|
rl.logger.Info(resultErr.OutputResultMessage())
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
case <-acquireTimer.C:
|
||||||
timer.Reset(ttl)
|
err := errors.New("the waiting time for obtaining the read lock operation has timed out")
|
||||||
case <-ctx.Done():
|
rl.logger.Info("the waiting time for obtaining the read lock operation has timed out")
|
||||||
// break LOOP
|
return err
|
||||||
panic("lock context already release")
|
|
||||||
case <-timer.C:
|
|
||||||
timer.Reset(ttl)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("get read lock failed:%w", constant.NewRedisError(constant.RLockFailure))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *redissionReadLocker) tryLock() (time.Duration, error) {
|
func (rl *redissionReadLocker) tryLock() error {
|
||||||
writeLockToken := strings.Join([]string{rl.token, "write"}, ":")
|
lockType := constant.LockType
|
||||||
res := rl.client.Eval(rlockScript, []string{rl.key, rl.rwTimeoutTokenPrefix}, rl.lockLeaseTime, rl.token, writeLockToken)
|
res := rl.client.Eval(luascript.RLockScript, []string{rl.key, rl.rwTimeoutPrefix}, rl.lockLeaseTime, rl.token)
|
||||||
v, err := res.Result()
|
v, err := res.Result()
|
||||||
if err != redis.Nil && err != nil {
|
if err != redis.Nil && err != nil {
|
||||||
return 0, err
|
return constant.ConvertResultToErr(constant.UnknownInternalError, lockType, err.Error())
|
||||||
}
|
}
|
||||||
|
return constant.ConvertResultToErr(v.(constant.RedisResult), lockType, "")
|
||||||
if v == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(v.(int64)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *redissionReadLocker) refreshLockTimeout() {
|
func (rl *redissionReadLocker) refreshLockTimeout() {
|
||||||
|
|
@ -271,7 +107,7 @@ LOOP:
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
timer.Reset(lockTime)
|
timer.Reset(lockTime)
|
||||||
// update key expire time
|
// update key expire time
|
||||||
res := rl.client.Eval(rlockrefreshScript, []string{rl.key, rl.prefixKey}, rl.lockLeaseTime, rl.token)
|
res := rl.client.Eval(luascript.RefreshLockScript, []string{rl.key, rl.prefixKey}, rl.lockLeaseTime, rl.token)
|
||||||
val, err := res.Int()
|
val, err := res.Int()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -289,7 +125,7 @@ LOOP:
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *redissionReadLocker) UnLock() {
|
func (rl *redissionReadLocker) UnLock() {
|
||||||
res := rl.client.Eval(runlockScript, []string{rl.key, rl.chankey, rl.rwTimeoutTokenPrefix, rl.prefixKey}, unlockMessage, rl.token)
|
res := rl.client.Eval(luascript.UnRLockScript, []string{rl.key, rl.waitChankey, rl.rwTimeoutPrefix, rl.prefixKey}, unlockMessage, rl.token)
|
||||||
val, err := res.Result()
|
val, err := res.Result()
|
||||||
if err != redis.Nil && err != nil {
|
if err != redis.Nil && err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -318,6 +154,7 @@ func (rl *redissionWriteLocker) Lock(ctx context.Context, timeout ...time.Durati
|
||||||
|
|
||||||
if ttl <= 0 {
|
if ttl <= 0 {
|
||||||
rl.once.Do(func() {
|
rl.once.Do(func() {
|
||||||
|
// async refresh lock timeout unitl receive exit singal
|
||||||
go rl.refreshLockTimeout()
|
go rl.refreshLockTimeout()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
@ -325,7 +162,7 @@ func (rl *redissionWriteLocker) Lock(ctx context.Context, timeout ...time.Durati
|
||||||
|
|
||||||
submsg := make(chan struct{}, 1)
|
submsg := make(chan struct{}, 1)
|
||||||
defer close(submsg)
|
defer close(submsg)
|
||||||
sub := rl.client.Subscribe(rl.chankey)
|
sub := rl.client.Subscribe(rl.waitChankey)
|
||||||
defer sub.Close()
|
defer sub.Close()
|
||||||
go rl.subscribeLock(sub, submsg)
|
go rl.subscribeLock(sub, submsg)
|
||||||
// listen := rl.listenManager.Subscribe(rl.key, rl.token)
|
// listen := rl.listenManager.Subscribe(rl.key, rl.token)
|
||||||
|
|
@ -397,7 +234,7 @@ LOOP:
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *redissionWriteLocker) tryLock() (time.Duration, error) {
|
func (rl *redissionWriteLocker) tryLock() (time.Duration, error) {
|
||||||
res := rl.client.Eval(wlockScript, []string{rl.key}, rl.lockLeaseTime, rl.token)
|
res := rl.client.Eval(luascript.WLockScript, []string{rl.key}, rl.lockLeaseTime, rl.token)
|
||||||
v, err := res.Result()
|
v, err := res.Result()
|
||||||
if err != redis.Nil && err != nil {
|
if err != redis.Nil && err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -411,7 +248,7 @@ func (rl *redissionWriteLocker) tryLock() (time.Duration, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *redissionWriteLocker) UnLock() {
|
func (rl *redissionWriteLocker) UnLock() {
|
||||||
res := rl.client.Eval(wunlockScript, []string{rl.key, rl.chankey}, unlockMessage, rl.lockLeaseTime, rl.token)
|
res := rl.client.Eval(luascript.UnWLockScript, []string{rl.key, rl.waitChankey}, unlockMessage, rl.lockLeaseTime, rl.token)
|
||||||
val, err := res.Result()
|
val, err := res.Result()
|
||||||
if err != redis.Nil && err != nil {
|
if err != redis.Nil && err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -436,16 +273,18 @@ func GetReadLocker(client *redis.Client, ops *RedissionLockConfig) *redissionRea
|
||||||
if len(ops.Prefix) <= 0 {
|
if len(ops.Prefix) <= 0 {
|
||||||
ops.Prefix = "redission-rwlock"
|
ops.Prefix = "redission-rwlock"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ops.ChanPrefix) <= 0 {
|
if len(ops.ChanPrefix) <= 0 {
|
||||||
ops.ChanPrefix = "redission-rwlock-channel"
|
ops.ChanPrefix = "redission-rwlock-channel"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ops.LockLeaseTime == 0 {
|
if ops.LockLeaseTime == 0 {
|
||||||
r.lockLeaseTime = internalLockLeaseTime
|
r.lockLeaseTime = internalLockLeaseTime
|
||||||
}
|
}
|
||||||
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
||||||
r.chankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
r.waitChankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
||||||
tkey := strings.Join([]string{"{", r.key, "}"}, "")
|
tkey := strings.Join([]string{"{", r.key, "}"}, "")
|
||||||
return &redissionReadLocker{redissionLocker: *r, rwTimeoutTokenPrefix: strings.Join([]string{tkey, r.token, "rwlock_timeout"}, ":"), prefixKey: tkey}
|
return &redissionReadLocker{redissionLocker: *r, rwTimeoutPrefix: strings.Join([]string{tkey, r.token, "rwlock_timeout"}, ":"), prefixKey: tkey, needRefresh: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWriteLocker(client *redis.Client, ops *RedissionLockConfig) *redissionWriteLocker {
|
func GetWriteLocker(client *redis.Client, ops *RedissionLockConfig) *redissionWriteLocker {
|
||||||
|
|
@ -454,6 +293,7 @@ func GetWriteLocker(client *redis.Client, ops *RedissionLockConfig) *redissionWr
|
||||||
client: client,
|
client: client,
|
||||||
exit: make(chan struct{}),
|
exit: make(chan struct{}),
|
||||||
once: &sync.Once{},
|
once: &sync.Once{},
|
||||||
|
logger: logger.GetLoggerInstance(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ops.Prefix) <= 0 {
|
if len(ops.Prefix) <= 0 {
|
||||||
|
|
@ -466,6 +306,6 @@ func GetWriteLocker(client *redis.Client, ops *RedissionLockConfig) *redissionWr
|
||||||
r.lockLeaseTime = internalLockLeaseTime
|
r.lockLeaseTime = internalLockLeaseTime
|
||||||
}
|
}
|
||||||
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
||||||
r.chankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
r.waitChankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
||||||
return &redissionWriteLocker{redissionLocker: *r}
|
return &redissionWriteLocker{redissionLocker: *r}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue