[场景实战]如何用golang 实现Redis 分布式锁
·
今天我们来讲一讲如何使用 Go 语言实现 Redis 分布式锁的完整示例,涵盖 加锁、解锁、自动续期、手动续期 以及 Redlock 算法 的实现方式。我们使用 go-redis 作为 Redis 客户端库,github.com/google/uuid 用于生成唯一标识符。
🧰 一、依赖安装
go get github.com/go-redis/redis/v8
go get github.com/google/uuid
✅二、 基础 Redis 客户端配置
package main
import (
"context"
"fmt"
"time"
"github.com/go-redis/redis/v8"
"github.com/google/uuid"
)
var (
rdb *redis.Client
)
func initRedis() {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
}
🔒 三、获取锁(基础实现)
// 获取锁
func acquireLock(ctx context.Context, key string, ttl time.Duration) (string, error) {
clientID := uuid.NewString()
result, err := rdb.SetNX(ctx, key, clientID, ttl).Result()
if err != nil {
return "", err
}
if !result {
return "", fmt.Errorf("failed to acquire lock")
}
return clientID, nil
}
🚪 四、释放锁(使用 Lua 脚本确保原子性)
// Lua 脚本:只有持有者才能释放锁
const unlockScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`
func releaseLock(ctx context.Context, key, clientID string) error {
script := redis.NewScript(unlockScript)
result, err := script.Run(ctx, rdb, []string{key}, clientID).Result()
if err != nil {
return err
}
if result.(int64) == 0 {
return fmt.Errorf("unlock failed: not the lock holder")
}
return nil
}
⏳ 五、自动续期(看门狗机制)
// 自动续期函数
func renewLock(ctx context.Context, key, clientID string, interval time.Duration, stopCh <-chan struct{}) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// 检查是否是当前客户端持有锁,是的话进行续期
script := redis.NewScript(`
if redis.call("get", KEYS[1]) == ARGV[1] then
redis.call("expire", KEYS[1], ARGV[2])
return 1
else
return 0
end
`)
res, err := script.Run(ctx, rdb, []string{key}, clientID, 30).Int64()
if err != nil || res == 0 {
return
}
case <-stopCh:
return
}
}
}
使用示例:
func demoAutoRenew(ctx context.Context) {
key := "my_lock"
ttl := 30 * time.Second
interval := 10 * time.Second
stop := make(chan struct{})
defer close(stop)
clientID, err := acquireLock(ctx, key, ttl)
if err != nil {
panic(err)
}
defer releaseLock(ctx, key, clientID)
// 启动看门狗
go renewLock(ctx, key, clientID, interval, stop)
// 模拟业务逻辑
time.Sleep(60 * time.Second)
// 停止续期
close(stop)
}
🔁 六、手动续期(调用 Lua 脚本)
// 手动续期
func renewLockManually(ctx context.Context, key, clientID string, newTTL time.Duration) error {
script := redis.NewScript(`
if redis.call("get", KEYS[1]) == ARGV[1] then
redis.call("expire", KEYS[1], ARGV[2])
return 1
else
return 0
end
`)
result, err := script.Run(ctx, rdb, []string{key}, clientID, int64(newTTL.Seconds())).Result()
if err != nil {
return err
}
if result.(int64) == 0 {
return fmt.Errorf("renew failed: not the lock holder")
}
return nil
}
使用示例:
func demoManualRenew(ctx context.Context) {
key := "my_lock"
ttl := 30 * time.Second
clientID, err := acquireLock(ctx, key, ttl)
if err != nil {
panic(err)
}
defer releaseLock(ctx, key, clientID)
// 手动续期 30s
err = renewLockManually(ctx, key, clientID, 30*time.Second)
if err != nil {
panic(err)
}
}
🔄 七、Redlock 算法(简化实现)
type RedisNode struct {
Host string
Port int
}
func createRedisClients(nodes []RedisNode) []*redis.Client {
clients := make([]*redis.Client, len(nodes))
for i, node := range nodes {
clients[i] = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", node.Host, node.Port),
})
}
return clients
}
func redLock(ctx context.Context, key string, value string, ttl time.Duration, nodes []*redis.Client) (bool, error) {
quorum := len(nodes) / 2 + 1
lockedNodes := 0
for _, client := range nodes {
result, err := client.SetNX(ctx, key, value, ttl).Result()
if err != nil {
continue
}
if result {
lockedNodes++
}
}
if lockedNodes >= quorum {
return true, nil
}
return false, fmt.Errorf("failed to acquire lock on majority of nodes")
}
使用示例:
func demoRedLock(ctx context.Context) {
nodes := []RedisNode{
{"127.0.0.1", 6379},
{"127.0.0.1", 6380},
{"127.0.0.1", 6381},
}
clients := createRedisClients(nodes)
key := "my_redlock"
value := uuid.NewString()
ttl := 30 * time.Second
locked, err := redLock(ctx, key, value, ttl, clients)
if err != nil {
panic(err)
}
if !locked {
fmt.Println("Failed to acquire RedLock")
return
}
// 执行业务逻辑
time.Sleep(10 * time.Second)
// 释放锁(遍历所有节点)
for _, client := range clients {
releaseLock(ctx, key, value)
}
}
🧩 八、总结
| 功能 | 实现方式 |
|---|---|
| 获取锁 | 使用 SETNX |
| 释放锁 | 使用 Lua 脚本保证原子性 |
| 自动续期 | 启动 goroutine 定期续约 |
| 手动续期 | Lua 脚本更新过期时间 |
| Redlock 算法 | 多个 Redis 节点上加锁,多数成功即可 |
✅ 九、最佳实践建议
- 使用
defer释放锁,避免忘记释放导致死锁; - 使用
context.Context控制 goroutine 生命周期; - 在实际生产中,建议使用成熟的库如 go-redsync;
- 锁持有期间,业务逻辑应具备幂等性,以应对锁过期后的并发问题。
更多推荐

所有评论(0)