113 lines
3.4 KiB
Go
113 lines
3.4 KiB
Go
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
|
||
}
|
||
}
|
||
}
|