goRedisDLM/redisLock/redis.go

113 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}
}