2025-02-28 16:00:16 +08:00
|
|
|
package distributed_lock
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-03-05 16:42:59 +08:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2025-02-28 16:00:16 +08:00
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
"modelRT/distributedlock/constant"
|
|
|
|
|
"modelRT/distributedlock/luascript"
|
|
|
|
|
"modelRT/logger"
|
|
|
|
|
|
2025-02-28 16:00:16 +08:00
|
|
|
"github.com/go-redis/redis"
|
|
|
|
|
uuid "github.com/google/uuid"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type redissionReadLocker struct {
|
|
|
|
|
redissionLocker
|
2025-03-05 16:42:59 +08:00
|
|
|
rwTimeoutPrefix string
|
|
|
|
|
prefixKey string
|
|
|
|
|
needRefresh bool
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
// TODO 将参数中的 ctx 优化掉
|
|
|
|
|
func (rl *redissionReadLocker) Lock(ctx context.Context, timeout ...time.Duration) error {
|
2025-02-28 16:00:16 +08:00
|
|
|
if rl.exit == nil {
|
|
|
|
|
rl.exit = make(chan struct{})
|
|
|
|
|
}
|
2025-03-05 16:42:59 +08:00
|
|
|
|
|
|
|
|
resultErr := rl.tryLock().(*constant.RedisError)
|
|
|
|
|
if resultErr.Code == constant.UnknownInternalError {
|
|
|
|
|
rl.logger.Error(resultErr.OutputResultMessage())
|
|
|
|
|
return fmt.Errorf("get read lock failed:%w", resultErr)
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
if (resultErr.Code == constant.LockSuccess) && rl.needRefresh {
|
2025-02-28 16:00:16 +08:00
|
|
|
rl.once.Do(func() {
|
2025-03-05 16:42:59 +08:00
|
|
|
// async refresh lock timeout unitl receive exit singal
|
2025-02-28 16:00:16 +08:00
|
|
|
go rl.refreshLockTimeout()
|
|
|
|
|
})
|
2025-03-05 16:42:59 +08:00
|
|
|
return nil
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
var acquireTimer *time.Timer
|
|
|
|
|
if len(timeout) > 0 && timeout[0] > 0 {
|
|
|
|
|
acquireTimer = time.NewTimer(timeout[0])
|
|
|
|
|
}
|
2025-02-28 16:00:16 +08:00
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
subMsg := make(chan struct{}, 1)
|
|
|
|
|
defer close(subMsg)
|
|
|
|
|
sub := rl.client.Subscribe(rl.waitChankey)
|
2025-02-28 16:00:16 +08:00
|
|
|
defer sub.Close()
|
2025-03-05 16:42:59 +08:00
|
|
|
go rl.subscribeLock(sub, subMsg)
|
2025-02-28 16:00:16 +08:00
|
|
|
|
|
|
|
|
if len(timeout) > 0 && timeout[0] > 0 {
|
2025-03-05 16:42:59 +08:00
|
|
|
acquireTimer = time.NewTimer(timeout[0])
|
|
|
|
|
for {
|
2025-02-28 16:00:16 +08:00
|
|
|
select {
|
2025-03-05 16:42:59 +08:00
|
|
|
case _, ok := <-subMsg:
|
2025-02-28 16:00:16 +08:00
|
|
|
if !ok {
|
2025-03-05 16:42:59 +08:00
|
|
|
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
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
resultErr := rl.tryLock().(*constant.RedisError)
|
|
|
|
|
if (resultErr.Code == constant.RLockFailure) || (resultErr.Code == constant.UnknownInternalError) {
|
|
|
|
|
rl.logger.Info(resultErr.OutputResultMessage())
|
|
|
|
|
continue
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
if resultErr.Code == constant.LockSuccess {
|
|
|
|
|
rl.logger.Info(resultErr.OutputResultMessage())
|
|
|
|
|
return nil
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
2025-03-05 16:42:59 +08:00
|
|
|
case <-acquireTimer.C:
|
|
|
|
|
err := errors.New("the waiting time for obtaining the read lock operation has timed out")
|
|
|
|
|
rl.logger.Info("the waiting time for obtaining the read lock operation has timed out")
|
|
|
|
|
return err
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-05 16:42:59 +08:00
|
|
|
return fmt.Errorf("get read lock failed:%w", constant.NewRedisError(constant.RLockFailure))
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:42:59 +08:00
|
|
|
func (rl *redissionReadLocker) tryLock() error {
|
|
|
|
|
lockType := constant.LockType
|
|
|
|
|
res := rl.client.Eval(luascript.RLockScript, []string{rl.key, rl.rwTimeoutPrefix}, rl.lockLeaseTime, rl.token)
|
2025-02-28 16:00:16 +08:00
|
|
|
v, err := res.Result()
|
|
|
|
|
if err != redis.Nil && err != nil {
|
2025-03-05 16:42:59 +08:00
|
|
|
return constant.ConvertResultToErr(constant.UnknownInternalError, lockType, err.Error())
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
2025-03-05 16:42:59 +08:00
|
|
|
return constant.ConvertResultToErr(v.(constant.RedisResult), lockType, "")
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rl *redissionReadLocker) refreshLockTimeout() {
|
|
|
|
|
rl.logger.Debug("rlock: %s lock %s\n", zap.String("token", rl.token), zap.String("key", rl.key))
|
|
|
|
|
lockTime := time.Duration(rl.lockLeaseTime/3) * time.Millisecond
|
|
|
|
|
timer := time.NewTimer(lockTime)
|
|
|
|
|
defer timer.Stop()
|
|
|
|
|
LOOP:
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-timer.C:
|
|
|
|
|
timer.Reset(lockTime)
|
|
|
|
|
// update key expire time
|
2025-03-05 16:42:59 +08:00
|
|
|
res := rl.client.Eval(luascript.RefreshLockScript, []string{rl.key, rl.prefixKey}, rl.lockLeaseTime, rl.token)
|
2025-02-28 16:00:16 +08:00
|
|
|
val, err := res.Int()
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
if val == 0 {
|
|
|
|
|
rl.logger.Debug("not find the rlock key of self")
|
|
|
|
|
break LOOP
|
|
|
|
|
}
|
|
|
|
|
case <-rl.exit:
|
|
|
|
|
break LOOP
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
rl.logger.Debug("rlock: refresh routine release", zap.String("token", rl.token))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rl *redissionReadLocker) UnLock() {
|
2025-03-05 16:42:59 +08:00
|
|
|
res := rl.client.Eval(luascript.UnRLockScript, []string{rl.key, rl.waitChankey, rl.rwTimeoutPrefix, rl.prefixKey}, unlockMessage, rl.token)
|
2025-02-28 16:00:16 +08:00
|
|
|
val, err := res.Result()
|
|
|
|
|
if err != redis.Nil && err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
if val == nil {
|
|
|
|
|
panic("attempt to unlock lock, not locked by current routine by lock id:" + rl.token)
|
|
|
|
|
}
|
|
|
|
|
rl.logger.Debug("lock: %s unlock %s\n", zap.String("token", rl.token), zap.String("key", rl.key))
|
|
|
|
|
if val.(int64) == 1 {
|
|
|
|
|
rl.cancelRefreshLockTime()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type redissionWriteLocker struct {
|
|
|
|
|
redissionLocker
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rl *redissionWriteLocker) Lock(ctx context.Context, timeout ...time.Duration) {
|
|
|
|
|
if rl.exit == nil {
|
|
|
|
|
rl.exit = make(chan struct{})
|
|
|
|
|
}
|
|
|
|
|
ttl, err := rl.tryLock()
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ttl <= 0 {
|
|
|
|
|
rl.once.Do(func() {
|
2025-03-05 16:42:59 +08:00
|
|
|
// async refresh lock timeout unitl receive exit singal
|
2025-02-28 16:00:16 +08:00
|
|
|
go rl.refreshLockTimeout()
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
submsg := make(chan struct{}, 1)
|
|
|
|
|
defer close(submsg)
|
2025-03-05 16:42:59 +08:00
|
|
|
sub := rl.client.Subscribe(rl.waitChankey)
|
2025-02-28 16:00:16 +08:00
|
|
|
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()
|
|
|
|
|
// outimer 理解为如果超过这个时间没有获取到锁,就直接放弃
|
|
|
|
|
var outimer *time.Timer
|
|
|
|
|
if len(timeout) > 0 && timeout[0] > 0 {
|
|
|
|
|
outimer = time.NewTimer(timeout[0])
|
|
|
|
|
}
|
|
|
|
|
LOOP:
|
|
|
|
|
for {
|
|
|
|
|
ttl, err = rl.tryLock()
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ttl <= 0 {
|
|
|
|
|
rl.once.Do(func() {
|
|
|
|
|
go rl.refreshLockTimeout()
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if outimer != nil {
|
|
|
|
|
select {
|
|
|
|
|
case _, ok := <-submsg:
|
|
|
|
|
if !timer.Stop() {
|
|
|
|
|
<-timer.C
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
panic("lock listen release")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timer.Reset(ttl)
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
// break LOOP
|
|
|
|
|
panic("lock context already release")
|
|
|
|
|
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 {
|
|
|
|
|
panic("lock listen release")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timer.Reset(ttl)
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
// break LOOP
|
|
|
|
|
panic("lock context already release")
|
|
|
|
|
case <-timer.C:
|
|
|
|
|
timer.Reset(ttl)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rl *redissionWriteLocker) tryLock() (time.Duration, error) {
|
2025-03-05 16:42:59 +08:00
|
|
|
res := rl.client.Eval(luascript.WLockScript, []string{rl.key}, rl.lockLeaseTime, rl.token)
|
2025-02-28 16:00:16 +08:00
|
|
|
v, err := res.Result()
|
|
|
|
|
if err != redis.Nil && err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if v == nil {
|
|
|
|
|
return 0, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return time.Duration(v.(int64)), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rl *redissionWriteLocker) UnLock() {
|
2025-03-05 16:42:59 +08:00
|
|
|
res := rl.client.Eval(luascript.UnWLockScript, []string{rl.key, rl.waitChankey}, unlockMessage, rl.lockLeaseTime, rl.token)
|
2025-02-28 16:00:16 +08:00
|
|
|
val, err := res.Result()
|
|
|
|
|
if err != redis.Nil && err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
if val == nil {
|
|
|
|
|
panic("attempt to unlock lock, not locked by current routine by lock id:" + rl.token)
|
|
|
|
|
}
|
|
|
|
|
rl.logger.Debug("lock: unlock", zap.String("token", rl.token), zap.String("key", rl.key))
|
|
|
|
|
if val.(int64) == 1 {
|
|
|
|
|
rl.cancelRefreshLockTime()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetReadLocker(client *redis.Client, ops *RedissionLockConfig) *redissionReadLocker {
|
|
|
|
|
r := &redissionLocker{
|
|
|
|
|
token: uuid.New().String(),
|
|
|
|
|
client: client,
|
|
|
|
|
exit: make(chan struct{}),
|
|
|
|
|
once: &sync.Once{},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(ops.Prefix) <= 0 {
|
|
|
|
|
ops.Prefix = "redission-rwlock"
|
|
|
|
|
}
|
2025-03-05 16:42:59 +08:00
|
|
|
|
2025-02-28 16:00:16 +08:00
|
|
|
if len(ops.ChanPrefix) <= 0 {
|
|
|
|
|
ops.ChanPrefix = "redission-rwlock-channel"
|
|
|
|
|
}
|
2025-03-05 16:42:59 +08:00
|
|
|
|
2025-02-28 16:00:16 +08:00
|
|
|
if ops.LockLeaseTime == 0 {
|
|
|
|
|
r.lockLeaseTime = internalLockLeaseTime
|
|
|
|
|
}
|
|
|
|
|
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
2025-03-05 16:42:59 +08:00
|
|
|
r.waitChankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
2025-02-28 16:00:16 +08:00
|
|
|
tkey := strings.Join([]string{"{", r.key, "}"}, "")
|
2025-03-05 16:42:59 +08:00
|
|
|
return &redissionReadLocker{redissionLocker: *r, rwTimeoutPrefix: strings.Join([]string{tkey, r.token, "rwlock_timeout"}, ":"), prefixKey: tkey, needRefresh: true}
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetWriteLocker(client *redis.Client, ops *RedissionLockConfig) *redissionWriteLocker {
|
|
|
|
|
r := &redissionLocker{
|
|
|
|
|
token: uuid.New().String(),
|
|
|
|
|
client: client,
|
|
|
|
|
exit: make(chan struct{}),
|
|
|
|
|
once: &sync.Once{},
|
2025-03-05 16:42:59 +08:00
|
|
|
logger: logger.GetLoggerInstance(),
|
2025-02-28 16:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(ops.Prefix) <= 0 {
|
|
|
|
|
ops.Prefix = "redission-rwlock"
|
|
|
|
|
}
|
|
|
|
|
if len(ops.ChanPrefix) <= 0 {
|
|
|
|
|
ops.ChanPrefix = "redission-rwlock-channel"
|
|
|
|
|
}
|
|
|
|
|
if ops.LockLeaseTime == 0 {
|
|
|
|
|
r.lockLeaseTime = internalLockLeaseTime
|
|
|
|
|
}
|
|
|
|
|
r.key = strings.Join([]string{ops.Prefix, ops.Key}, ":")
|
2025-03-05 16:42:59 +08:00
|
|
|
r.waitChankey = strings.Join([]string{ops.ChanPrefix, ops.Key}, ":")
|
2025-02-28 16:00:16 +08:00
|
|
|
return &redissionWriteLocker{redissionLocker: *r}
|
|
|
|
|
}
|