使用go实现一个分布式锁

Redis 本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁,而且Redis的读写性能高,可以应对高并发的锁操作场景。


Redis的SET命令有个NX参数可以实现「key不存在才插入」,所以可以用它来实现分布式锁:

  •  如果key不存在,则显示插入成功,可以用来表示加锁成功。
  • 如果key 存在,则会显示插入失败,可以用来表示加锁失败。

基于Redis 节点实现分布式锁时,对于加锁操作,我们需要满足三个条件。

  • 加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作,但需要以原子操作的方式完成,所以,我们使用SET命令带上NX选项来实现加锁;
  • 锁变量需要设置过期时间,以免客户端拿到锁后发生异常,导致锁一直无法释放,所以,我们在SET命令执行时加上EX/PX选项,设置其过期时间;
  • 锁变量的值需要能区分来自不同客户端的加锁操作,以免在释放锁时,出现误释放操作,所以,我们使用SET命令设置锁变量值时,每个客户端设置的值是一个唯一值,用于标识客户端;
 package main
 ​
 import (
     "context"
     "fmt"
     "github.com/go-redis/redis/v8"
     "github.com/google/uuid"
     "time"
 )
 ​
 var (
     Rdb     *redis.Client
     ctx     = context.Background()
     lockKey = "my-lock-key"
 )
 ​
 func getRdb() {
     Rdb = redis.NewClient(&redis.Options{
         Addr:     "8.130.17.124:6379",
         Password: "021001",
         DB:       2,
     })
     fmt.Println("Redis连接成功")
 }
 ​
 func Close() {
     err := Rdb.Close()
     if err != nil {
         return
     }
     fmt.Println("Redis关闭成功")
 }
 ​
 // 尝试去获取锁
 func acquireLock() (bool, error) {
     // 生成唯一标识符作为锁的 value
     uniqueValue := uuid.New().String()
     // 使用 SETNX 命令尝试获取锁
     result, err := Rdb.SetNX(ctx, lockKey, uniqueValue, 5*time.Second).Result()
     if err != nil {
         return false, err
     }
     return result, nil
 }
 ​
 // 释放锁,使用Lua脚本保证只有拥有锁的客户端能释放锁
 func releaseLock() (bool, error) {
     // Lua脚本
     script := `
     if redis.call("GET", KEYS[1]) == ARGV[1] then
         return redis.call("DEL", KEYS[1])
     else
         return 0
     end`
 ​
     result, err := Rdb.Eval(ctx, script, []string{lockKey}, "1").Result()
     if err != nil {
         return false, err
     }
     return result.(int64) == 1, nil
 }
 ​
 func main() {
     getRdb()
     defer Close()
 ​
     locked, err := acquireLock()
     if err != nil {
         fmt.Println(err)
         return
     }
     if locked {
         fmt.Println("获取锁成功")
         // 业务逻辑
         time.Sleep(6 * time.Second)
 ​
         //释放锁
         unlocked, err := releaseLock()
         if err != nil {
             fmt.Println(err)
             return
         }
         if unlocked {
             fmt.Println("锁已释放")
         } else {
             fmt.Println("未能释放锁,锁可能已被其他客户端持有")
         }
     } else {
         fmt.Println("获取锁失败,另一个进程持有锁")
     }
 }
 

在加锁的时候,应该保证这个锁的key值和value值的唯一性。这样可以在后面解除锁的时候,方便定位目标是否持有锁。

在Redis集群下,可以使用Redlock(红锁)来保证分布式锁的可靠性。它是基于多个Redis节点的分布式锁,基本思路就是让客户端和多个Redis节点去申请锁,如果和超过半数以上的节点申请到锁,并且加锁时间没有超过锁的有效时间,就认为加锁成功

加锁失败之后,或者锁的有效期不足以对共享数据进行保护就可以释放锁;过程就是客户端向所有的Redis节点发起释放锁的操作,释放锁的操作和单节点一样,执行Lua脚本即可

  • 13
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 Redis 分布式锁的基本思路是利用 Redis 的 SETNX 命令(SET if Not eXists)实现。SETNX 命令会在 key 不存在的情况下,将 key 的值设为 value,如果 key 已经存在,则不做任何操作。 以下是一个简单的 Golang 实现 Redis 分布式锁的代码示例: ```go package redislock import ( "fmt" "time" "github.com/go-redis/redis/v7" ) type RedisLock struct { redisClient *redis.Client key string value string expiration time.Duration } func NewRedisLock(redisClient *redis.Client, key, value string, expiration time.Duration) *RedisLock { return &RedisLock{ redisClient: redisClient, key: key, value: value, expiration: expiration, } } func (r *RedisLock) Lock() (bool, error) { success, err := r.redisClient.SetNX(r.key, r.value, r.expiration).Result() if err != nil { return false, err } return success, nil } func (r *RedisLock) Unlock() error { err := r.redisClient.Del(r.key).Err() if err != nil { return err } return nil } ``` 在上面的代码中,NewRedisLock 函数用于创建一个 RedisLock 实例,需要传入 Redis 客户端、锁的 key、锁的值、锁的过期时间。Lock 方法用于尝试获取锁,如果获取成功,返回 true,否则返回 false。Unlock 方法用于释放锁。 以下是一个简单的使用示例: ```go package main import ( "fmt" "time" "github.com/go-redis/redis/v7" "github.com/yourusername/redislock" ) func main() { redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", DB: 0, }) lock := redislock.NewRedisLock(redisClient, "my-lock", "my-value", 10*time.Second) success, err := lock.Lock() if err != nil { fmt.Println("failed to acquire lock:", err) return } if !success { fmt.Println("lock is already held by another process") return } defer lock.Unlock() // Do some work } ``` 在上面的示例中,我们创建了一个 Redis 客户端,并且创建了一个 RedisLock 实例。然后,我们调用 Lock 方法尝试获取锁,如果获取成功,就可以进行一些需要加锁的操作。最后,我们调用 Unlock 方法释放锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值