我们在实际开发中,经常会遇到对共享变量进行多线程同步访问的情况,为了保证数据、业务的一致性,我们需要用到分布式锁进行处理。

import (
    "context"
    "errors"
    "fmt"
    "github.com/go-redis/redis/v8"
    "sync"
    "time"
)

type Locker struct {
    prefix string
    client *redis.Client
}

var (
    instance       *Locker
    once           sync.Once
    defaultTimeout = 3 * time.Second
    ErrClientIsNil = errors.New("client is nil, stop command")
)

// NewLocker 获取锁实例
func NewLocker(addr, password string, db int) *Locker {
    once.Do(func() {
        instance = new(Locker)
        instance.prefix = "rlc"
        instance.client = redis.NewClient(&redis.Options{
            Addr:     addr,
            Password: password,
            DB:       db,
        })
    })
    return instance
}

// Lock 请求锁资源
// 注意超时设定,如果不带单位,那么默认是纳秒,所以一定要带上单位。如果传入0,那么默认是 `defaultTimeout`
//
func (lock *Locker) Lock(ctx context.Context, key string, value interface{}, expiration time.Duration) (bool, error) {
    if lock.client == nil {
        return false, ErrClientIsNil
    }
    if expiration <= 0 {
        expiration = defaultTimeout
    }
    k := lock.key(key)
    result, err := lock.client.SetNX(ctx, k, value, expiration).Result()
    if err != nil {
        return false, err
    }
    return result, nil
}
// Unlock 主动释放锁资源
func (lock *Locker) Unlock(ctx context.Context, key string) (int64, error) {
    if lock.client == nil {
        return 0, ErrClientIsNil
    }
    k := lock.key(key)
    val, err := lock.client.Del(ctx, k).Result()
    if err != nil {
        return 0, err
    }
    return val, nil
}

// key 加上默认前缀,作为区分
func (lock *Locker) key(key string) string {
    return fmt.Sprintf("%s:%s", lock.prefix, key)
}