github.com/binbinly/pkg@v0.0.11-0.20240321014439-f4fbf666eb0f/lock/redis.go (about) 1 package lock 2 3 import ( 4 "context" 5 "strings" 6 "time" 7 8 "github.com/pkg/errors" 9 "github.com/redis/go-redis/v9" 10 ) 11 12 // RedisLock is a redis lock. 13 type RedisLock struct { 14 prefix string 15 key string 16 token string 17 rdb *redis.Client 18 ttl time.Duration 19 } 20 21 // Option RedisLock option 22 type Option func(l *RedisLock) 23 24 // WithPrefix with prefix 25 func WithPrefix(prefix string) Option { 26 return func(l *RedisLock) { 27 l.prefix = prefix 28 } 29 } 30 31 // WithTTL with ttl 32 func WithTTL(ttl time.Duration) Option { 33 return func(l *RedisLock) { 34 l.ttl = ttl 35 } 36 } 37 38 // NewRedisLock new a redis lock instance 39 func NewRedisLock(rdb *redis.Client, key string, opts ...Option) *RedisLock { 40 opt := &RedisLock{ 41 rdb: rdb, 42 token: genToken(), 43 prefix: _prefix, 44 ttl: _ttl, 45 } 46 for _, f := range opts { 47 f(opt) 48 } 49 opt.key = strings.Join([]string{opt.prefix, key}, ":") 50 return opt 51 } 52 53 // Lock acquires the lock. 54 func (l *RedisLock) Lock(ctx context.Context) (bool, error) { 55 isSet, err := l.rdb.SetNX(ctx, l.key, l.token, l.ttl).Result() 56 if err == redis.Nil { 57 return false, nil 58 } else if err != nil { 59 return false, errors.Wrapf(err, "[lock] acquires the lock err, key: %s", l.key) 60 } 61 return isSet, nil 62 } 63 64 // Unlock del the lock. 65 // NOTE: token 一致才会执行删除,避免误删,这里用了lua脚本进行事务处理 66 func (l *RedisLock) Unlock(ctx context.Context) (bool, error) { 67 luaScript := "if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 68 ret, err := l.rdb.Eval(ctx, luaScript, []string{l.key}, l.token).Result() 69 if err != nil { 70 return false, err 71 } 72 reply, ok := ret.(int64) 73 if !ok { 74 return false, nil 75 } 76 return reply == 1, nil 77 }