go 分布式redis锁的实现方式

news2025/3/4 16:27:12

go 语言以高并发著称。那么在实际的项目中 经常会用到锁的情况。比如说秒杀抢购等等场景。下面主要介绍 redis 布式锁实现的两种高并发抢购场景。其实 高并发分布式锁 一个互斥的两个状态:

方式一 setNX:

使用 redis自带的API setNX 来实现。能解决高并发场景下的 绝大多数场景,待优化点 锁的续命 和 等待锁 的实现。实现流程:

  1. redis setNX 设置键值。如果 键存在则返回 false 反之则为 true
  2. 使用 setNX 来设置一个键值,值为当前协程设置的随机值。
  3. 当程序运行完成之后, 删除该键值
 这里只有当减库存成功

抢购流程成功 则返回 410其余失败则返回 200这样就能通过返回码  很容易看到成功抢购的数量 我么使用 postman 模拟 1600 用户点击 十分钟。库存为 一个亿。

// redis分布式锁  方式1:自己动手
// 该方案可以解决大多数场景中的 redis 锁的问题,
// 还剩余一个 锁续命的问题 极高并发下的微小概率事件
func redisLock_0(c *gin.Context) {
	// 实现逻辑
	// 1 先用商品ID为 key, uuid为值,  这一步是防止别人把自己的锁删除
	// 2 用SetNX 设置一个键值 锁住一个商品,并设置超时时间。 当 SetNX key 存在则 返回false, 反之为 true
	rdb := Rdb()
	lockKey := "product_001"
	newUUID := uuid.New()
	// 只能删除锁  并切判断是不是自己的锁,只有自己的锁才会删除
	defer func() {
		keyValue, err := rdb.Get(ctx, lockKey).Result()
		if err != nil {
			fmt.Println("keyValue error:", keyValue, err)
			c.JSON(http.StatusOK, gin.H{
				"message": "获取锁失败",
			})
			return
		}
		if keyValue == newUUID.String() {
			rdb.Del(ctx, lockKey)
		}
	}()

	//设置锁,30秒过期,只有当锁不存在时才会成功设置,
	//设置时间是为了 防止特殊情况所没有成功释放。
	success, err := rdb.SetNX(ctx, lockKey, newUUID.String(), time.Second*30).Result()
	if err != nil {
		fmt.Println("Error setting lock: %v", err)
		c.JSON(http.StatusOK, gin.H{
			"message": "设置锁单出错",
		})
		return
	}
	// 判断是否成功获得锁
	if success {
		fmt.Println("Successfully acquired lock:", newUUID)
		// 执行需要锁保护的操作 获取真实的 库存
		count, err := strconv.Atoi(rdb.Get(ctx, "product_count").Val())
		if err != nil {
			fmt.Println("Error getting product count: %v", err)
			c.JSON(http.StatusOK, gin.H{
				"message": "Error getting product count",
			})
			return
		}
		if count > 1 {
			stock := count - 1
			err := rdb.Set(ctx, "product_count", strconv.Itoa(stock), 0).Err()
			if err != nil {
				fmt.Println("Error setting product count: %v", err)
				c.JSON(http.StatusOK, gin.H{
					"message": "Error setting product count",
				})
				return
			} else {
				fmt.Println("减库存操作成功, 现在库存为: %v", stock)
				c.JSON(http.StatusGone, gin.H{
					"message": "Hello, World!",
				})
				return
			}
		} else {
			fmt.Println("库存为 0 ")
			c.JSON(http.StatusOK, gin.H{
				"message": "Hello, World!",
			})
			return
		}
	} else {
		///没有获得锁!  可以做延迟 轮询处理
		fmt.Println("Failed to acquire lock. The key already exists.")
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello, World!",
		})
		return
	}
}

经过十分钟我们看下数据:

该方案整体数据:
  • 一共请求了 534,979 次
  • 并发 877
  • 成功销售 280,367 个商品 即返回值为 410的个数。

方式二 redisson:

使用  go-redisson 库,这个 类似 java redisson:

