Simplified distributed locking implementation using Redis
GoDoc
分布式锁使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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!")
}
}
|
获取锁
-
生成一个Token, 作为标识符, 添加metadata信息, 辅助后期debug
-
设置ttl, 重试策略
策略实现:
https://pkg.go.dev/github.com/bsm/redislock#RetryStrategy
检查是否已经设置了最大超时时间, 如果没有设置, 默认使用ttl作为超时时间
不断尝试获取锁, 如果没有获取, 根据重试策略直接进行重试, 或超时返回
设置锁
使用obtain.lua进行尝试加锁, 使用 msetnx 和 mset 操作
获取TTL
使用pttl.lua进行最小ttl的获取
刷新TTL
使用refresh.lua更新所有的key的ttl
释放
调用release.lua删除所有占用的key
Redislock源码分析
重试策略
- 最大重试 LimitRetry
最大重试次数示例,重试3次,每次间隔100ms
1
2
3
4
5
6
7
|
// 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,
})
|
- 指数退避算法 ExponentialBackoff
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.
1
2
3
4
5
6
7
|
// Retry every 2**4ms, for up-to a 2**10ms
backoff := redislock.ExponentialBackoff(16 * time.Millisecond, 1024 * time.Millisecond)
// Obtain lock with retry
lock, err := locker.Obtain(ctx, "my-key", time.Second, &redislock.Options{
RetryStrategy: backoff,
})
|
- 自定义间隔重试 LinearBackoff
LinearBackoff allows retries regularly with customized intervals
自定义重试1分钟,重试间隔500ms
1
2
3
4
5
6
7
8
9
|
// 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,
})
|
NoRetry acquire the lock only once.
1
2
3
4
|
// NoRetry acquire the lock only once.
func NoRetry() RetryStrategy {
return linearBackoff(0)
}
|