redislock.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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. if err == red.Nil {
  51. return false, nil
  52. } else if err != nil {
  53. logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
  54. return false, err
  55. } else if resp == nil {
  56. return false, nil
  57. }
  58. reply, ok := resp.(string)
  59. if ok && reply == "OK" {
  60. return true, nil
  61. }
  62. logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
  63. return false, nil
  64. }
  65. // Release releases the lock.
  66. func (rl *RedisLock) Release() (bool, error) {
  67. resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
  68. if err != nil {
  69. return false, err
  70. }
  71. reply, ok := resp.(int64)
  72. if !ok {
  73. return false, nil
  74. }
  75. return reply == 1, nil
  76. }
  77. // SetExpire sets the expire.
  78. func (rl *RedisLock) SetExpire(seconds int) {
  79. atomic.StoreUint32(&rl.seconds, uint32(seconds))
  80. }
  81. func randomStr(n int) string {
  82. b := make([]byte, n)
  83. for i := range b {
  84. b[i] = letters[rand.Intn(len(letters))]
  85. }
  86. return string(b)
  87. }