go-redisson command - github.com/paceew/go-redisson - Go Packageshttps://pkg.go.dev/github.com/paceew/go-redisson

该方案使用起来就很简单了:

我们来测试一样的数据:

func redisLock_1(c *gin.Context) {
	//获取一个锁对象
	mutex := RedSon().NewMutex("godisson")
	//尝试加锁, 并且设置超时时间和等待时间,
	//如果加锁失败 会阻塞等待,或超时 或 加锁成功
	err := mutex.TryLock(20000, 20000)
	if err != nil {
		log.Println("can't obtained lock")
		c.JSON(http.StatusOK, gin.H{
			"message": "Error can't obtained lock",
		})
		return
	}
	defer func(mutex *godisson.Mutex) {
		_, err := mutex.Unlock()
		if err != nil {
			log.Println("can't obtained lock")
			c.JSON(http.StatusOK, gin.H{
				"message": "Error1 can't obtained lock",
			})
		}
	}(mutex)

	// 执行需要锁保护的操作 获取真实的 库存
	count, err := strconv.Atoi(rdb.Get(ctx, "product_count").Val())
	if err != nil {
		fmt.Println("Error getting product count: %v", err)
		c.JSON(http.StatusOK, gin.H{
			"message": "Error getting product count",
		})
		return
	}
	if count > 1 {
		stock := count - 1
		err := rdb.Set(ctx, "product_count", strconv.Itoa(stock), 0).Err()
		if err != nil {
			fmt.Println("Error setting product count: %v", err)
			c.JSON(http.StatusOK, gin.H{
				"message": "Error setting product count",
			})
			return
		} else {
			fmt.Println("减库存操作成功, 现在库存为: %v", stock)
			c.JSON(http.StatusGone, gin.H{
				"message": "Hello, World!",
			})
			return
		}
	} else {
		fmt.Println("库存为 0 ")
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello, World!",
		})
		return
	}
}

 

该方案整体数据:
  • 一共请求 528,686
  • 并发 868
  • 成功销售 343,381 个商品  即返回值为 410的个数。应该是实现了锁等待。所有这个方案比自己实现的抢购 要高。

如何提高吞吐 优化性能问题 

分段锁:

分段锁的核心思路就是:之前的方案都是一个锁,处理所有请求。这里呢 开十把锁。那吞吐性能不就 快了 十倍了麽。那么我们就采用redisson 来做十把分段锁:

把一个亿的商品库存,分成1千万的 十份。然后用 十把锁。这样:

func redisLock_2(c *gin.Context) {

	rand.Seed(time.Now().UnixNano())
	// 生成包含0和9的随机数
	num := rand.Intn(10)
	mutexKey := "godisson_" + strconv.Itoa(num)
	product_key := "product_count_" + strconv.Itoa(num)

	//获取一个锁对象
	mutex := RedSon().NewMutex(mutexKey)
	//尝试加锁, 并且设置超时时间和等待时间,
	//如果加锁失败 会阻塞等待,或超时 或 加锁成功
	err := mutex.TryLock(20000, 20000)
	if err != nil {
		log.Println("can't obtained lock")
		c.JSON(http.StatusOK, gin.H{
			"message": "Error can't obtained lock",
		})
		return
	}
	defer func(mutex *godisson.Mutex) {
		_, err := mutex.Unlock()
		if err != nil {
			log.Println("can't obtained lock")
			c.JSON(http.StatusOK, gin.H{
				"message": "Error1 can't obtained lock",
			})
		}
	}(mutex)

	// 执行需要锁保护的操作 获取真实的 库存
	count, err := strconv.Atoi(rdb.Get(ctx, product_key).Val())
	if err != nil {
		fmt.Println("Error getting product count: %v", err)
		c.JSON(http.StatusOK, gin.H{
			"message": "Error getting product count",
		})
		return
	}
	if count > 1 {
		stock := count - 1
		err := rdb.Set(ctx, product_key, strconv.Itoa(stock), 0).Err()
		if err != nil {
			fmt.Println("Error setting product count: %v", err)
			c.JSON(http.StatusOK, gin.H{
				"message": "Error setting product count",
			})
			return
		} else {
			fmt.Println("减库存操作成功, 现在库存为: %v", stock)
			c.JSON(http.StatusGone, gin.H{
				"message": "Hello, World!",
			})
			return
		}
	} else {
		fmt.Println("库存为 0 ")
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello, World!",
		})
		return
	}
}


 无超卖情况:

