github.com/weedge/lib@v0.0.0-20230424045628-a36dcc1d90e4/client/redis/dlock/locker.go (about)

     1  package dlock
     2  
     3  import (
     4  	"context"
     5  	"github.com/google/uuid"
     6  	"math"
     7  	"math/rand"
     8  	"time"
     9  
    10  	"github.com/weedge/lib/log"
    11  	"github.com/weedge/lib/runtimer"
    12  
    13  	"github.com/go-redis/redis/v8"
    14  )
    15  
    16  type RedisLocker struct {
    17  	rdb           *redis.Client
    18  	key           string
    19  	val           interface{}
    20  	retryTimes    int           // for block lock
    21  	retryInterval time.Duration // for block lock
    22  	expiration    time.Duration
    23  	tag           string // event tag
    24  	cancel        context.CancelFunc
    25  	isWatch       bool // is open watch to lease key ttl
    26  }
    27  
    28  type ILockerVal interface {
    29  	GetValue() interface{} // get rand value for del lock
    30  }
    31  
    32  type RandomVal struct{}
    33  
    34  func (v *RandomVal) GetValue() interface{} {
    35  	rand.Seed(time.Now().UnixNano())
    36  	return rand.Int()
    37  }
    38  
    39  type UUIDVal struct{}
    40  
    41  func (v *UUIDVal) GetValue() interface{} {
    42  	return uuid.NewString()
    43  }
    44  
    45  // New get a RedisLocker instance
    46  func New(rdb *redis.Client, key, tag string, val ILockerVal, retryTimes int, retryInterval, expiration time.Duration, isWatch bool) *RedisLocker {
    47  	return &RedisLocker{
    48  		rdb:           rdb,
    49  		key:           key,
    50  		val:           val.GetValue(),
    51  		retryTimes:    retryTimes,
    52  		retryInterval: retryInterval,
    53  		expiration:    expiration,
    54  		tag:           tag,
    55  		cancel:        nil,
    56  		isWatch:       isWatch,
    57  	}
    58  }
    59  
    60  // TryLock unblock try lock
    61  func (m *RedisLocker) TryLock(ctx context.Context) (err error, isGainLock bool) {
    62  	set, err := m.rdb.SetNX(ctx, m.key, m.val, m.expiration).Result()
    63  	if err != nil {
    64  		log.Errorf("err:%s", err.Error())
    65  		return
    66  	}
    67  
    68  	// retry lock
    69  	if set == false {
    70  		log.Infof("%s %s try lock fail", m.key, m.tag)
    71  		return
    72  	}
    73  
    74  	log.Infof("%s %s try lock ok", m.key, m.tag)
    75  
    76  	if m.isWatch {
    77  		var watchCtx context.Context
    78  		watchCtx, m.cancel = context.WithCancel(context.Background())
    79  		runtimer.GoSafely(nil, false, func() {
    80  			m.watch(watchCtx)
    81  		}, nil, nil)
    82  	}
    83  
    84  	return nil, set
    85  }
    86  
    87  // Lock block Lock util retryTimes per retryInterval
    88  func (m *RedisLocker) Lock(ctx context.Context) (err error, isGainLock bool) {
    89  	set, err := m.rdb.SetNX(ctx, m.key, m.val, m.expiration).Result()
    90  	if err != nil {
    91  		log.Errorf("err:%s", err.Error())
    92  		return
    93  	}
    94  
    95  	if set == false {
    96  		err, isGainLock = m.retryLock(ctx)
    97  		if err != nil {
    98  			return
    99  		}
   100  
   101  		if isGainLock == false {
   102  			log.Infof("%s %s lock fail", m.key, m.tag)
   103  			return
   104  		}
   105  	}
   106  	log.Infof("%s %s lock ok", m.key, m.tag)
   107  
   108  	if m.isWatch {
   109  		var watchCtx context.Context
   110  		watchCtx, m.cancel = context.WithCancel(context.Background())
   111  		runtimer.GoSafely(nil, false, func() {
   112  			m.watch(watchCtx)
   113  		}, nil, nil)
   114  	}
   115  
   116  	return nil, true
   117  }
   118  
   119  // retry lock util retry times by retry interval or gain lock return
   120  func (m *RedisLocker) retryLock(ctx context.Context) (err error, isGainLock bool) {
   121  	i := 1
   122  	var set bool
   123  	for {
   124  		if i > m.retryTimes && m.retryTimes > 0 {
   125  			break
   126  		}
   127  
   128  		log.Infof("%s %s retry lock cn %d", m.key, m.tag, i)
   129  		i++
   130  
   131  		if i == math.MaxInt32 {
   132  			i = 1
   133  		}
   134  
   135  		set, err = m.rdb.SetNX(ctx, m.key, m.val, m.expiration).Result()
   136  		if err != nil {
   137  			return
   138  		}
   139  		if set == true {
   140  			isGainLock = set
   141  			return
   142  		}
   143  
   144  		time.Sleep(m.retryInterval)
   145  	}
   146  
   147  	return
   148  }
   149  
   150  // watch to lease key expiration; or ticker to lease
   151  func (m *RedisLocker) watch(ctx context.Context) {
   152  	log.Infof("%s %s watching", m.key, m.tag)
   153  	for {
   154  		select {
   155  		case <-ctx.Done():
   156  			log.Infof("%s %s task done, close watch", m.key, m.tag)
   157  			return
   158  		default:
   159  			// lease todo lua get ttl to add expire
   160  			m.rdb.PExpire(ctx, m.key, m.expiration)
   161  			time.Sleep(m.expiration / 2)
   162  		}
   163  	}
   164  }
   165  
   166  // UnLock unlock ok return true or false by lua script for atomic cmd(get->del)
   167  func (m *RedisLocker) UnLock(ctx context.Context) (isDel bool) {
   168  	lua := `
   169  if redis.call('GET', KEYS[1]) == ARGV[1] then
   170  	return redis.call('DEL', KEYS[1])
   171  else
   172  	return 0
   173  end
   174  `
   175  	scriptKeys := []string{m.key}
   176  
   177  	val, err := m.rdb.Eval(ctx, lua, scriptKeys, m.val).Result()
   178  	if err != nil {
   179  		log.Errorf("rdb.Eval error:%s", err.Error())
   180  		return
   181  	}
   182  
   183  	if val == int64(1) {
   184  		if m.cancel != nil {
   185  			m.cancel()
   186  		}
   187  		log.Infof("%s %s unlock ok", m.key, m.tag)
   188  		isDel = true
   189  	}
   190  
   191  	return
   192  }