当前位置: 首页 > news >正文

Go微服务: redis分布式锁

概述

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

Redis 分布式锁的基本原理


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

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

示例程序

package mainimport ("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.WaitGroupfor 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 的分布式锁

参考文档

  • https://redis.io/docs/latest/develop/use/patterns/distributed-locks/
  • https://redis.uptrace.dev/zh/guide/go-redis-option.html#redis-client

相关文章:

  • css中content属性你了解多少?
  • 【Python Cookbook】S02E12 字符串的连接及合并 ‘ ‘.join()
  • 解决uniapp h5 本地代理实现跨域访问及如何配置开发环境
  • C语言笔记25 •顺序表介绍•
  • Ubuntu乌班图安装VIM文本编辑器工具
  • k8s解决java服务下载超时问题
  • lighttpd cgi不能重启
  • 【毕业设计】Django 校园二手交易平台(有源码+mysql数据)
  • 笔记-python map函数
  • 视频智能分析平台智能边缘分析一体机安防监控平台打手机检测算法工作原理介绍
  • 2024年旅游与经济发展国际会议(ICTED 2024)
  • 在WordPress上添加亚马逊联盟链接的三种方法
  • 网络安全筑基篇——SQL注入
  • 什么是粘性代理IP
  • Ubuntu 22.04.4 LTS openresty(Nginx) 通过Lua+Redis 实现动态封禁IP
  • avalon2.2的VM生成过程
  • gops —— Go 程序诊断分析工具
  • java 多线程基础, 我觉得还是有必要看看的
  • Java编程基础24——递归练习
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • mockjs让前端开发独立于后端
  • MySQL主从复制读写分离及奇怪的问题
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • VUE es6技巧写法(持续更新中~~~)
  • 创建一种深思熟虑的文化
  • 从伪并行的 Python 多线程说起
  • 第2章 网络文档
  • 复杂数据处理
  • 给第三方使用接口的 URL 签名实现
  • 排序算法之--选择排序
  • 驱动程序原理
  • 深度学习入门:10门免费线上课程推荐
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 算法-插入排序
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • SAP CRM里Lead通过工作流自动创建Opportunity的原理讲解 ...
  • #QT(串口助手-界面)
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (4)事件处理——(2)在页面加载的时候执行任务(Performing tasks on page load)...
  • (C语言)fread与fwrite详解
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (二)c52学习之旅-简单了解单片机
  • (二)windows配置JDK环境
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (算法设计与分析)第一章算法概述-习题
  • (一) springboot详细介绍
  • (杂交版)植物大战僵尸
  • .java 9 找不到符号_java找不到符号
  • .NET 使用 XPath 来读写 XML 文件