rueidislock

package
v1.0.51 Latest Latest
Warning

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

Go to latest
Published: Dec 2, 2024 License: Apache-2.0 Imports: 10 Imported by: 13

README

rueidislock

A Redis Distributed Lock Pattern enhanced by Client Side Caching.

package main

import (
	"context"
	"github.com/redis/rueidis"
	"github.com/redis/rueidis/rueidislock"
)

func main() {
	locker, err := rueidislock.NewLocker(rueidislock.LockerOption{
		ClientOption:   rueidis.ClientOption{InitAddress: []string{"localhost:6379"}},
		KeyMajority:    1,    // Use KeyMajority=1 if you have only one Redis instance. Also make sure that all your `Locker`s share the same KeyMajority.
		NoLoopTracking: true, // Enable this to have better performance if all your Redis are >= 7.0.5.
	})
	if err != nil {
		panic(err)
	}
	defer locker.Close()

	// acquire the lock "my_lock"
	ctx, cancel, err := locker.WithContext(context.Background(), "my_lock")
	if err != nil {
		panic(err)
	}

	// "my_lock" is acquired. use the ctx as normal.
	doSomething(ctx)

	// invoke cancel() to release the lock.
	cancel()
}

Features backed by the Redis Client Side Caching

  • The returned ctx will be canceled automatically and immediately once the KeyMajority is not held anymore, for example:
    • Redis are down.
    • Acquired keys has been deleted by other programs or administrators.
  • The waiting Locker.WithContext will try acquiring the lock again automatically and immediately once it has been released by someone or by another program.

How it works

When the locker.WithContext is invoked, it will:

  1. Try acquiring 3 keys (given that the default KeyMajority is 2), which are rueidislock:0:my_lock, rueidislock:1:my_lock and rueidislock:2:my_lock, by sending redis command SET NX PXAT or SET NX PX if FallbackSETPX is set.
  2. If the KeyMajority is satisfied within the KeyValidity duration, the invocation is successful and a ctx is returned as the lock.
  3. If the invocation is not successful, it will wait for client-side caching notifications to retry again.
  4. If the invocation is successful, the Locker will extend the ctx validity periodically and also watch client-side caching notifications for canceling the ctx if the KeyMajority is not held anymore.
Disable Client Side Caching

Some Redis provider doesn't support client-side caching, ex. Google Cloud Memorystore. You can disable client-side caching by setting ClientOption.DisableCache to true. Please note that when the client-side caching is disabled, rueidislock will only try to re-acquire locks for every ExtendInterval.

Benchmark

▶ go test -bench=. -benchmem -run=.
goos: darwin
goarch: arm64
pkg: rueidis-benchmark/locker
Benchmark/rueidislock-10         	   20103	     57842 ns/op	    1849 B/op	      29 allocs/op
Benchmark/redislock-10           	   13209	     86285 ns/op	    8083 B/op	     225 allocs/op
PASS
ok  	rueidis-benchmark/locker	3.782s
package locker

import (
	"context"
	"testing"
	"time"

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

func Benchmark(b *testing.B) {
	b.Run("rueidislock", func(b *testing.B) {
		l, _ := rueidislock.NewLocker(rueidislock.LockerOption{
			ClientOption:   rueidis.ClientOption{InitAddress: []string{"127.0.0.1:6379"}},
			KeyMajority:    1,
			NoLoopTracking: true,
		})
		defer l.Close()
		b.ResetTimer()
		b.RunParallel(func(pb *testing.PB) {
			for pb.Next() {
				_, cancel, err := l.WithContext(context.Background(), "mylock")
				if err != nil {
					panic(err)
				}
				cancel()
			}
		})
		b.StopTimer()
	})
	b.Run("redislock", func(b *testing.B) {
		client := redis.NewUniversalClient(&redis.UniversalOptions{Addrs: []string{"127.0.0.1:6379"}})
		locker := redislock.New(client)
		defer client.Close()
		b.ResetTimer()
		b.RunParallel(func(pb *testing.PB) {
			for pb.Next() {
			retry:
				lock, err := locker.Obtain(context.Background(), "mylock", time.Minute, nil)
				if err == redislock.ErrNotObtained {
					goto retry
				} else if err != nil {
					panic(err)
				}
				lock.Release(context.Background())
			}
		})
		b.StopTimer()
	})
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrLockerClosed = errors.New("locker closed")

ErrLockerClosed is returned from the Locker.WithContext when the Locker is closed

View Source
var ErrNotLocked = errors.New("not locked")

ErrNotLocked is returned from the Locker.TryWithContext when it fails

Functions

This section is empty.

Types

type Locker

type Locker interface {
	// WithContext acquires a distributed redis lock by name by waiting for it. It may return ErrLockerClosed.
	WithContext(ctx context.Context, name string) (context.Context, context.CancelFunc, error)
	// TryWithContext tries to acquire a distributed redis lock by name without waiting. It may return ErrNotLocked.
	TryWithContext(ctx context.Context, name string) (context.Context, context.CancelFunc, error)
	// ForceWithContext takes over a distributed redis lock by canceling the original holder. It may return ErrNotLocked.
	ForceWithContext(ctx context.Context, name string) (context.Context, context.CancelFunc, error)
	// Client exports the underlying rueidis.Client
	Client() rueidis.Client
	// Close closes the underlying rueidis.Client
	Close()
}

Locker is the interface of rueidislock

func NewLocker

func NewLocker(option LockerOption) (Locker, error)

NewLocker creates the distributed Locker backed by redis client side caching

type LockerOption

type LockerOption struct {
	// ClientBuilder can be used to modify rueidis.Client used by Locker
	ClientBuilder func(option rueidis.ClientOption) (rueidis.Client, error)
	// KeyPrefix is the prefix of redis key for locks. Default value is "rueidislock".
	KeyPrefix string
	// ClientOption is passed to rueidis.NewClient or LockerOption.ClientBuilder to build a rueidis.Client
	ClientOption rueidis.ClientOption
	// KeyValidity is the validity duration of locks and will be extended periodically by the ExtendInterval. Default value is 5s.
	KeyValidity time.Duration
	// ExtendInterval is the interval to extend KeyValidity. Default value is 1s.
	ExtendInterval time.Duration
	// TryNextAfter is the timeout duration before trying the next redis key for locks. Default value is 20ms.
	TryNextAfter time.Duration
	// KeyMajority is at least how many redis keys in a total of KeyMajority*2-1 should be acquired to be a valid lock.
	// Default value is 2.
	KeyMajority int32
	// NoLoopTracking will use NOLOOP in the CLIENT TRACKING command to avoid unnecessary notifications and thus have better performance.
	// This can only be enabled if all your redis nodes >= 7.0.5. (https://github.com/redis/redis/pull/11052)
	NoLoopTracking bool
	// Use SET PX instead of SET PXAT when acquiring locks to be compatible with Redis < 6.2
	FallbackSETPX bool
}

LockerOption should be passed to NewLocker to construct a Locker

Jump to

Keyboard shortcuts

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