lockx.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package redisx
  2. import (
  3. "context"
  4. "errors"
  5. "time"
  6. "github.com/google/uuid"
  7. "github.com/redis/go-redis/v9"
  8. )
  9. // 分布式锁
  10. type RedisLock struct {
  11. key string
  12. token string
  13. ttl time.Duration
  14. }
  15. // TryLock 非阻塞尝试加锁
  16. func TryLock(ctx context.Context, key string, ttl time.Duration) (*RedisLock, bool, error) {
  17. token := uuid.NewString()
  18. ok, err := getClient().SetNX(ctx, key, token, ttl).Result()
  19. if err != nil {
  20. return nil, false, err
  21. }
  22. if !ok {
  23. return nil, false, nil
  24. }
  25. return &RedisLock{key: key, token: token, ttl: ttl}, true, nil
  26. }
  27. // Lock 阻塞加锁,可设置重试间隔和超时
  28. func Lock(ctx context.Context, key string, ttl, retryInterval, timeout time.Duration) (*RedisLock, error) {
  29. start := time.Now()
  30. for {
  31. lock, ok, err := TryLock(ctx, key, ttl)
  32. if err != nil {
  33. return nil, err
  34. }
  35. if ok {
  36. return lock, nil
  37. }
  38. if timeout > 0 && time.Since(start) >= timeout {
  39. return nil, errors.New("lock timeout")
  40. }
  41. select {
  42. case <-ctx.Done():
  43. return nil, ctx.Err()
  44. case <-time.After(retryInterval):
  45. }
  46. }
  47. }
  48. // Release 安全释放锁(Lua 脚本) - 通过key和token直接释放
  49. func LockRelease(ctx context.Context, key string, token string) error {
  50. const luaScript = `
  51. if redis.call("get", KEYS[1]) == ARGV[1] then
  52. return redis.call("del", KEYS[1])
  53. else
  54. return 0
  55. end
  56. `
  57. res, err := redis.NewScript(luaScript).Run(ctx, getClient(), []string{key}, token).Int()
  58. if err != nil {
  59. return err
  60. }
  61. if res == 0 {
  62. return errors.New("unlock failed: not owner")
  63. }
  64. return nil
  65. }
  66. // Release 安全释放锁(Lua 脚本)
  67. func (l *RedisLock) Release(ctx context.Context) error {
  68. const luaScript = `
  69. if redis.call("get", KEYS[1]) == ARGV[1] then
  70. return redis.call("del", KEYS[1])
  71. else
  72. return 0
  73. end
  74. `
  75. res, err := redis.NewScript(luaScript).Run(ctx, getClient(), []string{l.key}, l.token).Int()
  76. if err != nil {
  77. return err
  78. }
  79. if res == 0 {
  80. return errors.New("unlock failed: not owner")
  81. }
  82. return nil
  83. }
  84. // SafeRelease 带日志保护版本
  85. func (l *RedisLock) SafeRelease(ctx context.Context) {
  86. _ = l.Release(ctx)
  87. }
  88. func (l *RedisLock) GetKey() string {
  89. return l.key
  90. }
  91. func (l *RedisLock) GetToken() string {
  92. return l.token
  93. }