golang redis并发锁如何实现


在Go语言中实现Redis分布式锁主要依赖于Redis的SETNX命令和Lua脚本,确保在高并发环境下的原子性和可靠性4。


package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
)

var (
	rdb *redis.Client
	ctx = context.Background()
)

// 初始化Redis连接
func initRedis() {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // 无密码
		DB:       0,  // 默认数据库
	})
}

// 获取分布式锁
func acquireLock(lockKey string, ttl time.Duration) (string, error) {
	clientID := uuid.NewString()
	
	result, err := rdb.SetNX(ctx, lockKey, clientID, ttl).Result()
	if err != nil {
		return "", err
	}
	
	if !result {
		return "", fmt.Errorf("获取锁失败")
	}
	
	return clientID, nil
}

// 释放分布式锁的Lua脚本
const unlockScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("del", KEYS[1])
else
	return 0
end
`

// 释放分布式锁
func releaseLock(lockKey, clientID string) error {
	script := redis.NewScript(unlockScript)
	
	result, err := script.Run(ctx, rdb, []string{lockKey}, clientID).Result()
	if err != nil {
		return err
	}
	
	if result.(int64) == 0 {
		return fmt.Errorf("释放锁失败:不是锁的持有者")
	}
	
	return nil
}

// 自动续期
func renewLock(lockKey, clientID string, ttl time.Duration) error {
	script := `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("expire", KEYS[1], ARGV[2])
else
	return 0
end
`
	result, err := redis.NewScript(script).Run(ctx, rdb, []string{lockKey}, clientID, int(ttl/time.Second)).Result()
	if err != nil {
		return err
	}
	
	if result.(int64) == 0 {
		return fmt.Errorf("续期失败:不是锁的持有者")
	}
	
	return nil
}

// 带重试的获取锁
func acquireLockWithRetry(lockKey string, ttl time.Duration, maxRetries int) (string, error) {
	for i := 0; i < maxRetries; i++ {
		clientID, err := acquireLock(lockKey, ttl)
		if err == nil {
			return clientID, nil
		}
		
		time.Sleep(100 * time.Millisecond)
	}
	
	return "", fmt.Errorf("获取锁超时")
}

func main() {
	// 初始化Redis连接
	initRedis()
	
	// 测试锁功能
	lockKey := "my_distributed_lock"
	ttl := 10 * time.Second
	
	// 获取锁
	clientID, err := acquireLockWithRetry(lockKey, ttl, 5)
	if err != nil {
		log.Fatal("获取锁失败:", err)
	}
	
	fmt.Printf("成功获取锁,客户端ID: %s\n", clientID)
	
	// 模拟业务处理
	fmt.Println("执行关键业务操作...")
	time.Sleep(5 * time.Second)
	
	// 续期锁
	err = renewLock(lockKey, clientID, ttl)
	if err != nil {
		log.Println("续期失败:", err)
	} else {
		fmt.Println("锁续期成功")
	}
	
	// 释放锁
	err = releaseLock(lockKey, clientID)
	if err != nil {
		log.Fatal("释放锁失败:", err)
	}
	
	fmt.Println("锁释放成功")
}

该实现包含以下核心功能:

1. **原子性加锁**:使用SETNX命令确保只有一个客户端能成功获取锁。

2. **唯一标识**:通过UUID生成客户端唯一ID,避免误删其他客户端的锁。

3. **安全释放**:采用Lua脚本保证只有锁的持有者才能释放锁,操作具有原子性。

4. **自动续期**:提供锁的续期功能,防止业务执行时间过长导致锁自动过期。

5. **重试机制**:支持在获取锁失败时进行有限次数的重试。

6. **超时控制**:通过TTL设置锁的自动过期时间,避免死锁情况发生。

在实际使用中,建议将锁的TTL设置为业务处理时间的2-3倍,并定期检查锁的状态进行续期,确保分布式系统的数据一致性


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注