Go 使用 Redis 实现分布式锁

embedded/2024/11/14 23:00:49/

Go 使用 Redis 实现分布式
Redis 提供了一些原语,可以帮助我们实现高效的分布式锁。下边是使用 Redis 实现分布式锁的一种常见方法

实现分布式锁的方法

1. 使用 Redis 的 SET 命令

Redis 的 SET 命令支持设置键值对,并且可以通过 NXEX 参数来实现原子性操作,从而实现分布式锁。

  • NX:只有当键不存在时,才设置键。
  • EX:设置键的过期时间(秒)。

示例代码

以下是一个使用 Go 和 Redis 实现分布式锁的示例代码:

package mainimport ("context""fmt""log""time""github.com/go-redis/redis/v8"
)var ctx = context.Background()func main() {// 初始化 Redis 客户端rdb := redis.NewClient(&redis.Options{Addr:     "localhost:6379", // Redis 地址Password: "",               // 密码DB:       0,                // 数据库编号})// 锁的键名和超时时间key := "my_lock"timeout := time.Second * 10// 尝试获取锁lockAcquired := acquireLock(ctx, rdb, key, timeout)if lockAcquired {defer releaseLock(ctx, rdb, key)// 在这里执行需要加锁的操作fmt.Println("Lock acquired, performing critical section operations...")time.Sleep(time.Second * 5) // 模拟耗时操作fmt.Println("Critical section operations completed.")} else {fmt.Println("Failed to acquire lock.")}
}// acquireLock 尝试获取锁
func acquireLock(ctx context.Context, client *redis.Client, key string, timeout time.Duration) bool {// 设置键值对,只有当键不存在时才设置,并设置过期时间result, err := client.SetNX(ctx, key, "locked", timeout).Result()if err != nil {log.Fatalf("Failed to acquire lock: %v", err)}return result
}// releaseLock 释放锁
func releaseLock(ctx context.Context, client *redis.Client, key string) {// 删除键err := client.Del(ctx, key).Err()if err != nil {log.Printf("Failed to release lock: %v", err)}
}

注意事项

  1. 超时时间:设置合理的超时时间,防止死锁。如果持有锁的进程崩溃,锁不会永远占用。
  2. 幂等性:确保释放锁的操作是幂等的,即多次调用 releaseLock 不会出问题。
  3. 竞争条件:在高并发场景下,可能会出现竞争条件。可以通过 Lua 脚本来确保原子性操作。
  4. 安全性:确保只有持有锁的进程才能释放锁。可以通过在 SET 命令中设置唯一的值来实现这一点。

使用 Lua 脚本确保原子性

为了确保释放锁的操作是原子的,可以使用 Lua 脚本来实现。以下是一个改进的示例:

package mainimport ("context""fmt""log""time""github.com/go-redis/redis/v8"
)var ctx = context.Background()func main() {// 初始化 Redis 客户端rdb := redis.NewClient(&redis.Options{Addr:     "localhost:6379", // Redis 地址Password: "",               // 密码DB:       0,                // 数据库编号})// 锁的键名和超时时间key := "my_lock"value := "unique_value"timeout := time.Second * 10// 尝试获取锁lockAcquired := acquireLock(ctx, rdb, key, value, timeout)if lockAcquired {defer releaseLock(ctx, rdb, key, value)// 在这里执行需要加锁的操作fmt.Println("Lock acquired, performing critical section operations...")time.Sleep(time.Second * 5) // 模拟耗时操作fmt.Println("Critical section operations completed.")} else {fmt.Println("Failed to acquire lock.")}
}// acquireLock 尝试获取锁
func acquireLock(ctx context.Context, client *redis.Client, key, value string, timeout time.Duration) bool {// 设置键值对,只有当键不存在时才设置,并设置过期时间result, err := client.SetNX(ctx, key, value, timeout).Result()if err != nil {log.Fatalf("Failed to acquire lock: %v", err)}return result
}// releaseLock 释放锁
func releaseLock(ctx context.Context, client *redis.Client, key, value string) {// 使用 Lua 脚本确保释放锁的操作是原子的script := redis.NewScript(`if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])elsereturn 0end`)err := script.Run(ctx, client, []string{key}, value).Err()if err != nil {log.Printf("Failed to release lock: %v", err)}
}

使用 SET 命令和 Lua 脚本可以确保操作的原子性和安全性。

====================

分布式锁的实现中,key 是一个非常重要的参数,它用于唯一标识一个锁。下面详细解释 keyacquireLock 方法中的作用:

key 的作用

  1. 唯一标识锁

    • key 是一个字符串,用于唯一标识一个特定的锁。不同的锁应该有不同的 key,这样可以确保不同的资源可以独立地被锁定。
    • 例如,如果你有两个资源 resource1resource2,你可以分别为它们设置不同的 key,比如 "lock:resource1""lock:resource2"
  2. 存储锁的状态

    • 当你尝试获取锁时,key 被用作 Redis 中的一个键。如果这个键已经存在,说明已经有其他客户端持有了这个锁。
    • 如果键不存在,Redis 会设置这个键,并将其值设为你提供的值(例如 "locked" 或一个唯一的标识符)。
  3. 设置过期时间

    • 在设置键的同时,你可以为键设置一个过期时间(使用 EX 参数)。这可以防止锁由于客户端崩溃或其他原因而永远占用。
    • 过期时间确保了即使持有锁的客户端出现问题,锁最终也会自动释放。

