redislock

package module
v0.9.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2023 License: Apache-2.0 Imports: 10 Imported by: 465

README

redislock

Test GoDoc License

Simplified distributed locking implementation using Redis. For more information, please see examples.

Examples

import (
  "context"
  "fmt"
  "log"
  "time"

  "github.com/bsm/redislock"
  "github.com/redis/go-redis/v9"
)

func main() {
	// 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!")
	} else if err != nil {
		log.Fatalln(err)
	}

	// 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!")
	}

}

Documentation

Full documentation is available on GoDoc

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!")
} else if err != nil {
	log.Fatalln(err)
}

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

Examples

Constants

This section is empty.

Variables

View Source
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!")
Output:

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!")
Output:

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 (*Lock) Key

func (l *Lock) Key() string

Key returns the redis key used by the lock.

func (*Lock) Metadata

func (l *Lock) Metadata() string

Metadata returns the metadata of the lock.

func (*Lock) Refresh

func (l *Lock) Refresh(ctx context.Context, ttl time.Duration, opt *Options) error

Refresh extends the lock with a new TTL. May return ErrNotObtained if refresh is unsuccessful.

func (*Lock) Release

func (l *Lock) Release(ctx context.Context) error

Release manually releases the lock. May return ErrLockNotHeld.

func (*Lock) TTL

func (l *Lock) TTL(ctx context.Context) (time.Duration, error)

TTL returns the remaining time-to-live. Returns 0 if the lock has expired.

func (*Lock) Token

func (l *Lock) Token() string

Token returns the token value set by the 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

type RedisClient interface {
	redis.Scripter
}

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 strategy is an optimization strategy with a retry time of 2**n milliseconds (n means number of times). You can set a minimum and maximum value, the recommended minimum value is not less than 16ms.

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

func NoRetry added in v0.4.0

func NoRetry() RetryStrategy

NoRetry acquire the lock only once.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL