commit cbd30f56f2de5976b522ff16cd2780c336b3dbe4 Author: Yun Date: Sun Nov 26 20:42:30 2023 +0800 全局锁 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..467c086 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module code.yun.ink/pkg/lockx + +go 1.19 + +require ( + code.yun.ink/open/timer v1.0.1 + github.com/go-redis/redis/v8 v8.11.5 +) + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cbe69a9 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +code.yun.ink/open/timer v1.0.1 h1:ZWecU5K0rFB15p8DZubozTEwo1vrO4mUCRwEoD1tbEQ= +code.yun.ink/open/timer v1.0.1/go.mod h1:i6+mEL5eUab+9ZDtxt9S5fMiJkwVLBRsNmfLj2qzN30= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/lockx.go b/lockx.go new file mode 100644 index 0000000..23f1ede --- /dev/null +++ b/lockx.go @@ -0,0 +1,120 @@ +package lockx + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/go-redis/redis/v8" +) + +// 全局锁 +type globalLock struct { + redis *redis.Client + ctx context.Context + cancel context.CancelFunc + uniqueKey string + value string +} + +func NewGlobalLock(ctx context.Context, red *redis.Client, uniqueKey string) *globalLock { + ctx, cancel := context.WithTimeout(ctx, time.Second*30) + return &globalLock{ + redis: red, + ctx: ctx, + cancel: cancel, + uniqueKey: uniqueKey, + value: fmt.Sprintf("%d", time.Now().UnixNano()), + } +} + +// 获取锁 +func (g *globalLock) Lock() bool { + + script := ` + local token = redis.call('get',KEYS[1]) + if token == false + then + return redis.call('set',KEYS[1],ARGV[1],'EX',ARGV[2]) + end + return 'ERROR' + ` + + resp, err := g.redis.Eval(g.ctx, script, []string{g.uniqueKey}, g.value, 5).Result() + if resp != "OK" { + _ = err + log.Println("globalLock Lock", resp, err, g.uniqueKey, g.value) + } + if resp == "OK" { + g.refresh() + return true + } + return false +} + +// 尝试获取锁 +func (g *globalLock) Try(limitTimes int) bool { + for i := 0; i < limitTimes; i++ { + if g.Lock() { + return true + } + time.Sleep(time.Millisecond * 100) + } + return false +} + +// 删除锁 +func (g *globalLock) Unlock() bool { + + script := ` + local token = redis.call('get',KEYS[1]) + if token == ARGV[1] + then + redis.call('del',KEYS[1]) + return 'OK' + end + return 'ERROR' + ` + + resp, err := g.redis.Eval(g.ctx, script, []string{g.uniqueKey}, g.value).Result() + if resp != "OK" { + log.Println("globalLock Unlock", resp, err, g.uniqueKey, g.value) + } + g.cancel() + return false +} + +// 刷新锁 +func (g *globalLock) refresh() { + go func() { + t := time.NewTicker(time.Second) + for { + select { + case <-t.C: + g.refreshExec() + case <-g.ctx.Done(): + t.Stop() + return + } + } + }() +} + +func (g *globalLock) refreshExec() bool { + script := ` + local token = redis.call('get',KEYS[1]) + if token == ARGV[1] + then + redis.call('set',KEYS[1],ARGV[1],'EX',ARGV[2]) + return 'OK' + end + return 'ERROR' + ` + + resp, err := g.redis.Eval(g.ctx, script, []string{g.uniqueKey}, g.value, 5).Result() + if resp != "OK" { + log.Println("globalLock refresh", resp, err, g.uniqueKey, g.value) + } + return resp == "OK" +} diff --git a/lockx_test.go b/lockx_test.go new file mode 100644 index 0000000..9b36e9b --- /dev/null +++ b/lockx_test.go @@ -0,0 +1,52 @@ +package lockx_test + +import ( + "context" + "fmt" + "testing" + + "code.yun.ink/open/timer/lockx" + "github.com/go-redis/redis/v8" +) + +var Redis *redis.Client + +// func TestMain(m *testing.M) { +// client := redis.NewClient(&redis.Options{ +// Addr: "127.0.0.1" + ":" + "6379", +// Password: "", // no password set +// DB: 0, // use default DB +// }) +// if client == nil { +// fmt.Println("redis init error") +// return +// } +// // fmt.Println("ffff") +// Redis = client +// } + +func TestLockx(t *testing.T) { + client := redis.NewClient(&redis.Options{ + Addr: "127.0.0.1" + ":" + "6379", + Password: "", // no password set + DB: 0, // use default DB + }) + if client == nil { + fmt.Println("redis init error") + return + } + fmt.Println("begin") + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + lock := lockx.NewGlobalLock(ctx, client, "lockx:test") + + if !lock.Lock() { + fmt.Println("lock error") + } + defer lock.Unlock() + + fmt.Println("ssss") + +}