go-redis读写锁实现参考 By ivan.L 发表于 2017-05-30 本文参考开源的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() } [参看]: Redis Eval 命令 redis分布式锁