A Redis-based distributed lock implementation in Go, designed to coordinate and arbitrate access to shared resources in distributed systems.
- Distributed Lock with Redis
- Lock acquisition with timeout
- Automatic lock renewal (watchdog)
- Lock reentrant support
- Safe lock release
- Configurable logging
go get github.com/huimingz/arbiter
import (
"context"
"time"
"github.com/redis/go-redis/v9"
"github.com/huimingz/arbiter"
)
func main() {
// Create Redis client
redisClient := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
defer redisClient.Close()
// Create Arbiter client
client := arbiter.NewClient(redisClient)
// Create a lock with options
lock := client.NewLock("my-lock",
arbiter.WithWaitTimeout(5*time.Second), // Wait up to 5s to acquire lock
arbiter.WithLeaseTime(30*time.Second), // Lock expires after 30s
arbiter.WithWatchDog(true), // Auto-renew lock
)
// Try to acquire the lock
ctx := context.Background()
if err := lock.Lock(ctx); err != nil {
// Handle lock acquisition failure
return
}
// Don't forget to unlock
defer lock.Unlock(ctx)
// Your critical section here
// ...
}
WithWaitTimeout(d time.Duration)
: Maximum time to wait for lock acquisitionWithLeaseTime(d time.Duration)
: Lock lease time (expiration)WithWatchDog(enable bool)
: Enable automatic lock renewalWithWatchDogTimeout(d time.Duration)
: Interval for watchdog renewal
Arbiter supports customizable logging through a simple interface:
type Logger interface {
Debug(ctx context.Context, format string, args ...interface{})
Info(ctx context.Context, format string, args ...interface{})
Warn(ctx context.Context, format string, args ...interface{})
Error(ctx context.Context, format string, args ...interface{})
}
You can provide your own logger implementation:
client := arbiter.NewClient(redisClient,
arbiter.WithLogger(myLogger),
)
The distributed lock is implemented using Redis hash structures and Lua scripts to ensure atomicity. The key features are:
-
Atomic Lock Acquisition
- Uses Lua script to check and set lock atomically
- Supports lock reentrance (same client can acquire lock multiple times)
- Sets lock expiration to prevent deadlocks
-
Safe Lock Release
- Only the lock owner can release the lock
- Uses Lua script to verify ownership before deletion
-
Automatic Lock Renewal
- Optional watchdog mechanism to prevent lock expiration
- Periodically refreshes lock lease time
- Stops renewal when lock is released or context is cancelled
- Key:
<lock-name>
- Type: Hash
- Fields:
owner
: Client identifier- TTL: Set using
PEXPIRE
-
Always Use Timeouts
lock := client.NewLock("my-lock", arbiter.WithWaitTimeout(5*time.Second), arbiter.WithLeaseTime(30*time.Second), )
-
Use Watchdog for Long Operations
lock := client.NewLock("my-lock", arbiter.WithWatchDog(true), arbiter.WithWatchDogTimeout(10*time.Second), )
-
Proper Error Handling
if err := lock.Lock(ctx); err != nil { switch err { case arbiter.ErrLockTimeout: // Handle timeout case context.DeadlineExceeded: // Handle context timeout default: // Handle other errors } }
-
Use defer for Unlocking
if err := lock.Lock(ctx); err != nil { return err } defer lock.Unlock(ctx)
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License