示例代码中的 key 使用

在之前的示例代码中,key 被用于 acquireLock 方法中:

func acquireLock(ctx context.Context, client *redis.Client, key, value string, timeout time.Duration) bool {// 设置键值对,只有当键不存在时才设置,并设置过期时间result, err := client.SetNX(ctx, key, value, timeout).Result()if err != nil {log.Fatalf("Failed to acquire lock: %v", err)}return result
}
  • key:用于唯一标识锁的键。
  • value:设置键的值,可以是一个固定的字符串(如 "locked"),也可以是一个唯一的标识符(如客户端的唯一 ID)。
  • timeout:设置键的过期时间,单位为秒。

具体示例

假设你有两个资源 resource1resource2,你可以分别为它们设置不同的 key

key1 := "lock:resource1"
key2 := "lock:resource2"// 尝试获取 resource1 的锁
lockAcquired1 := acquireLock(ctx, rdb, key1, "unique_value1", time.Second * 10)
if lockAcquired1 {defer releaseLock(ctx, rdb, key1, "unique_value1")// 在这里执行需要加锁的操作fmt.Println("Lock acquired for resource1, performing critical section operations...")time.Sleep(time.Second * 5) // 模拟耗时操作fmt.Println("Critical section operations completed for resource1.")
} else {fmt.Println("Failed to acquire lock for resource1.")
}// 尝试获取 resource2 的锁
lockAcquired2 := acquireLock(ctx, rdb, key2, "unique_value2", time.Second * 10)
if lockAcquired2 {defer releaseLock(ctx, rdb, key2, "unique_value2")// 在这里执行需要加锁的操作fmt.Println("Lock acquired for resource2, performing critical section operations...")time.Sleep(time.Second * 5) // 模拟耗时操作fmt.Println("Critical section operations completed for resource2.")
} else {fmt.Println("Failed to acquire lock for resource2.")
}

KEY

key分布式锁的实现中起到了唯一标识锁的作用。通过为不同的资源设置不同的 key,可以确保不同的资源可以独立地被锁定。同时,key 还用于存储锁的状态,并可以设置过期时间以防止死锁。


http://www.ppmy.cn/embedded/136628.html

相关文章

Linux:基于ncdu命令的存储容量自动扫描统计工具

一、背景 设备存储容量不够时,需要删除清理无用文件,若文件目录较多,逐个去统计每个文件目录的存储占用量,比较麻烦。ncdu命令有一个比较好的扫描和删除交互界面,基于ncdu命令写一个定时自动统计脚本,可以…

PPT文件设置了修改权限,如何取消权?

不知道大家在使用PPT文件的时候,是否遇到过下面的提示框,这就是PPT文件设置了修改权限,只有输入密码才可以编辑文件。 如果我们没有输入密码,以只读方式进入,那么我们会发现功能栏中的按钮全是灰色,无法使用…

w030基于web的甘肃非物质文化网站的设计与开发

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文件&#xff0…

zookeeper常用命令

1.5.1 连接到 ZooKeeper 服务器 # 连接到本地 ZooKeeper 服务器 # 先进入ZooKeeper容器:docker exec -it zookeeper bash bin/zkCli.sh -server localhost:21811.5.2 基本命令 1.5.2.1 查看帮助 help1.5.2.2 列出子节点 ls /1.5.3 ZNode 操作 1.5.3.1 创建 ZNo…

右旋圆极化散射后的stocks矢量 与T3矩阵的关系

T3矩阵如下 斯托克斯与T3的关系如下。 斯托克斯与T3均没有平均处理,即斯托克斯是完全极化波的(一种琼斯矢量得到),T3是由一个散射矩阵得到,只有一个特征值。

【NLP自然语言处理】深入解析Encoder与Decoder模块:结构、作用与深度学习应用

目录 🍔 Encoder模块 1.1 Encoder模块的结构和作用 1.2 关于Encoder Block 1.3 多头自注意力层(self-attention) 🍔 Decoder模块及Add & Norm模块 3.1 Decoder模块介绍 3.2 Add & Norm模块 3.3 位置编码器Positional Encoding 3.4 Decod…

哥德巴赫猜想渐行渐远

我现在的工作,表明经典分析可能出了问题,如此则连Vinogradov的三素数定理都不成立了,更别说基于L-函数方程的陈氏定理“12”了。事实上即使L-函数方程成立,由于我指出Siegel定理不成立,陈景润和张益唐的工作就不成立。…

高效管理iPhone存储:苹果手机怎么删除相似照片

在使用iPhone的过程中,我们经常会遇到存储空间不足的问题,尤其是当相册中充满了大量相似照片时。这些照片不仅占用了宝贵的存储空间,还可能使iPhone出现运行卡顿的情况。因此,我们迫切需要寻找苹果手机怎么删除相似照片的方法&…