redislock.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package redis
  2. import (
  3. "math/rand"
  4. "strconv"
  5. "sync/atomic"
  6. "time"
  7. red "github.com/go-redis/redis"
  8. "github.com/tal-tech/go-zero/core/logx"
  9. )
  10. const (
  11. letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  12. lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
  13. redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
  14. return "OK"
  15. else
  16. return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
  17. end`
  18. delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
  19. return redis.call("DEL", KEYS[1])
  20. else
  21. return 0
  22. end`
  23. randomLen = 16
  24. tolerance = 500 // milliseconds
  25. millisPerSecond = 1000
  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: randomStr(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 expire.
  79. func (rl *RedisLock) SetExpire(seconds int) {
  80. atomic.StoreUint32(&rl.seconds, uint32(seconds))
  81. }
  82. func randomStr(n int) string {
  83. b := make([]byte, n)
  84. for i := range b {
  85. b[i] = letters[rand.Intn(len(letters))]
  86. }
  87. return string(b)
  88. }