redis_lock.go 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. package utils
  2. import (
  3. "context"
  4. "errors"
  5. "time"
  6. )
  7. var ErrRedisNotInitialized = errors.New("redis client is not initialized, set redis_enable=true and restart")
  8. const redisLockReleaseScript = `
  9. if redis.call("GET", KEYS[1]) == ARGV[1] then
  10. return redis.call("DEL", KEYS[1])
  11. else
  12. return 0
  13. end
  14. `
  15. // AcquireRedisLock tries to create a distributed lock with key and value.
  16. // It returns true when lock is acquired, false when lock already exists.
  17. func AcquireRedisLock(key, value string) (bool, error) {
  18. if RedisClient == nil {
  19. return false, ErrRedisNotInitialized
  20. }
  21. if key == "" || value == "" {
  22. return false, errors.New("key and value must not be empty")
  23. }
  24. lockTTLMS := redisInt("redis_lock_ttl_ms", 30000)
  25. opTimeoutMS := redisInt("redis_lock_op_timeout_ms", 1000)
  26. ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opTimeoutMS)*time.Millisecond)
  27. defer cancel()
  28. return RedisClient.SetNX(ctx, key, value, time.Duration(lockTTLMS)*time.Millisecond).Result()
  29. }
  30. // ReleaseRedisLock releases lock only when key currently matches value.
  31. // It returns true when released successfully.
  32. func ReleaseRedisLock(key, value string) (bool, error) {
  33. if RedisClient == nil {
  34. return false, ErrRedisNotInitialized
  35. }
  36. if key == "" || value == "" {
  37. return false, errors.New("key and value must not be empty")
  38. }
  39. opTimeoutMS := redisInt("redis_lock_op_timeout_ms", 1000)
  40. ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opTimeoutMS)*time.Millisecond)
  41. defer cancel()
  42. n, err := RedisClient.Eval(ctx, redisLockReleaseScript, []string{key}, value).Int64()
  43. if err != nil {
  44. return false, err
  45. }
  46. return n == 1, nil
  47. }