redislock.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  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. }
  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. return rl.AcquireCtx(context.Background())
  49. }
  50. // AcquireCtx acquires the lock with the given ctx.
  51. func (rl *RedisLock) AcquireCtx(ctx context.Context) (bool, error) {
  52. seconds := atomic.LoadUint32(&rl.seconds)
  53. resp, err := rl.store.EvalCtx(ctx, lockCommand, []string{rl.key}, []string{
  54. rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
  55. })
  56. if err == red.Nil {
  57. return false, nil
  58. } else if err != nil {
  59. logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
  60. return false, err
  61. } else if resp == nil {
  62. return false, nil
  63. }
  64. reply, ok := resp.(string)
  65. if ok && reply == "OK" {
  66. return true, nil
  67. }
  68. logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
  69. return false, nil
  70. }
  71. // Release releases the lock.
  72. func (rl *RedisLock) Release() (bool, error) {
  73. return rl.ReleaseCtx(context.Background())
  74. }
  75. // ReleaseCtx releases the lock with the given ctx.
  76. func (rl *RedisLock) ReleaseCtx(ctx context.Context) (bool, error) {
  77. resp, err := rl.store.EvalCtx(ctx, delCommand, []string{rl.key}, []string{rl.id})
  78. if err != nil {
  79. return false, err
  80. }
  81. reply, ok := resp.(int64)
  82. if !ok {
  83. return false, nil
  84. }
  85. return reply == 1, nil
  86. }
  87. // SetExpire sets the expiration.
  88. func (rl *RedisLock) SetExpire(seconds int) {
  89. atomic.StoreUint32(&rl.seconds, uint32(seconds))
  90. }