redislock.go 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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. type RedisLock struct {
  28. store *Redis
  29. seconds uint32
  30. key string
  31. id string
  32. }
  33. func init() {
  34. rand.Seed(time.Now().UnixNano())
  35. }
  36. func NewRedisLock(store *Redis, key string) *RedisLock {
  37. return &RedisLock{
  38. store: store,
  39. key: key,
  40. id: randomStr(randomLen),
  41. }
  42. }
  43. func (rl *RedisLock) Acquire() (bool, error) {
  44. seconds := atomic.LoadUint32(&rl.seconds)
  45. resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
  46. rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance)})
  47. if err == red.Nil {
  48. return false, nil
  49. } else if err != nil {
  50. logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
  51. return false, err
  52. } else if resp == nil {
  53. return false, nil
  54. }
  55. reply, ok := resp.(string)
  56. if ok && reply == "OK" {
  57. return true, nil
  58. } else {
  59. logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
  60. return false, nil
  61. }
  62. }
  63. func (rl *RedisLock) Release() (bool, error) {
  64. resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
  65. if err != nil {
  66. return false, err
  67. }
  68. if reply, ok := resp.(int64); !ok {
  69. return false, nil
  70. } else {
  71. return reply == 1, nil
  72. }
  73. }
  74. func (rl *RedisLock) SetExpire(seconds int) {
  75. atomic.StoreUint32(&rl.seconds, uint32(seconds))
  76. }
  77. func randomStr(n int) string {
  78. b := make([]byte, n)
  79. for i := range b {
  80. b[i] = letters[rand.Intn(len(letters))]
  81. }
  82. return string(b)
  83. }