redislock.go 2.4 KB

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