redislock.go 2.0 KB

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