测试结果如下:
  • 一共请求 523,418
  • 并发 858
  • 成功销售 404,238 个商品  即返回值为 410的个数

如此看,不知道是我 单台机器性能跑满了测试不准确还是其他原因。并没有十倍的性能提升

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2309575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入理解递归:从原理到C++实践

什么是递归? 递归(Recursion)是编程中一种强大的技术,其核心思想是:函数直接或间接地调用自身。如同俄罗斯套娃一般,每个函数调用都会解开问题的一个层级,直到达到基础条件。 递归三要素&…

MyBatis-Plus 入门详解:从零搭建高效持久层

一、MyBatis-Plus 简介 MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,在保留 MyBatis 原生功能的基础上,提供了全自动化的 CRUD 操作、强大的分页插件、代码生成器等功能,显著减少开发工作量。与原生 MyBatis 相比&…

阿里云物联网获取设备属性api接口:QueryDevicePropertyData

阿里云物联网接口:QueryDevicePropertyData 说明:调用该接口查询指定设备或数字孪生节点,在指定时间段内,单个属性的数据 比如提取上传到物联网的温度数据 api文档:QueryDevicePropertyData_物联网平台_API文档-阿里…

歌曲分类和流行度预测

1. 项目介绍 本项目从kaggle平台上下载了数据集,该数据集包含了3万多首来自Spotify API 的歌曲,共有23个特征。首先对数据集进行预处理,如重复行、缺失值、标准化处理等。再对预处理后的数据进行探索性分析,观察各变量的分布情况&…

不重启mysql情况下排查慢SQL

查状态 mysql> show variables like %slow_query_log%; 开启慢日志 mysql> set global slow_query_logON; 设置1s超时 mysql> set global long_query_time1; 如果想更小,可以设置0.5 查看慢SQL的日志 cat /var/lib/mysql/localhost-slow.log &…

27、Java 反射机制

15-1 Java 反射机制概述 Reflection(反射)是被视为动态语言的关键 动态语言:在运行时代码可以根据某些条件改变自身结构。如 C#\JavaScript\PHP 静态语言:运行时结构不可变的语言。如 Java\C\C 问题:通过直接new的方…

Android 端侧运行 LLM 框架 MNN 及其应用

MNN Chat Android App - 基于 MNN 引擎的智能聊天应用 一、MNN 框架简介与工作原理1.1 什么是 MNN?1.2 MNN 的工作原理 二、MNN Chat Android App2.1 MNN Chat 的功能2.2 MNN Chat 的优势2.3 MNN Chat Android App 的使用 三、总结 随着移动端人工智能需求的日益增长…

FPGA学习(一) —— 四位全加器

FPGA学习(一) —— 四位全加器 文章目录 FPGA学习(一) —— 四位全加器一、半加器1、半加器的真值表2、Verilog代码实现3、RTL原理图4、波形仿真 二、一位全加器1、一位全加器真值表2、Verilog代码实现3、RTL原理图4、波形仿真 三…

PHP:IDEA开发工具配置XDebug,断点调试

文章目录 一、php.ini配置二、IDEA配置 一、php.ini配置 [xdebug] zend_extension"F:\wamp64\bin\php\php7.4.0\ext\php_xdebug-2.8.0-7.4-vc15-x86_64.dll" xdebug.remote_enable on xdebug.remote_host 127.0.0.1 xdebug.remote_port 9001 xdebug.idekey"…

LINUX网络基础 - 网络编程套接字,UDP与TCP

目录 前言 一. 端口号的认识 1.1 端口号的作用 二. 初识TCP协议和UDP协议 2.1 TCP协议 TCP的特点 使用场景 2.2 UDP协议 UDP的特点 使用场景 2.3 TCP与UDP的对比 2.4 思考 2.5 总结 三. 网络字节序 3.1 网络字节序的介绍 3.2 网络字节序思考 四. socket接口 …

