package redisLock import ( "context" "log" "time" "github.com/go-redis/redis/v8" ) type redisCommInfo struct { key string value string ttl int } type RedisLock struct { client *redis.Client ctx context.Context lockKeepAliveCh chan struct{} lockKeepDoneCh chan struct{} kvt redisCommInfo } func CreateRedisLock(c *redis.Client, ctx context.Context, k, v string, ttl int) *RedisLock { return &RedisLock{c, ctx, make(chan struct{}, 1), make(chan struct{}), redisCommInfo{k, v, ttl}} } func (r *RedisLock) Lock() { //for r.client.SetNX(r.ctx, r.kvt.key, r.kvt.value, time.Duration(r.kvt.ttl)*time.Second).Val() == false { // time.Sleep(time.Millisecond * 20) //} script := `if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('pexpire',KEYS[1],ARGV[2]) return 1 else return 0 end` for r.client.Eval(r.ctx, script, []string{r.kvt.key}, r.kvt.value, r.kvt.ttl).Val().(int64) == 0 { time.Sleep(time.Millisecond * 20) } select { case r.lockKeepAliveCh <- struct{}{}: go r.autoReNewExpire() default: log.Printf("start renew expore err: %s,%s,%d\n", r.kvt.key, r.kvt.value, r.kvt.ttl) } log.Printf("lock ok: %s,%s,%d\n", r.kvt.key, r.kvt.value, r.kvt.ttl) } func (r *RedisLock) Unlock() { //if r.client.Get(r.ctx, r.kvt.key).Val() == r.kvt.value { // r.client.Del(r.ctx, r.kvt.key) //} //script := "if (redis.call('get',KEYS[1]) == ARGV[1]) then return redis.call('del',KEYS[1]) else return 0 end" //r.client.Eval(r.ctx, script, []string{r.kvt.key}, r.kvt.value) script := `if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then return nil elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 then return redis.call('del',KEYS[1]) else return 0 end` r.lockKeepDoneCh <- struct{}{} //这里一定要放到if外面,并且进来就要退掉定时线程,开始死锁是因为续期失败 //导致续期协程return掉,再往lockKeepDoneCh写数据时就会死锁。而且不能只在判断解锁成功时给该管道发送数据。如果解锁失败不掉用写数据 //会导致续期协程永不退出。ttl时间不能太短,太短可能会导致续期还没有完成key过期。30ms会有问题。网络环境差该值还要继续增大。 //for len(r.lockKeepAliveCh) > 0 { // //<-r.lockKeepAliveCh //} val := r.client.Eval(r.ctx, script, []string{r.kvt.key}, r.kvt.value).Val() if i, ok := val.(int64); ok && i == 1 { log.Printf("unlock ok: %s,%s,%d\n", r.kvt.key, r.kvt.value, r.kvt.ttl) } else { log.Printf("unlock err: %s,%s,%d\n", r.kvt.key, r.kvt.value, r.kvt.ttl) } } func (r *RedisLock) autoReNewExpire() { defer func() { <-r.lockKeepAliveCh }() log.Printf("auto renew start: %s,%s,%d\n", r.kvt.key, r.kvt.value, r.kvt.ttl) tc := time.NewTicker(time.Duration(r.kvt.ttl) * time.Millisecond / 5) defer tc.Stop() for { select { case <-tc.C: script := `if redis.call('hexists',KEYS[1],ARGV[1]) == 1 then return redis.call('pexpire',KEYS[1],ARGV[2]) else return 0 end` val := r.client.Eval(r.ctx, script, []string{r.kvt.key}, r.kvt.value, r.kvt.ttl).Val() if i, ok := val.(int64); !ok || i == 0 { log.Printf("auto renew err: %s,%s,%d\n", r.kvt.key, r.kvt.value, r.kvt.ttl) //return } case <-r.lockKeepDoneCh: log.Printf("auto renew down: %s,%s,%d\n", r.kvt.key, r.kvt.value, r.kvt.ttl) return } } }