44 lines
1.6 KiB
Go
44 lines
1.6 KiB
Go
package flow
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/gogf/gf/v2/database/gredis"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// RedisLock 基于 SETNX 的分布式锁:token 标识持有者身份,Lua 原子释放防误删他人锁。
|
|
// 用途:恢复例程抢锁防多节点对同一 exec 重复恢复(spec §6)
|
|
type RedisLock struct {
|
|
key string
|
|
token string
|
|
ttl int64 // 秒
|
|
}
|
|
|
|
func NewRedisLock(key string, ttlSec int64) *RedisLock {
|
|
return &RedisLock{key: key, token: uuid.NewString(), ttl: ttlSec}
|
|
}
|
|
|
|
// Acquire 抢锁;返回 true 表示抢到(SET NX 成功)。
|
|
// 注意:gogf v2.10.2 的 SetNX(ctx,key,value) 不接收 TTL 参数,直接 SETNX 会永不过期(崩溃后死锁);
|
|
// 故改用 Set + SetOption{NX,TTLOption{EX}},原子地执行 `SET key value EX <ttl> NX`。
|
|
func (l *RedisLock) Acquire(ctx context.Context) (bool, error) {
|
|
r, err := g.Redis().Set(ctx, l.key, l.token, gredis.SetOption{
|
|
TTLOption: gredis.TTLOption{EX: &l.ttl},
|
|
NX: true,
|
|
})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// SET NX 失败时 Redis 返回 nil(空回复),gogf 会转换为值 nil 的 gvar;成功时返回 "OK"
|
|
return !r.IsNil(), nil
|
|
}
|
|
|
|
// Release 释放锁:仅当 key 值仍为本锁 token 时删除(Lua 原子),防止超时后误删他人已续的锁
|
|
func (l *RedisLock) Release(ctx context.Context) error {
|
|
const luaRelease = `if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end`
|
|
_, err := g.Redis().Eval(ctx, luaRelease, 1, []string{l.key}, []any{l.token})
|
|
return err
|
|
}
|