redislock.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package redis
  2. import (
  3. "context"
  4. "math/rand"
  5. "strconv"
  6. "sync/atomic"
  7. "time"
  8. red "github.com/go-redis/redis/v8"
  9. "github.com/zeromicro/go-zero/core/logx"
  10. "github.com/zeromicro/go-zero/core/stringx"
  11. )
  12. const (
  13. randomLen = 16
  14. tolerance = 500 // milliseconds
  15. millisPerSecond = 1000
  16. lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
  17. redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
  18. return "OK"
  19. else
  20. return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
  21. end`
  22. delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
  23. return redis.call("DEL", KEYS[1])
  24. else
  25. return 0
  26. end`
  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. ctx context.Context
  35. }
  36. func init() {
  37. rand.Seed(time.Now().UnixNano())
  38. }
  39. // NewRedisLock returns a RedisLock.
  40. func NewRedisLock(store *Redis, key string) *RedisLock {
  41. return &RedisLock{
  42. store: store,
  43. key: key,
  44. id: stringx.Randn(randomLen),
  45. }
  46. }
  47. // Acquire acquires the lock.
  48. func (rl *RedisLock) Acquire() (bool, error) {
  49. rl.fillCtx()
  50. seconds := atomic.LoadUint32(&rl.seconds)
  51. resp, err := rl.store.EvalCtx(rl.ctx, lockCommand, []string{rl.key}, []string{
  52. rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
  53. })
  54. if err == red.Nil {
  55. return false, nil
  56. } else if err != nil {
  57. logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
  58. return false, err
  59. } else if resp == nil {
  60. return false, nil
  61. }
  62. reply, ok := resp.(string)
  63. if ok && reply == "OK" {
  64. return true, nil
  65. }
  66. logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
  67. return false, nil
  68. }
  69. // Release releases the lock.
  70. func (rl *RedisLock) Release() (bool, error) {
  71. rl.fillCtx()
  72. resp, err := rl.store.EvalCtx(rl.ctx, delCommand, []string{rl.key}, []string{rl.id})
  73. if err != nil {
  74. return false, err
  75. }
  76. reply, ok := resp.(int64)
  77. if !ok {
  78. return false, nil
  79. }
  80. return reply == 1, nil
  81. }
  82. // SetExpire sets the expiration.
  83. func (rl *RedisLock) SetExpire(seconds int) {
  84. atomic.StoreUint32(&rl.seconds, uint32(seconds))
  85. }
  86. // WithContext set context.
  87. func (rl *RedisLock) WithContext(ctx context.Context) {
  88. rl.ctx = ctx
  89. }
  90. func (rl *RedisLock) fillCtx() {
  91. if rl.ctx == nil {
  92. rl.ctx = context.Background()
  93. }
  94. }