• Go微服务: redis分布式锁


    概述

    • 在分布式系统中,并发控制和数据一致性是至关重要的问题
    • 当多个服务或进程需要访问和修改共享资源时,我们必须确保在同一时间只有一个服务或进程能够执行操作,以防止数据竞争和不一致,这就是分布式锁要解决的问题
    • Redis 作为一个高性能的键值存储系统,经常被用作实现分布式锁的工具。
    • Redis 的 SETNX、EXPIRE、DEL 等命令可以组合起来实现一个简单的分布式锁,但是,直接使用这些命令可能会引入一些复杂的逻辑和潜在的错误
    • 因此,许多开发者选择使用现成的库来简化分布式锁的实现
    • 在 Go 语言中,github.com/go-redsync/redsync 是一个流行的库,它基于 Redis 提供了分布式锁的抽象和实现

    Redis 分布式锁的基本原理


    Redis 分布式锁通常基于以下原理

    • 加锁:使用 SETNX 命令尝试设置一个键值对,如果键不存在则设置成功(返回 1),否则设置失败(返回 0)。设置成功的进程获得了锁
    • 设置过期时间:为了防止死锁,通常会为锁设置一个过期时间,使用 EXPIRE 命令
    • 解锁:当进程完成操作后,需要删除之前设置的键值对来释放锁,使用 DEL 命令
    • 处理锁过期:如果持有锁的进程在锁过期之前未能完成操作并释放锁,其他进程可以重新获取锁

    示例程序

    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/go-redsync/redsync/v4"
    	"github.com/go-redsync/redsync/v4/redis/goredis/v9"
    	goredislib "github.com/redis/go-redis/v9"
    
    	"sync"
    )
    
    func RedisLock(wg *sync.WaitGroup) {
    	// 初始化锁客户端
    	client := goredislib.NewClient(&goredislib.Options{
    		Addr:     "127.0.0.1:6380",
    		Password: "123456_redis",
    		Username: "root",
    		DB:       0,
    	})
    	pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
    	rs := redsync.New(pool)
    
    	// 初始化锁
    	mutexname := "my-global-mutex"
    	mutex := rs.NewMutex(mutexname, redsync.WithExpiry(30*time.Second))
    
    	// 开始锁定
    	fmt.Println("Lock()....")
    	if err := mutex.Lock(); err != nil {
    		panic(err)
    	}
    	// 自己的一些业务逻辑
    	fmt.Println("Get Lock!!!")
    	time.Sleep(time.Second * 1)
    	// 开始解锁
    	fmt.Println("UnLock()")
    	if ok, err := mutex.Unlock(); !ok || err != nil {
    		panic("unlock failed")
    	}
    	fmt.Println("Released Lock!!!")
    }
    
    func main() {
    	// 测试程序
    	var wg sync.WaitGroup
    	for i := 0; i < 3; i++ {
    		wg.Add(1)
    		go RedisLock(&wg)
    	}
    	wg.Wait()
    }
    
    • 程序运行,输出

      Lock()....
      Lock()....
      Lock()....
      Get Lock!!!
      UnLock()
      Released Lock!!!
      Get Lock!!!
      UnLock()
      Released Lock!!!
      Get Lock!!!
      UnLock()
      Released Lock!!!
      
    • 前三个Lock() 代表程序在排队阶段

    • 后面接着三次输出,每次都有: Get Lock!!!, UnLock(), Released Lock!!!

    • 每个过程都是 获取锁,解锁,释放锁

    • 所以在高并发的时候,使用redis的分布式锁可以解决资源数据的竞争

    • 特别注意的是: mutexname := "my-global-mutex" 这个锁的名称在 redis 中使用的时候会生成,当锁被全部释放后,就会自动释放,而且这个锁的名称最好也自行规范下

      // 以下是 docker 内的redis容器cli
      127.0.0.1:6379> keys *
      1) "my-global-mutex"
      // 稍后再次查询后,锁的key和value消失
      127.0.0.1:6379> keys *
      (empty array)
      
    • 另外,如果锁在规定的时间内没有完成工作,那么在设定的过期时间后就会自动结束,如果不设置过期时间,走系统内的过期时间

    使用 redsync 实现 Redis 分布式锁

    • redsync 库提供了一个高级接口,使得在 Go 语言中使用 Redis 分布式锁变得更加简单
    • 下面是如何使用 redsync 实现分布式锁的步骤:
      • 初始化 Redis 客户端:首先,你需要初始化一个 Redis 客户端。在上述代码中,使用了 github.com/redis/go-redis/v9 库来创建 Redis 客户端,并为其设置了地址、密码、用户名和数据库编号
      • 创建锁池:redsync 需要一个锁池(Pool)来管理锁,使用 goredis.NewPool 方法来创建一个基于 go-redis 客户端的锁池
      • 创建分布式锁:使用 redsync.New 方法来创建一个 Redsync 实例,并使用 NewMutex 方法来创建一个分布式锁,可以为锁指定一个名称,并设置锁的过期时间。
      • 加锁:调用 Lock 方法来尝试获取锁,如果加锁失败(例如,其他进程已经持有锁),则该方法会返回一个错误
      • 执行业务逻辑:在成功获取锁之后,执行您的业务逻辑
      • 解锁:完成业务逻辑后,调用 Unlock 方法来释放锁,请注意,Unlock 方法会返回一个布尔值和一个错误,布尔值表示是否成功释放了锁,而错误则表示在解锁过程中是否发生了错误

    注意事项

    • 死锁:务必为锁设置合理的过期时间,以防止死锁
    • 重试机制:当加锁失败时,您可能需要实现一个重试机制来等待一段时间后再次尝试加锁
    • 解锁失败:虽然 Unlock 方法会尝试释放锁,但在某些情况下(例如,Redis 实例崩溃或网络问题),解锁可能会失败。因此,您应该始终检查 Unlock 方法的返回值,并在必要时处理解锁失败的情况
    • 并发控制:在分布式系统中,除了使用分布式锁之外,您还需要考虑其他并发控制策略,例如消息队列、事件驱动架构等
    • 安全性:确保 Redis 实例的安全性,包括使用强密码、限制访问权限等。此外,如果您的系统对安全性有更高的要求,您可能需要考虑使用更复杂的分布式锁实现,例如基于 ZooKeeper 或 etcd 的分布式锁

    参考文档

  • 相关阅读:
    第4章 前馈神经网络
    照片怎么压缩变小?
    行列均按段分组汇总
    363. 矩形区域不超过 K 的最大数值和
    法律战爆发:“币安退出俄罗斯引发冲击波“
    不知道吧?未加工的食物可以帮助你减肥
    内置函数部分
    crossover23.6闪亮登场发布啦,2023最新功能解析
    深度学习服务器(Linux)开发环境搭建教程
    编译安装oh-my-zsh
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/139844533