redislock.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  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. }
  59. logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
  60. return false, nil
  61. }
  62. func (rl *RedisLock) Release() (bool, error) {
  63. resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
  64. if err != nil {
  65. return false, err
  66. }
  67. reply, ok := resp.(int64)
  68. if !ok {
  69. return false, nil
  70. }
  71. return reply == 1, nil
  72. }
  73. func (rl *RedisLock) SetExpire(seconds int) {
  74. atomic.StoreUint32(&rl.seconds, uint32(seconds))
  75. }
  76. func randomStr(n int) string {
  77. b := make([]byte, n)
  78. for i := range b {
  79. b[i] = letters[rand.Intn(len(letters))]
  80. }
  81. return string(b)
  82. }