lock

package module
v0.0.0-...-19974b0 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 5 Imported by: 0

README

go-redis-lock

基于 Redis 的分布式锁包装层,基于 bsm/redislock。

限制与注意事项

Watchdog(自动续期)

本库不提供 watchdog 自动续期机制。当临界区执行时间超过锁的 TTL 时,锁会自动过期,互斥性可能被破坏。

如需延长锁的持有时间,请在临界区内手动调用 Lock.Refresh(ctx, ttl) 续期。

Re-entrancy(重入)

本库不支持可重入。同一持有者无法多次获取同一把锁。如需重入语义,请在业务层自行实现。

Fencing Token

本库使用随机 token 防止锁被误释放,但不提供 fencing token。锁过期后,无法阻止下游系统被并发双写。

对强一致性要求高的场景,建议使用 etcd 或 Zookeeper 等基于共识算法的分布式锁。

TODO

  • Watchdog 自动续期:提供 ObtainWithWatchdog 变体,后台 goroutine 自动续期,避免临界区超时导致锁过期
  • Re-entrancy 支持:支持同一持有者重入,需记录持有者标识和重入计数
  • Fencing Token:提供单调递增的 fencing token,防止锁过期后下游系统被并发双写

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrEmptyKey = errors.New("key must not be empty")
View Source
var ErrInvalidBackoff = errors.New("backoff must be positive")
View Source
var ErrInvalidMaxRetries = errors.New("maxRetries must not be negative")
View Source
var ErrInvalidTTL = errors.New("ttl must be positive")
View Source
var ErrInvalidTimeout = errors.New("timeout must be positive")
View Source
var ErrLockExpired = errors.New("lock has expired")
View Source
var ErrLockNotHeld = errors.New("lock not held")
View Source
var ErrLockNotInitialized = errors.New("lock not initialized")

Functions

This section is empty.

Types

type Lock

type Lock struct {
	// contains filtered or unexported fields
}

func (*Lock) Refresh

func (l *Lock) Refresh(ctx context.Context, ttl time.Duration) (ok bool, err error)

Refresh extends the lock with a new TTL. ttl is the lock's new time-to-live after refresh, must be positive. Returns (true, nil) on success, (false, nil) if the lock is no longer held (expired or released), and an error only on underlying Redis failures. Returns ErrInvalidTTL if ttl <= 0. Returns ErrLockNotInitialized if the Lock was not initialized (programming error).

func (*Lock) Release

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

Release releases the lock manually. Returns ErrLockNotHeld if the lock is no longer held (expired or released).

func (*Lock) TTL

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

TTL returns the remaining time to live of the lock. Returns (ttl, nil) with ttl > 0 if the lock is held. Returns (0, ErrLockExpired) if the lock is no longer held (expired, released, or held by someone else). Returns ErrLockNotInitialized if the Lock was not initialized (programming error).

type RedisLocker

type RedisLocker struct {
	// contains filtered or unexported fields
}

RedisLocker redis locker

func NewRedisLocker

func NewRedisLocker(client redislock.RedisClient) (*RedisLocker, error)

NewRedisLocker creates a RedisLocker with the given redis client. Returns an error if client is nil.

func (*RedisLocker) ObtainWithRetry

func (r *RedisLocker) ObtainWithRetry(ctx context.Context, key string, ttl time.Duration, maxRetries int, backoff time.Duration) (lock *Lock, ok bool, err error)

ObtainWithRetry attempts to obtain the lock with a limited number of retry attempts (count-based control). ttl is the lock's time-to-live after being obtained, NOT the acquire timeout. maxRetries is the number of retries (excluding the first attempt), so total attempts = maxRetries + 1. backoff is the duration between retry attempts, must be positive.

Returns (lock, true, nil) on success, (nil, false, nil) if the lock could not be obtained after all retries, and an error only on underlying Redis failures. Returns ErrInvalidTTL if ttl <= 0. Returns ErrInvalidMaxRetries if maxRetries < 0. Returns ErrInvalidBackoff if backoff <= 0. Returns ErrEmptyKey if key is empty.

example: Retry every 100ms, for up-to 3x ObtainWithRetry(context.Background(), "key", time.Second, 3, 100 * time.Millisecond)

Example
lock, ok, err := locker.ObtainWithRetry(
	context.Background(),
	"redis:key",
	time.Second*10,
	5,
	time.Second,
)
if err != nil {
	// redis operation error
	log.Fatal(err)
}

if !ok {
	// lock not obtained
	log.Fatal("lock not obtained")
}

// release lock
_ = lock.Release(context.Background())

func (*RedisLocker) ObtainWithTimeout

func (r *RedisLocker) ObtainWithTimeout(ctx context.Context, key string, ttl, timeout, backoff time.Duration) (lock *Lock, ok bool, err error)

ObtainWithTimeout attempts to obtain the lock with a total timeout, retrying every backoff until timeout expires (time-based control). ttl is the lock's time-to-live after being obtained, NOT the acquire timeout. timeout is the maximum total time to spend acquiring the lock. backoff is the duration between retry attempts.

Returns (lock, true, nil) on success, (nil, false, nil) if the lock could not be obtained within timeout, and an error only on underlying Redis failures. Returns ErrInvalidTTL if ttl <= 0. Returns ErrInvalidTimeout if timeout <= 0. Returns ErrInvalidBackoff if backoff <= 0. Returns ErrEmptyKey if key is empty.

example: ttl is 1s, Retry every 500ms, for up-to a minute ObtainWithTimeout(context.Background(), "key", time.Second, time.Minute, 500 * time.Millisecond)

Example
lock, ok, err := locker.ObtainWithTimeout(
	context.Background(),
	"redis:key",
	time.Second*5,
	time.Second,
	time.Millisecond*500,
)
if err != nil {
	// redis operation error
	log.Fatal(err)
}

if !ok {
	// lock not obtained
	log.Fatal("lock not obtained")
}

// release lock
_ = lock.Release(context.Background())

func (*RedisLocker) TryObtain

func (r *RedisLocker) TryObtain(ctx context.Context, key string, ttl time.Duration) (lock *Lock, ok bool, err error)

TryObtain attempts to obtain the lock a single time without retry. ttl is the lock's time-to-live after being obtained, NOT the acquire timeout. Returns (lock, true, nil) on success, (nil, false, nil) if the lock is already held by someone else, and an error only on underlying Redis failures. Returns ErrInvalidTTL if ttl <= 0. Returns ErrEmptyKey if key is empty.

Example
client := redis.NewClient(&redis.Options{
	Network:  "tcp",
	Addr:     "127.0.0.1:6379",
	Password: "",
	DB:       0,
})

var err error
locker, err = NewRedisLocker(client)
if err != nil {
	log.Fatal(err)
}

lock, ok, err := locker.TryObtain(context.Background(), "redis:key", time.Millisecond*500)
if err != nil {
	// redis operation error
	log.Fatal(err)
}

if !ok {
	// lock not obtained
	log.Fatal("lock not obtained")
}

// release lock
_ = lock.Release(context.Background())

// return remaining TTL
_, _ = lock.TTL(context.Background())

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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