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 }