go 分布式redis锁的实现方式

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的个数

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络安全等级保护2.0 vs GDPR vs NIST 2.0:全方位对比解析

在网络安全日益重要的今天,各国纷纷出台相关政策法规,以加强信息安全保护。本文将对比我国网络安全等级保护2.0、欧盟的GDPR以及美国的NIST 2.0,分析它们各自的特点及差异。 网络安全等级保护2.0 网络安全等级保护2.0是我国信息安全领域的一…

upload-labs靶场 1-21通关

目录 1.Pass-01 前端绕过 分析 解题 2.Pass-02 服务器端检测--修改IMME 分析 解题 3.Pass-03 黑名单绕过 分析 解题 4.Pass-04 .htaccess绕过 分析 解题 5.Pass-05 . .绕过和.user.ini绕过 分析 解题 6.Pass-06 大小写绕过 分析 解题 7.Pass-07 空格绕过 分…

CInternetToolbar::_CommonHandleFileSysChange函数分析之CReBar::_IDToIndex函数的作用

第一部分: // IMPORTANT: dont change the value of anything between CBIDX_FIRST and CBIDX_LAST. // CInternetToolbar::_LoadUpgradeSettings assumes these values havent changed from // version to version. #define CBIDX_MENU 1 …

为AI聊天工具添加一个知识系统 之138 设计重审 之2 文章学 引言之2 附加符号学附属诠释学附随工程学(联系)

本文要点 要点 符号学大局观: 诠释学(当代 加成[0]:“预期”和“预设” 两者的 不期而遇 。“邂逅”) 我们在文章学工具设计中 以全局观考虑:嵌入编程工具的逻辑性底( 哲学诠释 下确界) 并…

VEC系列-RabbitMQ 入门笔记

消息队列(MQ)对于开发者来说是一个经常听到的词汇,但在实际开发中,大多数人并不会真正用到它。网上已经有很多关于 MQ 概述和原理的详细讲解,官网文档和技术博客也都介绍得很深入,因此,我在这里…

大模型应用开发学习笔记

Huggingface 下载模型: model_dirr"G:\python_ws_g\code\LLMProject\session_4\day02_huggingface\transformers_test\model\uer\uer\gpt2-chinese-cluecorpussmall\models--uer--gpt2-chinese-cluecorpussmall\snapshots\c2c0249d8a2731f269414cc3b22dff021…

使用 Elasticsearch 进行集成测试初始化​​数据时的注意事项

作者:来自 Elastic piotrprz 在创建应该使用 Elasticsearch 进行搜索、数据聚合或 BM25/vector/search 的软件时,创建至少少量的集成测试至关重要。虽然 “模拟索引” 看起来很诱人,因为测试甚至可以在几分之一秒内运行,但它们实际…

高并发内存池 · 基本认识

目录 前言: 项目基础认识 内存碎片 效率问题 定长内存池 切内存 给谁切?怎么切? 怎么管理回收内存? 前言: 本文呢开始搞搞项目咯,于是准备从一个最经典的项目入手--tcmalloc,也就是从谷…

通用信息抽取大模型PP-UIE开源发布,强化零样本学习与长文本抽取能力,全面适配多场景任务

背景与简介 信息抽取(information extraction)是指,从非结构化或半结构化数据(如自然语言文本)中自动识别、提取并组织出结构化信息。通常包含多个子任务,例如:命名实体识别(NER&am…

游戏引擎学习第140天

回顾并为今天的内容做准备 目前代码的进展到了声音混音的部分。昨天我详细解释了声音的处理方式,声音在技术上是一个非常特别的存在,但在游戏中进行声音混音的需求其实相对简单明了,所以今天的任务应该不会太具挑战性。 今天我们会编写一个…

Goby 漏洞安全通告| Ollama /api/tags 未授权访问漏洞(CNVD-2025-04094)

漏洞名称:Ollama /api/tags 未授权访问漏洞(CNVD-2025-04094) English Name:Ollama /api/tags Unauthorized Access Vulnerability (CNVD-2025-04094) CVSS core: 6.5 风险等级: 中风险 漏洞描述: O…

Python----数据分析(Matplotlib五:pyplot的其他函数,Figure的其他函数, GridSpec)

一、pyplot的其他函数 1.1、xlabel 在matplotlib中, plt.xlabel() 函数用于为当前活动的坐标轴(Axes)设置x轴的 标签。当你想要标识x轴代表的数据或单位时,这个函数非常有用。 plt.xlabel(xlabel text) 1.2、ylabel 在matplotl…

构建python3.8的docker镜像,以便解决: dlopen: /lib64/libc.so.6: version `GLIBC_2.28‘

1、简介 在使用pyinstaller打包工具打包应用为二进制的时候,出现了一个“”: dlopen: /lib64/libc.so.6: version GLIBC_2.28”的问题 2、解决方案 2.1、问题原因 由于使用了官方提供的镜像,而官方提供的镜像编译的机器上、glibc的版本过高&#xff…

音频3A测试--AEC(回声消除)测试

一、测试前期准备 一台录制电脑:用于作为近段音源和收集远端处理后的数据; 一台测试设备B:用于测试AEC的设备; 一个高保真音响:用于播放设备B的讲话; 一台播放电脑:用于模拟设备A讲话,和模拟设备B讲话; 一台音频处理器(调音台):用于录制和播放数据; 测试使用转接线若…

MATLAB程序介绍,三维环境下的IMM(交互式多模型),使用CV和CT模型,EKF作为滤波

本文所述的MATLAB代码为三维的交互式多模型(IMM)滤波器,结合了匀速直线运动(CV模型)和匀速圆周运动(CT模型)的状态估计。使用扩展卡尔曼滤波(EKF)来处理状态更新与观测数…

upload-labs详解(1-12)文件上传分析

目录 uploa-labs-main upload-labs-main第一关 前端防御 绕过前端防御 禁用js Burpsuite抓包改包 upload-labs-main第二关 上传测试 错误类型 upload-labs-env upload-labs-env第三关 上传测试 查看源码 解决方法 重命名,上传 upload-labs-env第四关…

第一:goland安装

GOPROXY (会话临时性),长久的可以在配置文件中配置 go env -w GOPROXYhttps://goproxy.cn,direct 长久的,在~/.bashrc文件中添加: export GOPROXYhttps://goproxy.cn,direct ----&#xff0d…

ASP使用EFCore和AutoMapper添加导航属性数据

目录 一、不使用自增主键 (1)下载AutoMapper的nuget包 (2)配置映射规则 (3)配置MappingProfile文件 (4)控制器编写添加控制器 (5)测试 二、使用自增主…

什么是Jmeter? Jmeter工作原理是什么?

第一篇 什么是 JMeter?JMeter 工作原理 1.1 什么是 JMeter Apache JMeter 是 Apache 组织开发的基于 Java 的压力测试工具。用于对软件做压力测试,它最初被设计用于 Web 应用测试,但后来扩展到其他测试领域。 它可以用于测试静态和动态资源…

汽车零部件厂如何选择最适合的安灯系统解决方案

在现代制造业中,安灯系统作为一种重要的生产管理工具,能够有效提升生产线的异常处理效率,确保生产过程的顺畅进行。对于汽车零部件厂来说,选择一套适合自身生产需求的安灯系统解决方案尤为重要。 一、安灯系统的核心功能 安灯系统…