增加集群部署支持,修复定时任务分组添加后选项不显示

This commit is contained in:
孟帅
2023-07-26 16:49:09 +08:00
parent 996ed818ee
commit 12bf36cd15
22 changed files with 1185 additions and 529 deletions

View File

@@ -0,0 +1,59 @@
// Package lock
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package lock
import (
"github.com/gogf/gf/v2/errors/gerror"
"time"
)
const (
// 加锁脚本
lockScript = `
local token = redis.call('get', KEYS[1])
if token then
return 0
else
local setResult = redis.call('setex', KEYS[1], ARGV[2], ARGV[1])
return setResult
end
`
// 续约脚本
renewalScript = `
if redis.call('get',KEYS[1])==ARGV[2] then
return redis.call('expire',KEYS[1],ARGV[1])
end
return 0
`
// 解锁脚本
unlockScript = `
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 2
end
`
)
const (
// DefaultTTL 锁默认过期时间
DefaultTTL = time.Second * 10
// DefaultTryLockInterval 默认重试获取锁间隔时间
DefaultTryLockInterval = time.Millisecond * 100
)
var (
// ErrLockFailed 加锁失败
ErrLockFailed = gerror.New("lock failed")
// ErrTimeout 加锁超时
ErrTimeout = gerror.New("timeout")
// ErrNotCaller 锁持有者不是当前实例
ErrNotCaller = gerror.New("lock not held by the caller")
// ErrNotExist 锁不存在
ErrNotExist = gerror.New("lock does not exist")
)

View File

@@ -0,0 +1,155 @@
// Package lock
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package lock
// 分布式锁
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/guid"
"sync"
"time"
)
// Config 锁配置
type Config struct {
ttl time.Duration // 过期时间
tryLockInterval time.Duration // 重新获取锁间隔
}
// Lock 一把锁 不可重复使用
type Lock struct {
resource string // 锁定的资源
randomValue string // 随机值
watchDog chan struct{} // 看门狗
ttl time.Duration // 过期时间
tryLockInterval time.Duration // 重新获取锁间隔
wg sync.WaitGroup
}
// NewConfig 初始化一个锁配置
func NewConfig(ttl, tryLockInterval time.Duration) *Config {
return &Config{
ttl: ttl,
tryLockInterval: tryLockInterval,
}
}
// Mutex 根据配置创建一把锁
func (lc *Config) Mutex(resource string) *Lock {
return &Lock{
resource: resource,
randomValue: guid.S(),
watchDog: make(chan struct{}),
ttl: lc.ttl,
tryLockInterval: lc.tryLockInterval,
}
}
// Lock 阻塞加锁
func (l *Lock) Lock(ctx context.Context) error {
// 尝试加锁
err := l.TryLock(ctx)
if err == nil {
return nil
}
if !gerror.Is(err, ErrLockFailed) {
return err
}
// 加锁失败,不断尝试
ticker := time.NewTicker(l.tryLockInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
// 超时
return ErrTimeout
case <-ticker.C:
// 重新尝试加锁
err = l.TryLock(ctx)
if err == nil {
return nil
}
if !gerror.Is(err, ErrLockFailed) {
return err
}
}
}
}
// TryLock 尝试加锁,如果失败立即返回错误,而不会阻塞等待锁
func (l *Lock) TryLock(ctx context.Context) error {
var args = []interface{}{l.randomValue, l.ttl.Seconds()}
eval, err := g.Redis().GroupScript().Eval(ctx, lockScript, 1, []string{l.resource}, args)
if err != nil {
return err
}
if eval.String() != "OK" {
return ErrLockFailed
}
go l.startWatchDog()
return nil
}
// Unlock 解锁
func (l *Lock) Unlock(ctx context.Context) error {
var args []interface{}
args = append(args, l.randomValue)
eval, err := g.Redis().GroupScript().Eval(ctx, unlockScript, 1, []string{l.resource}, args)
if eval.Int() == 2 {
return ErrNotCaller
}
if eval.Int() == 0 {
return ErrNotExist
}
close(l.watchDog)
return err
}
// startWatchDog 看门狗
func (l *Lock) startWatchDog() {
resetTTLInterval := l.ttl / 3
ticker := time.NewTicker(resetTTLInterval)
defer ticker.Stop()
l.wg.Add(1)
defer l.wg.Wait()
conn := g.Redis()
for {
select {
case <-ticker.C:
// 延长锁的过期时间
ctx, cancel := context.WithTimeout(context.Background(), resetTTLInterval)
var args = []interface{}{l.ttl.Seconds(), l.randomValue}
eval, err := conn.GroupScript().Eval(ctx, renewalScript, 1, []string{l.resource}, args)
cancel()
// 异常或锁已经不存在则不再续期
if err != nil || eval.Int() < 1 {
return
}
case <-l.watchDog:
// 已经解锁
return
}
}
}
// defaultMutex 一个默认配置锁
var defaultMutex = NewConfig(DefaultTTL, DefaultTryLockInterval)
// Mutex 获取默认锁
func Mutex(resource string) *Lock {
return defaultMutex.Mutex(resource)
}

View File

@@ -0,0 +1,118 @@
// Package lock_test
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package lock_test
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"hotgo/internal/library/hgrds/lock"
"sync"
"testing"
"time"
)
func TestDefaultLock(t *testing.T) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
l := lock.Mutex("test")
err := l.TryLock(context.Background())
if err != nil {
t.Error(err)
}
time.Sleep(lock.DefaultTTL)
err = l.Unlock(context.Background())
if err != nil {
t.Error(err)
}
}()
time.Sleep(time.Second)
go func() {
defer wg.Done()
l := lock.Mutex("test")
err := l.TryLock(context.Background())
if err != nil && !gerror.Is(err, lock.ErrLockFailed) {
t.Error(err)
}
}()
wg.Wait()
}
func TestNewLock(t *testing.T) {
locker := lock.NewConfig(time.Second*30, time.Second)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
l := locker.Mutex("test")
err := l.TryLock(context.Background())
if err != nil {
t.Error(err)
}
time.Sleep(lock.DefaultTTL)
err = l.Unlock(context.Background())
if err != nil {
t.Error(err)
}
}()
time.Sleep(time.Second)
go func() {
defer wg.Done()
l := lock.Mutex("test")
err := l.TryLock(context.Background())
if err != nil && !gerror.Is(err, lock.ErrLockFailed) {
t.Error(err)
}
}()
wg.Wait()
}
func TestNewLock2(t *testing.T) {
locker := lock.NewConfig(time.Second*30, time.Second)
var wg sync.WaitGroup
wg.Add(2)
count := 0
times := 1000
go func() {
defer wg.Done()
for i := 0; i < times; i++ {
l := locker.Mutex("test")
err := l.Lock(context.Background())
if err != nil {
t.Error(err)
}
count++
err = l.Unlock(context.Background())
if err != nil {
t.Error(err)
}
}
}()
go func() {
defer wg.Done()
for i := 0; i < times; i++ {
l := lock.Mutex("test")
err := l.Lock(context.Background())
if err != nil {
t.Error(err)
}
count++
err = l.Unlock(context.Background())
if err != nil {
t.Error(err)
}
}
}()
wg.Wait()
if count != times*2 {
t.Errorf("count = %d", count)
}
}