在 Go 语言里,可以借助 Redis 实现分布式锁,以下为你介绍几种常见的实现方式。
1. 使用SET命令
Redis 2.6.12 版本之后,SET 命令支持 NX(键不存在时设置)和 EX(设置过期时间)选项,能原子性地完成设置键值和过期时间的操作,从而避免死锁。
go
package main
import (
"context"
"fmt"
"time"
"github.com/go-redis/redis/v8"
)
// 初始化 Redis 客户端
var redisClient = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
// AcquireLock 尝试获取锁
func AcquireLock(ctx context.Context, lockKey string, lockValue string, expiration time.Duration) bool {
set, err := redisClient.SetNX(ctx, lockKey, lockValue, expiration).Result()
if err != nil {
fmt.Printf("获取锁时发生错误: %v\n", err)
return false
}
return set
}
// ReleaseLock 释放锁
func ReleaseLock(ctx context.Context, lockKey string) {
err := redisClient.Del(ctx, lockKey).Err()
if err != nil {
fmt.Printf("释放锁时发生错误: %v\n", err)
}
}
func main() {
ctx := context.Background()
lockKey := "my_distributed_lock"
lockValue := "unique_value"
expiration := 10 * time.Second
if AcquireLock(ctx, lockKey, lockValue, expiration) {
defer ReleaseLock(ctx, lockKey)
fmt.Println("获取到锁,执行关键操作...")
// 模拟关键操作
time.Sleep(5 * time.Second)
fmt.Println("关键操作执行完毕,释放锁")
} else {
fmt.Println("未能获取到锁")
}
}
代码解释
- 初始化 Redis 客户端:利用 github.com/go-redis/redis/v8 库创建一个 Redis 客户端。
- AcquireLock 函数:借助 SetNX 方法尝试获取锁,若键不存在则设置成功并返回 true,同时设置过期时间以避免死锁。
- ReleaseLock 函数:使用 Del 方法删除键,从而释放锁。
- main 函数:尝试获取锁,若成功则执行关键操作,最后释放锁;若失败则输出提示信息。
2. 使用 Redlock 算法(多节点 Redis)
在多节点 Redis 场景下,可使用 Redlock 算法增强分布式锁的可靠性。
go
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/go-redis/redis/v8"
)
// 初始化多个 Redis 客户端
var redisClients = []*redis.Client{
redis.NewClient(&redis.Options{Addr: "localhost:6379", Password: "", DB: 0}),
redis.NewClient(&redis.Options{Addr: "localhost:6380", Password: "", DB: 0}),
redis.NewClient(&redis.Options{Addr: "localhost:6381", Password: "", DB: 0}),
}
// AcquireRedlock 尝试获取 Redlock
func AcquireRedlock(ctx context.Context, lockKey string, lockValue string, expiration time.Duration) bool {
var wg sync.WaitGroup
successCount := 0
for _, client := range redisClients {
wg.Add(1)
go func(c *redis.Client) {
defer wg.Done()
set, err := c.SetNX(ctx, lockKey, lockValue, expiration).Result()
if err == nil && set {
successCount++
}
}(client)
}
wg.Wait()
return successCount > len(redisClients)/2
}
// ReleaseRedlock 释放 Redlock
func ReleaseRedlock(ctx context.Context, lockKey string) {
for _, client := range redisClients {
err := client.Del(ctx, lockKey).Err()
if err != nil {
fmt.Printf("释放 Redlock 时发生错误: %v\n", err)
}
}
}
func main() {
ctx := context.Background()
lockKey := "my_redlock"
lockValue := "unique_value"
expiration := 10 * time.Second
if AcquireRedlock(ctx, lockKey, lockValue, expiration) {
defer ReleaseRedlock(ctx, lockKey)
fmt.Println("获取到 Redlock,执行关键操作...")
// 模拟关键操作
time.Sleep(5 * time.Second)
fmt.Println("关键操作执行完毕,释放 Redlock")
} else {
fmt.Println("未能获取到 Redlock")
}
}
代码解释
- 初始化多个 Redis 客户端:创建多个 Redis 客户端,分别连接不同的 Redis 节点。
- AcquireRedlock 函数:并发地在多个 Redis 节点上尝试获取锁,统计成功获取锁的节点数量,若超过半数则认为获取锁成功。
- ReleaseRedlock 函数:在所有 Redis 节点上释放锁。
- main 函数:尝试获取 Redlock,若成功则执行关键操作,最后释放锁;若失败则输出提示信息。