mirror of
https://github.com/hanzokv/lock.git
synced 2026-10-30 15:07:01 +00:00
github.com/hanzokv/go/v9 v9.22.0 renamed its Go package from "redis" to "kv"; the import path is unchanged. A lock library is typed against the client it wraps -- the adapter's NewPool takes the client's UniversalClient and compares against the client's Nil sentinel -- so the qualifier change is load bearing, not cosmetic. Client-driven: - go.mod: github.com/hanzokv/go/v9 v9.21.1 -> v9.22.0 - the adapter for our client speaks kv.UniversalClient / kv.Nil, imported unaliased as in hanzokv/go's own sources Our own vocabulary follows: - redis/ -> kv/, package redis -> package kv. This is redsync's own Pool/Conn/Script abstraction over a KV backend, not anyone's product. - redis/goredis/v9 -> kv/kvgo (package kvgo). That adapter wraps hanzokv/go, not go-redis; the old name said the wrong thing. Its example moves with it: examples/goredis/v9 -> examples/kvgo. - import alias redsyncredis -> redsynckv - RedisError -> KVError (and its tests) - comments and README speak kv Deliberately NOT renamed, because they name foreign things: - the redigo, rueidis, valkeygo and go-redis v6/v7/v8 adapters, which target genuinely different clients, keep their names and their own "redis." qualifiers (redigo's redis.Conn, go-redis's redis.Nil) - the server-side Lua in scripts.go: redis.call is the store's scripting API. Rewriting it would change the script source and therefore the SHA1 hashes pinned in scripts.go, which TestScripts_Hash verifies against a live server. - upstream links (redis.io distlock post, redigo attribution) Import paths move from .../v4/redis/... to .../v4/kv/...; the module major stays v4 by policy. Verified with a local redis-server: go build ./..., go vet ./..., go test -count=1 ./... and go test -race -count=1 ./... all pass, TestScripts_Hash included. Co-authored-by: Hanzo Dev <[email protected]>
164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
package redsync
|
|
|
|
import (
|
|
"math/rand"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/hanzokv/lock/v4/kv"
|
|
)
|
|
|
|
const (
|
|
minRetryDelayMilliSec = 50
|
|
maxRetryDelayMilliSec = 250
|
|
)
|
|
|
|
// Redsync provides a simple method for creating distributed mutexes using multiple KV connection pools.
|
|
type Redsync struct {
|
|
pools []kv.Pool
|
|
}
|
|
|
|
// New creates and returns a new Redsync instance from given KV connection pools.
|
|
func New(pools ...kv.Pool) *Redsync {
|
|
return &Redsync{
|
|
pools: pools,
|
|
}
|
|
}
|
|
|
|
// NewMutex returns a new distributed mutex with given name.
|
|
func (r *Redsync) NewMutex(name string, options ...Option) *Mutex {
|
|
m := &Mutex{
|
|
name: name,
|
|
expiry: 8 * time.Second,
|
|
tries: 32,
|
|
delayFunc: func(tries int) time.Duration {
|
|
return time.Duration(rand.Intn(maxRetryDelayMilliSec-minRetryDelayMilliSec)+minRetryDelayMilliSec) * time.Millisecond
|
|
},
|
|
genValueFunc: genValue,
|
|
driftFactor: 0.01,
|
|
timeoutFactor: 0.05,
|
|
quorum: len(r.pools)/2 + 1,
|
|
pools: r.pools,
|
|
}
|
|
for _, o := range options {
|
|
o.Apply(m)
|
|
}
|
|
if m.shuffle {
|
|
// Create a copy to avoid data races between concurrent locks.
|
|
m.pools = slices.Clone(m.pools)
|
|
randomPools(m.pools)
|
|
}
|
|
return m
|
|
}
|
|
|
|
// An Option configures a mutex.
|
|
type Option interface {
|
|
Apply(*Mutex)
|
|
}
|
|
|
|
// OptionFunc is a function that configures a mutex.
|
|
type OptionFunc func(*Mutex)
|
|
|
|
// Apply calls f(mutex)
|
|
func (f OptionFunc) Apply(mutex *Mutex) {
|
|
f(mutex)
|
|
}
|
|
|
|
// WithExpiry can be used to set the expiry of a mutex to the given value.
|
|
// The default is 8s.
|
|
func WithExpiry(expiry time.Duration) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.expiry = expiry
|
|
})
|
|
}
|
|
|
|
// WithTries can be used to set the number of times lock acquire is attempted.
|
|
// The default value is 32.
|
|
func WithTries(tries int) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.tries = tries
|
|
})
|
|
}
|
|
|
|
// WithRetryDelay can be used to set the amount of time to wait between retries.
|
|
// The default value is rand(50ms, 250ms).
|
|
func WithRetryDelay(delay time.Duration) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.delayFunc = func(tries int) time.Duration {
|
|
return delay
|
|
}
|
|
})
|
|
}
|
|
|
|
// WithSetNXOnExtend improves extending logic to extend the key if exist
|
|
// and if not, tries to set a new key in the KV store
|
|
// Useful if your KV nodes restart often and you want to reduce the chances of losing the lock
|
|
// Read this MR for more info: https://github.com/go-redsync/redsync/pull/149
|
|
func WithSetNXOnExtend() Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.setNXOnExtend = true
|
|
})
|
|
}
|
|
|
|
// WithRetryDelayFunc can be used to override default delay behavior.
|
|
func WithRetryDelayFunc(delayFunc DelayFunc) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.delayFunc = delayFunc
|
|
})
|
|
}
|
|
|
|
// WithDriftFactor can be used to set the clock drift factor.
|
|
// The default value is 0.01.
|
|
func WithDriftFactor(factor float64) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.driftFactor = factor
|
|
})
|
|
}
|
|
|
|
// WithTimeoutFactor can be used to set the timeout factor.
|
|
// The default value is 0.05.
|
|
func WithTimeoutFactor(factor float64) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.timeoutFactor = factor
|
|
})
|
|
}
|
|
|
|
// WithGenValueFunc can be used to set the custom value generator.
|
|
func WithGenValueFunc(genValueFunc func() (string, error)) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.genValueFunc = genValueFunc
|
|
})
|
|
}
|
|
|
|
// WithValue can be used to assign the random value without having to call lock.
|
|
// This allows the ownership of a lock to be "transferred" and allows the lock to be unlocked from elsewhere.
|
|
func WithValue(v string) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.value = v
|
|
})
|
|
}
|
|
|
|
// WithFailFast can be used to quickly acquire and release the lock.
|
|
// When some KV servers are blocking, we do not need to wait for responses from all the KV servers response.
|
|
// As long as the quorum is met, we can assume the lock is acquired. The effect of this parameter is to achieve low
|
|
// latency, avoid KV blocking causing Lock/Unlock to not return for a long time.
|
|
func WithFailFast(b bool) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.failFast = b
|
|
})
|
|
}
|
|
|
|
// WithShufflePools can be used to shuffle KV pools to reduce centralized access in concurrent scenarios.
|
|
func WithShufflePools(b bool) Option {
|
|
return OptionFunc(func(m *Mutex) {
|
|
m.shuffle = b
|
|
})
|
|
}
|
|
|
|
// randomPools shuffles KV pools.
|
|
func randomPools(pools []kv.Pool) {
|
|
rand.Shuffle(len(pools), func(i, j int) {
|
|
pools[i], pools[j] = pools[j], pools[i]
|
|
})
|
|
}
|