| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
- package redisx
- import (
- "context"
- "errors"
- "time"
- "github.com/google/uuid"
- "github.com/redis/go-redis/v9"
- )
- // 分布式锁
- type RedisLock struct {
- key string
- token string
- ttl time.Duration
- }
- // TryLock 非阻塞尝试加锁
- func TryLock(ctx context.Context, key string, ttl time.Duration) (*RedisLock, bool, error) {
- token := uuid.NewString()
- ok, err := getClient().SetNX(ctx, key, token, ttl).Result()
- if err != nil {
- return nil, false, err
- }
- if !ok {
- return nil, false, nil
- }
- return &RedisLock{key: key, token: token, ttl: ttl}, true, nil
- }
- // Lock 阻塞加锁,可设置重试间隔和超时
- func Lock(ctx context.Context, key string, ttl, retryInterval, timeout time.Duration) (*RedisLock, error) {
- start := time.Now()
- for {
- lock, ok, err := TryLock(ctx, key, ttl)
- if err != nil {
- return nil, err
- }
- if ok {
- return lock, nil
- }
- if timeout > 0 && time.Since(start) >= timeout {
- return nil, errors.New("lock timeout")
- }
- select {
- case <-ctx.Done():
- return nil, ctx.Err()
- case <-time.After(retryInterval):
- }
- }
- }
- // Release 安全释放锁(Lua 脚本)
- func (l *RedisLock) Release(ctx context.Context) error {
- const luaScript = `
- if redis.call("get", KEYS[1]) == ARGV[1] then
- return redis.call("del", KEYS[1])
- else
- return 0
- end
- `
- res, err := redis.NewScript(luaScript).Run(ctx, getClient(), []string{l.key}, l.token).Int()
- if err != nil {
- return err
- }
- if res == 0 {
- return errors.New("unlock failed: not owner")
- }
- return nil
- }
- // SafeRelease 带日志保护版本
- func (l *RedisLock) SafeRelease(ctx context.Context) {
- _ = l.Release(ctx)
- }
|