Documentation
¶
Overview ¶
Example ¶
// Connect to redis.
client := redis.NewClient(&redis.Options{
Network: "tcp",
Addr: "127.0.0.1:6379",
})
defer client.Close()
// Create a new lock client.
locker := redislock.New(client)
ctx := context.Background()
// Try to obtain lock.
lock, err := locker.Obtain(ctx, "my-key", 100*time.Millisecond, nil)
if err == redislock.ErrNotObtained {
fmt.Println("Could not obtain lock!")
return
} else if err != nil {
log.Fatalln(err)
return
}
// Don't forget to defer Release.
defer lock.Release(ctx)
fmt.Println("I have a lock!")
// Sleep and check the remaining TTL.
time.Sleep(50 * time.Millisecond)
if ttl, err := lock.TTL(ctx); err != nil {
log.Fatalln(err)
} else if ttl > 0 {
fmt.Println("Yay, I still have my lock!")
}
// Extend my lock.
if err := lock.Refresh(ctx, 100*time.Millisecond, nil); err != nil {
log.Fatalln(err)
}
// Sleep a little longer, then check.
time.Sleep(100 * time.Millisecond)
if ttl, err := lock.TTL(ctx); err != nil {
log.Fatalln(err)
} else if ttl == 0 {
fmt.Println("Now, my lock has expired!")
}
Output: I have a lock! Yay, I still have my lock! Now, my lock has expired!
Index ¶
- Variables
- type Client
- type Lock
- func (l *Lock) Key() string
- func (l *Lock) Keys() []string
- func (l *Lock) Metadata() string
- func (l *Lock) Refresh(ctx context.Context, ttl time.Duration, opt *Options) error
- func (l *Lock) Release(ctx context.Context) error
- func (l *Lock) TTL(ctx context.Context) (time.Duration, error)
- func (l *Lock) Token() string
- type Options
- type RedisClient
- type RetryStrategy
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotObtained is returned when a lock cannot be obtained. ErrNotObtained = errors.New("redislock: not obtained") // ErrLockNotHeld is returned when trying to release an inactive lock. ErrLockNotHeld = errors.New("redislock: lock not held") )
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps a redis client.
func New ¶
func New(client RedisClient) *Client
New creates a new Client instance with a custom namespace.
func (*Client) Obtain ¶
func (c *Client) Obtain(ctx context.Context, key string, ttl time.Duration, opt *Options) (*Lock, error)
Obtain tries to obtain a new lock using a key with the given TTL. May return ErrNotObtained if not successful.
Example (CustomDeadline) ¶
client := redis.NewClient(&redis.Options{Network: "tcp", Addr: "127.0.0.1:6379"})
defer client.Close()
locker := redislock.New(client)
// Retry every 500ms, for up-to a minute
backoff := redislock.LinearBackoff(500 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
defer cancel()
// Obtain lock with retry + custom deadline
lock, err := locker.Obtain(ctx, "my-key", time.Second, &redislock.Options{
RetryStrategy: backoff,
})
if err == redislock.ErrNotObtained {
fmt.Println("Could not obtain lock!")
} else if err != nil {
log.Fatalln(err)
}
defer lock.Release(context.Background())
fmt.Println("I have a lock!")
Example (Retry) ¶
client := redis.NewClient(&redis.Options{Network: "tcp", Addr: "127.0.0.1:6379"})
defer client.Close()
locker := redislock.New(client)
ctx := context.Background()
// Retry every 100ms, for up-to 3x
backoff := redislock.LimitRetry(redislock.LinearBackoff(100*time.Millisecond), 3)
// Obtain lock with retry
lock, err := locker.Obtain(ctx, "my-key", time.Second, &redislock.Options{
RetryStrategy: backoff,
})
if err == redislock.ErrNotObtained {
fmt.Println("Could not obtain lock!")
} else if err != nil {
log.Fatalln(err)
}
defer lock.Release(ctx)
fmt.Println("I have a lock!")
func (*Client) ObtainMulti ¶ added in v0.10.0
func (c *Client) ObtainMulti(ctx context.Context, keys []string, ttl time.Duration, opt *Options) (*Lock, error)
ObtainMulti tries to obtain new locks using keys with the given TTL. If any of requested key are already locked, no additional keys are locked and ErrNotObtained is returned. May return ErrNotObtained if not successful.
type Lock ¶
type Lock struct {
*Client
// contains filtered or unexported fields
}
Lock represents an obtained, distributed lock.
func Obtain ¶
func Obtain(ctx context.Context, client RedisClient, key string, ttl time.Duration, opt *Options) (*Lock, error)
Obtain is a short-cut for New(...).Obtain(...).
func ObtainMulti ¶ added in v0.10.0
func ObtainMulti(ctx context.Context, client RedisClient, keys []string, ttl time.Duration, opt *Options) (*Lock, error)
ObtainMulti is a short-cut for New(...).ObtainMulti(...).
func (*Lock) Key ¶
Key returns the redis key used by the lock. If the lock hold multiple key, only the first is returned.
func (*Lock) Refresh ¶
Refresh extends the lock with a new TTL. May return ErrNotObtained if refresh is unsuccessful.
Example (Watchdog) ¶
client := redis.NewClient(&redis.Options{Network: "tcp", Addr: "127.0.0.1:6379"})
defer client.Close()
locker := redislock.New(client)
ctx := context.Background()
// Obtain a lock with a 30s TTL.
const ttl = 30 * time.Second
lock, err := locker.Obtain(ctx, "my-key", ttl, nil)
if err != nil {
log.Fatalln(err)
}
defer lock.Release(context.Background())
// Start a watchdog that refreshes the lock every ttl/3. The work context
// is cancelled if a refresh fails so the protected work can abort.
workCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
t := time.NewTicker(ttl / 3)
defer t.Stop()
for {
select {
case <-workCtx.Done():
return
case <-t.C:
if err := lock.Refresh(workCtx, ttl, nil); err != nil {
log.Printf("lock refresh failed: %v", err)
cancel()
return
}
}
}
}()
// ... do work using workCtx ...
fmt.Println("I have a lock!")
type Options ¶
type Options struct {
// RetryStrategy allows to customise the lock retry strategy.
// Default: do not retry
RetryStrategy RetryStrategy
// Metadata string.
Metadata string
// Token is a unique value that is used to identify the lock. By default, a random tokens are generated. Use this
// option to provide a custom token instead.
Token string
}
Options describe the options for the lock
type RedisClient ¶
RedisClient is a minimal client interface.
type RetryStrategy ¶ added in v0.4.0
type RetryStrategy interface {
// NextBackoff returns the next backoff duration.
NextBackoff() time.Duration
}
RetryStrategy allows to customise the lock retry strategy.
func ExponentialBackoff ¶ added in v0.4.0
func ExponentialBackoff(min, max time.Duration) RetryStrategy
ExponentialBackoff returns a strategy that doubles the wait between retries, computed as 2**(n+1) milliseconds where n is the attempt count (so the first wait is 4ms, the second 8ms, the third 16ms, and so on, capped at 2**26 ms once n reaches 25).
The min and max arguments clamp the returned duration:
- if the computed value is below min, min is returned; pass 0 to disable the lower bound. The first few attempts produce sub-16ms waits, so callers that want a sensible floor should pass min >= 16ms.
- if max is non-zero and the computed value exceeds max, max is returned. Passing 0 means no upper bound, which lets the wait grow into hours after enough attempts; combine with LimitRetry or a ctx deadline if that is undesirable.
func LimitRetry ¶ added in v0.4.0
func LimitRetry(s RetryStrategy, max int) RetryStrategy
LimitRetry limits the number of retries to max attempts.
func LinearBackoff ¶ added in v0.4.0
func LinearBackoff(backoff time.Duration) RetryStrategy
LinearBackoff allows retries regularly with customized intervals
