redislock.go 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package redis
  2. import (
  3. "math/rand"
  4. "strconv"
  5. "sync/atomic"
  6. "time"
  7. red "github.com/go-redis/redis/v8"
  8. "github.com/zeromicro/go-zero/core/logx"
  9. "github.com/zeromicro/go-zero/core/stringx"
  10. )
  11. const (
  12. randomLen = 16
  13. tolerance = 500 // milliseconds
  14. millisPerSecond = 1000
  15. lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
  16. redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
  17. return "OK"
  18. else
  19. return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
  20. end`
  21. delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
  22. return redis.call("DEL", KEYS[1])
  23. else
  24. return 0
  25. end`
  26. )
  27. // A RedisLock is a redis lock.
  28. type RedisLock struct {
  29. store *Redis
  30. seconds uint32
  31. key string
  32. id string
  33. }
  34. func init() {
  35. rand.Seed(time.Now().UnixNano())
  36. }
  37. // NewRedisLock returns a RedisLock.
  38. func NewRedisLock(store *Redis, key string) *RedisLock {
  39. return &RedisLock{
  40. store: store,
  41. key: key,
  42. id: stringx.Randn(randomLen),
  43. }
  44. }
  45. // Acquire acquires the lock.
  46. func (rl *RedisLock) Acquire() (bool, error) {
  47. seconds := atomic.LoadUint32(&rl.seconds)
  48. resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
  49. rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
  50. })
  51. if err == red.Nil {
  52. return false, nil
  53. } else if err != nil {
  54. logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
  55. return false, err
  56. } else if resp == nil {
  57. return false, nil
  58. }
  59. reply, ok := resp.(string)
  60. if ok && reply == "OK" {
  61. return true, nil
  62. }
  63. logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
  64. return false, nil
  65. }
  66. // Release releases the lock.
  67. func (rl *RedisLock) Release() (bool, error) {
  68. resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
  69. if err != nil {
  70. return false, err
  71. }
  72. reply, ok := resp.(int64)
  73. if !ok {
  74. return false, nil
  75. }
  76. return reply == 1, nil
  77. }
  78. // SetExpire sets the expiration.
  79. func (rl *RedisLock) SetExpire(seconds int) {
  80. atomic.StoreUint32(&rl.seconds, uint32(seconds))
  81. }