package lock import ( "context" "errors" "time" "github.com/redis/go-redis/v9" ) var ( ErrLockNotAcquired = errors.New("lock not acquired") ErrLockNotHeld = errors.New("lock not held by this owner") ) // DistributedLock 基于 Redis 的分布式锁 type DistributedLock struct { client *redis.Client key string owner string // 唯一标识,用于安全释放锁 ttl time.Duration } // New 创建一个分布式锁实例 // - client: Redis 客户端 // - key: 锁的 key // - owner: 持有者唯一标识(如 UUID) // - ttl: 锁的过期时间,防止死锁 func New(client *redis.Client, key string, owner string, ttl time.Duration) *DistributedLock { return &DistributedLock{ client: client, key: key, owner: owner, ttl: ttl, } } // Acquire 尝试获取锁(非阻塞) func (l *DistributedLock) Acquire(ctx context.Context) (bool, error) { ok, err := l.client.SetNX(ctx, l.key, l.owner, l.ttl).Result() if err != nil { return false, err } return ok, nil } // AcquireWithRetry 带重试的获取锁 // - retryInterval: 重试间隔 // - maxRetries: 最大重试次数,0 表示无限重试 func (l *DistributedLock) AcquireWithRetry(ctx context.Context, retryInterval time.Duration, maxRetries int) (bool, error) { for i := 0; maxRetries == 0 || i < maxRetries; i++ { acquired, err := l.Acquire(ctx) if err != nil { return false, err } if acquired { return true, nil } select { case <-ctx.Done(): return false, ctx.Err() case <-time.After(retryInterval): } } return false, ErrLockNotAcquired } // Release 释放锁(仅当自己持有时才释放,使用 Lua 脚本保证原子性) func (l *DistributedLock) Release(ctx context.Context) error { script := redis.NewScript(` if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end `) result, err := script.Run(ctx, l.client, []string{l.key}, l.owner).Int64() if err != nil { return err } if result == 0 { return ErrLockNotHeld } return nil } // Refresh 续期锁(延长 TTL,仅当自己持有时才续期) func (l *DistributedLock) Refresh(ctx context.Context) (bool, error) { script := redis.NewScript(` if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("PEXPIRE", KEYS[1], ARGV[2]) else return 0 end `) result, err := script.Run(ctx, l.client, []string{l.key}, l.owner, l.ttl.Milliseconds()).Int64() if err != nil { return false, err } return result == 1, nil } // IsHeld 检查锁是否被持有(不一定是自己) func (l *DistributedLock) IsHeld(ctx context.Context) (bool, error) { val, err := l.client.Exists(ctx, l.key).Result() if err != nil { return false, err } return val > 0, nil }