QT实现单个控制点在曲线上的贝塞尔曲线

最终效果: 一共三个文件 main.cpp #include <QApplication> #include "SplineBoard.h" int main(int argc,char** argv) {QApplication a(argc, argv);SplineBoard b;b.setWindowTitle("标准的贝塞尔曲线");b.show();SplineBoard b2(0.0001);b2.sh…

Linux基础开发工具(vim编译器,yum与apt软件安装)

Linux 下载安装软件的方案 源代码安装-》》》非常麻烦与复杂一步错步步错 rmp包安装 -》》》只是安装没有对应的库与依赖相当于只是一个外壳 包管理器进行安装-》》 yum / apt(本篇重点讲解) 1.什么是软件包和软件包管理器 就好⽐ "App" 和 "应⽤商店"…

神经网络 - 激活函数(Maxout 单元)

一、Maxout 单元 Maxout 单元是一种特殊的激活函数&#xff0c;用于神经网络中&#xff0c;其主要思想是通过多个线性变换的最大值来作为神经元的输出&#xff0c;从而提高模型的表达能力和鲁棒性。 1. 数学定义 假设输入为 x&#xff0c;Maxout 单元会计算 k 个线性变换&am…

nginx+keepalived负载均衡及高可用

1 项目背景 keepalived除了能够管理LVS软件外&#xff0c;还可以作为其他服务的高可用解决方案软件。采用nginxkeepalived&#xff0c;它是一个高性能的服务器高可用或者热备解决方案&#xff0c;Keepalived主要来防止服务器单点故障的发生问题&#xff0c;可以通过其与Nginx的…

VirtualBox虚拟机转VM虚拟机

前言&#xff1a;部分靶机只适用于VirtualBox&#xff0c;VM打不开VirtualBox的文件&#xff0c;所以需要进行转换 前置条件&#xff1a;本机已经下载VM和VirtualBox 第一步&#xff1a;文件转换 找到VirtualBox.exe所在位置&#xff0c;启动cmd窗口 文件转换的命令&#xf…

使用DeepSeek+KIMI生成高质量PPT

一、使用DeepSeek DeepSeek官网&#xff1a;DeepSeek 点击“开始对话”&#xff0c;进入交互页面。 在上图中&#xff0c;输入问题&#xff0c;即可获取AI生成的结果。 基础模型&#xff08;V3&#xff09;&#xff1a;通用模型&#xff08;2024.12&#xff09;&#xff0c;高…

基于SpringBoot的失物招领平台的设计与实现

基于SpringBoot的失物招领平台的设计与实现 基于微信小程序的失物招领系统 失物招领小程序 校园失物招领小程序 基于微信小程序SSMMySQL开发&#xff0c;高分JAVA成品毕业设计&#xff0c;附带往届论文、启动教程、讲解视频、二次开发教程和配套安装包文件&#xff0c;论文中…

鸿蒙NEXT开发-元服务和服务卡片的开发

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 目录 1. 元服务基本概念 1.1 基本介绍 1.2 元…

【Spark+Hive】基于Spark大数据技术小红书舆情分析可视化预测系统(完整系统源码+数据库+开发笔记+详细部署教程+虚拟机分布式启动教程)✅

目录 一、项目背景 二、项目目标 三、算法介绍 四、开发技术介绍 五、项目创新点 六、项目展示 七、权威教学视频 源码获取方式在文章末尾 一、项目背景 在数字经济蓬勃发展的当下&#xff0c;社交电商平台小红书凭借其"内容电商"的独特模式&#xff0c;已…

IO基础知识和练习

一、思维导图 二、练习 1.使用标准IO函数&#xff0c;实现文件的拷贝 #include <head.h> int main(int argc, const char *argv[]) {FILE *pfopen("./one.txt","r");FILE *fpfopen("./two.txt","r");if(pNULL)PRINT_ERROR(&qu…