go-redis读写锁实现参考
本文参考开源的redisson,简要的实现了一个go版本的redis读写锁。
1. go-redis读写锁
1.1 ReadWriteLock入口
参看如下代码实现(RReadWriteLock.go):
package goredisson
import (
"strings"
)
const(
lockWatchdogTimeout = 10 * 60 * 1000 //10min
)
type RedissonReadWriteLock struct{
name string
}
/*
* Description: Create readwrite lock
*
* Note: for support redis-cluster, please reference
* 1) https://redis.io/commands/cluster-keyslot
* 2) https://blog.csdn.net/xixingzhe2/article/details/86167859
*/
func GetReadWriteLock(name string)(* RedissonReadWriteLock){
rRWLock := new(RedissonReadWriteLock)
rRWLock.name = name
return rRWLock
}
/*
* Description: get a read lock
* id: the indicator
* (we could have many read locks in a 'rwlock', and most probably we can use an indicator to distinguwish them)
*/
func (lock* RedissonReadWriteLock)ReadLock(id string)(*RReadLock){
if id == ""{
id = "admin"
}
return createReadLock(lock.name, id)
}
/*
* Description: get a write lock
* id: the indicator
*/
func (lock *RedissonReadWriteLock)WriteLock(id string)(*RWriteLock){
if id == ""{
id = "admin"
}
return createWriteLock(lock.name, id)
}
func prefixName(prefix string, name string)(string){
if strings.Contains(name, "{"){
return prefix + ":" + name
}
return prefix + ":{" + name + "}"
}
func suffixName(name string, suffix string)(string){
if strings.Contains(name, "{"){
return name + ":" + suffix
}
return "{" + name + "}:" + suffix
}
1.2 读锁的实现
参看如下实现(RedissonReadLock.go):
package goredisson
import(
"strconv"
"errors"
"strings"
goredis "github.com/go-redis/redis"
)
type RReadLock struct{
name string
id string
}
func createReadLock(name string, id string)(*RReadLock){
rLock := new(RReadLock)
rLock.name = name
rLock.id = id
return rLock
}
func (rlock *RReadLock)getName()string{
return rlock.name
}
func (rlock *RReadLock)getIndicator()string{
return rlock.id
}
func (rlock *RReadLock)getReadWriteTimeoutNamePrefix()string{
return suffixName(rlock.name, rlock.id) + ":rwlock_timeout"
}
/*
* Description: try to get a lock
* leaseTime: the lock time (unit: milliseconds)
*/
func (rlock *RReadLock)doLock(leaseTime int64)(error){
if leaseTime == -1{
leaseTime = lockWatchdogTimeout
}
script := `
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
redis.call('hset', KEYS[1], 'mode', 'read');
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('set', KEYS[2] .. ':1', 1);
redis.call('pexpire', KEYS[2] .. ':1',ARGV[1]);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (mode == 'read') then
local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1);
local key = KEYS[2] .. ':' .. ind;
redis.call('set', key, 1);
redis.call('pexpire', key, ARGV[1]);
local remainTime = redis.call('pttl', KEYS[1]);
redis.call('pexpire', KEYS[1], math.max(remainTime, ARGV[1]));
return nil;
end;
return redis.call('pttl', KEYS[1]);
`
keys := []string{
rlock.getName(),
rlock.getReadWriteTimeoutNamePrefix(),
}
args := []string{
strconv.FormatInt(leaseTime, 10),
rlock.getIndicator(),
}
repl, err := Redis_handle.Eval(script, keys, args)
if err != nil{
return err
}
_, err = repl.StringValue()
return err
}
func (rlock *RReadLock)doUnlock()error{
timeoutNamePrefix := rlock.getReadWriteTimeoutNamePrefix()
keyPrefix := strings.Split(timeoutNamePrefix, ":"+rlock.getIndicator())[0]
/*
* Note: here not check the mode type, it seems not a problem
*/
script := `
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
return 1;
end;
local lockExists = redis.call('hexists', KEYS[1], ARGV[1]);
if (lockExists == 0) then
return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);
if (counter == 0) then
redis.call('hdel', KEYS[1], ARGV[1]);
end;
redis.call('del', KEYS[2] .. ':' .. (counter+1));
if (redis.call('hlen', KEYS[1]) > 1) then
local maxRemainTime = -3;
local keys = redis.call('hkeys', KEYS[1]);
for n, key in ipairs(keys) do
counter = tonumber(redis.call('hget', KEYS[1], key));
if type(counter) == 'number' then
for i=counter, 1, -1 do
local remainTime = redis.call('pttl', KEYS[3] .. ':' .. key .. ':rwlock_timeout:' .. i);
maxRemainTime = math.max(remainTime, maxRemainTime);
end;
end;
end;
if (maxRemainTime > 0) then
redis.call('pexpire', KEYS[1], maxRemainTime);
return 0;
end;
if (mode == 'write') then
return 0;
end;
end;
redis.call('del', KEYS[1]);
return 1;
`
keys := []string{
rlock.getName(),
rlock.getReadWriteTimeoutNamePrefix(),
keyPrefix,
}
args := []string{
rlock.getIndicator(),
}
repl, err := Redis_handle.Eval(script, keys, args)
if err != nil{
return err
}
if repl.Type == goredis.ErrorReply{
return errors.New(repl.Error)
}
if repl.Type == goredis.IntegerReply || repl.Type == goredis.BulkReply{
return nil
}
return errors.New("invalid reply type")
}
func (rlock *RReadLock)Lock()(error){
return rlock.doLock(-1)
}
func (rlock *RReadLock)Unlock()(error){
return rlock.doUnlock()
}
1.3 写锁的实现
参看如下实现(RedissonWriteLock.go):
package goredisson
import (
"strconv"
"errors"
goredis "github.com/go-redis/redis"
)
type RWriteLock struct{
name string
id string
}
func createWriteLock(name string, id string)(*RWriteLock){
wLock := new(RWriteLock)
wLock.name = name
wLock.id = id
return wLock
}
func (wlock *RWriteLock)getName()(string){
return wlock.name
}
func (wlock *RWriteLock)getIndicator()(string){
return wlock.id
}
func (wlock *RWriteLock)getLockName()(string){
return wlock.name + ":write"
}
func (wlock *RWriteLock)doLock(leaseTime int64)(error){
if leaseTime == -1{
leaseTime = lockWatchdogTimeout
}
script := `
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
redis.call('hset', KEYS[1], 'mode', 'write');
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
`
keys := []string{
wlock.getName(),
}
args := []string{
strconv.FormatInt(leaseTime, 10),
wlock.getIndicator(),
}
repl, err := util.Redis_handle.Eval(script, keys, args)
if err != nil{
return err
}
_, err = repl.StringValue()
return err
}
func (wlock *RWriteLock)doUnlock()(error){
script := `
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
return 1;
end;
if (mode == 'write') then
local lockExists = redis.call('hget', KEYS[1], ARGV[1]);
if (lockExists == 0) then
return nil;
else
redis.call('hdel', KEYS[1], ARGV[1]);
if (redis.call('hlen', KEYS[1]) == 1) then
redis.call('del', KEYS[1]);
end;
return 1;
end;
end;
return nil;
`
keys := []string{
wlock.getName(),
}
args := []string{
wlock.getIndicator(),
}
repl, err := Redis_handle.Eval(script, keys, args)
if err != nil{
return err
}
if repl.Type == goredis.ErrorReply{
return errors.New(repl.Error)
}
if repl.Type == goredis.IntegerReply{
return nil
}
return errors.New("the lock is busy")
}
func (wlock *RWriteLock)Lock()(error){
return wlock.doLock(-1)
}
func (wlock *RWriteLock)Unlock()(error){
return wlock.doUnlock()
}
[参看]: