Go + Redis 实现分布式锁

2023-10-31

一、前言

1.1 需要对交易订单加锁原因

开始本篇文章分享之前,先简单进行一下项目描述。该项目为一个中心化钱包。java接收到用户的以太坊转账请求后,调用后端golang服务的转账接口,将交易发送至链上。

如果golang服务处理交易后,正常返回交易哈希至java,说明该交易已发送至链上,后续检查交易哈希是否上链即可。如果因为网络等原因,java未收到golang返回的交易哈希,则认为该交易出现问题,java应将该交易置为待处理状态,java不应该继续发送该订单交易,而等待人工介入,排查具体原因。从而防止用户双花。

在与java层同事沟通以上规则后,java层认为:交易失败后,不会进行重试,但如果代码错误导致bug出现或者交易出现并发的情况下(发送交易时应使用队列,不然一定会出现交易并发情况),可能会进行多笔同样订单交易发送,所以需要在后端golang这里进行加锁,进行最后一次拦截。

1.2 加锁方案

  1. levelDB或mysql 持久化存储

因为golang支持使用levelDB进行key值存储,所以可将java的交易订单利用levelDB进行持久化存储。已经接收的交易订单不再进行二次处理,如果需要重新发起这笔交易,由java改变交易订单后,重新发送。

因为后面考虑golang服务需要部署多节点负载,多节点的levelDB的存储可以使用共享存储,但levelDB的特点是一次只允许一个进程访问一个特定的数据库。所以不能作为分布式存储。

  1. redis

使用redis存储key值,实现简单的分布式锁。使用此种方式的缺点是:

  • key值存在过期时间,如果key值失效后,java层依然进行交易重试,则依然会出现双花现象,所以必须要在key值失效前,人工介入排查处理。
  • redis库如果被Flush,则key值不存在,问题交易会被放通
  • redis服务宕机,无法获取key值,同样也会出现交易双花问题

二、Go + Redis 实现分布式锁

2.1 为什么需要分布式锁

  1. 用户下单

锁住 uid,防止重复下单。

  1. 库存扣减

锁住库存,防止超卖。

  1. 余额扣减

锁住账户,防止并发操作。

分布式系统中共享同一个资源时往往需要分布式锁来保证变更资源一致性。

2.2 分布式锁需要具备特性

  1. 排他性

锁的基本特性,并且只能被第一个持有者持有。

  1. 防死锁

高并发场景下临界资源一旦发生死锁非常难以排查,通常可以通过设置超时时间到期自动释放锁来规避。

3.可重入

锁持有者支持可重入,防止锁持有者再次重入时锁被超时释放。

4.高性能高可用

锁是代码运行的关键前置节点,一旦不可用则业务直接就报故障了。高并发场景下,高性能高可用是基本要求。

2.3 实现 Redis 锁应先掌握哪些知识点

  1. set 命令
SET key value [EX seconds] [PX milliseconds] [NX|XX]
  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
  • XX :只在键已经存在时,才对键进行设置操作。

先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。

  1. setnx 方法

可以先来看一下setnx方法:

func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd {
	var cmd *BoolCmd
	if expiration == 0 {
		// Use old `SETNX` to support old Redis versions.
		cmd = NewBoolCmd("setnx", key, value)
	} else {
		if usePrecise(expiration) {
			cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "nx")
		} else {
			cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx")
		}
	}
	c.process(cmd)
	return cmd
}

setnx的含义就是SET if Not Exists,该方法是原子的。如果key不存在,则设置当前key为value成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。

expire(key, seconds)
expire设置过期时间,要注意的是setnx命令不能设置key的超时时间,只能通过expire()来对key设置。

2.4 golang 连接redis

  • 下载reids包
go get github.com/go-redis/redis
  • golang连接redis
package redis

import (
	"github.com/go-redis/redis"
	"wallet/config"
	"wallet/pkg/logger"
)

// RedisDB Redis的DB对象
var RedisDB *redis.Client

func NewRedis() {  //创建redis连接
	RedisDB = redis.NewClient(&redis.Options{
		Addr:     config.Conf.Redis.Host,   //redis连接地址
		Password: config.Conf.Redis.Password,  //redis连接密码
		DB:       config.Conf.Redis.Database,  //redis连接库
	})

	defer func() {
		if r := recover(); r != nil {
			logger.Error("Redis connection error,", r)
		}
	}()
	_, err := RedisDB.Ping().Result()
	if err != nil {
		panic(err)
	}
	logger.Info("Redis connection successful!")
}
  • 项目启动时,应首先创建一个redis连接
func main() {
	//连接redis
	redis.NewRedis()
}

2.5 golang + redis实现分布式锁

  1. 利用redis的Set方法进行存key,简单实现一个加锁的方法
//判断当前订单是否已进行处理
isExist := redis.RedisDB.Exists(order)
//判断是否获取到key值,若获取到,则说明该交易订单已请求,向调用者返回报错
if isExist.Val() != 0 {
	apicommon.ReturnErrorResponse(ctx, 1, "Order transaction already exists, please check transaction", "")
	return
} else { //若未获取到,则说明暂未处理此笔交易订单,向redis中set此订单
	redis.RedisDB.Set(order, order, 86400*time.Second)
}
  1. 利用redis的setnx方法,实现分布式加锁
//判断当前订单是否已进行处理
bool := redis.RedisDB.SetNX(order, order, 24*time.Hour)
if bool.Val() { //SetNX只进行一次操作,若返回为true,则之前未处理该订单,此次已set该key
	logger.Info("The transaction order key value has been saved")
} else { //若返回false,则说明该交易订单已请求,向调用者返回报错
	logger.Error("The transaction order key value has been saved")
	apicommon.ReturnErrorResponse(ctx, 1, "Order transaction already exists, please check transaction", "")
	return
}

2.6 总结

通过代码和执行结果可以看到,我们远程调用 setnx 实际上和单机的 trylock 非常相似,如果获取锁失败,那么相关的任务逻辑就不应该继续向前执行。

setnx 很适合在高并发场景下,用来争抢一些“唯一”的资源。比如交易撮合系统中卖家发起订单,而多个买家会对其进行并发争抢。这种场景我们没有办法依赖具体的时间来判断先后,因为不管是用户设备的时间,还是分布式场景下的各台机器的时间,都是没有办法在合并后保证正确的时序的。哪怕是我们同一个机房的集群,不同的机器的系统时间可能也会有细微的差别。

所以,我们需要依赖于这些请求到达 redis 节点的顺序来做正确的抢锁操作。如果用户的网络环境比较差,那也只能自求多福了。

参考:

  • golang操作redis官方文档:https://pkg.go.dev/github.com/go-redis/redis#Client.Expire
  • golang分布式锁: https://books.studygolang.com/advanced-go-programming-book/ch6-cloud/ch6-01-lock.html
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Go + Redis 实现分布式锁 的相关文章

  • 使用Redis从有限范围内生成唯一ID

    我有一些数据库项目 除了主键之外 还需要项目所属组的唯一索引 我们来调用属性nbr 以及将项目分组在一起并定义唯一范围的属性nbr 我们会打电话group This nbr必须在 1 N 范围内 并且may从外部源导入项目时进行设置 由于所
  • 如何在Redis中进行持久化存储?

    关闭redis服务器后 使用set存储的值被破坏 在这里我找到了使用持久性存储的方法 有人帮助我 如何使用javascript实现这一点 我想将客户端的一些值存储在 redis 数据库中 并且必须在其他客户端中使用该值 您需要配置 Redi
  • Node Js:Redis 作业在完成其任务后未完成

    希望你们做得很好 我在我的 Nodejs 项目中实现了 BullMQ Bull 的下一个主要版本 来安排发送电子邮件的作业 例如 发送忘记密码请求的电子邮件 所以 我编写了如下所示的代码 用户服务 await resetPasswordJo
  • 如何在Redis中从hmset()切换到hset()?

    我收到弃用警告 即 Redis hmset 已弃用 请改用 Redis hset 但是 hset 采用第三个参数 我不知道是什么name应该是 info users 10 timestamp datetime utcnow strftime
  • Lua中按字符分割字符串

    我有像这样的字符串 ABC DEF 我需要将它们分开 字符并将两个部分分别分配给一个变量 在 Ruby 中 我会这样做 a b ABC DEF split 显然Lua没有这么简单的方法 经过一番挖掘后 我找不到一种简短的方法来实现我所追求的
  • Redis是如何实现高吞吐量和高性能的?

    我知道这是一个非常普遍的问题 但是 我想了解允许 Redis 或 MemCached Cassandra 等缓存 以惊人的性能极限工作的主要架构决策是什么 如何维持连接 连接是 TCP 还是 HTTP 我知道它完全是用C写的 内存是如何管理
  • StackExchange.Redis的正确使用方法

    这个想法是使用更少的连接和更好的性能 连接会随时过期吗 对于另一个问题 redis GetDatabase 打开新连接 private static ConnectionMultiplexer redis private static ID
  • redis 2.8.7 Linux Sentinel环境配置问题,如何使其自启动,应该订阅什么?

    现在我们尝试使用 redis 2 8 7 作为缓存存储 来自使用 booksleeve 客户端的 NET Web 应用程序 目前看来这是一个非常有趣和令人兴奋的任务 redis 文档非常好 但由于缺乏真正的实践经验 我确实有几个关于如何正确
  • Spring Redis删除不删除key

    我正在尝试删除一个 Redis 键 但由于某种原因它没有删除 但也没有抛出异常 这是我要删除的代码 import com example service CustomerService import com example model Cu
  • docker-compose:容器之间的 Redis 连接被拒绝

    我正在尝试设置一个 docker compose 文件 该文件旨在替换运行多个进程 RQ 工作线程 RQ 仪表板和 Flask 应用程序 的单个 Docker 容器解决方案导师 http supervisord org 主机系统是 Debi
  • 如何延长 django-redis 中的缓存 ttl(生存时间)?

    我正在使用 django 1 5 4 和 django redis 3 7 1 我想延长缓存的 ttl 生存时间 当我取回它时 这是示例代码 from django core cache import cache foo cache get
  • Spring Redis 排序键

    我在 Redis Spring Data Redis 中有以下键 localhost gt Keys 1 id 1 Name C5796 Site DRG1 2 id 2 Name CX1XE Site DG1 3 id 3 Name C5
  • Spring Data Redis 覆盖默认序列化器

    我正在尝试创建一个RedisTemplatebean 将具有更新的值序列化器来序列化对象JSONredis 中的格式 Configuration class RedisConfig Bean name redisTemplate Prima
  • 没有适用于机器人的 Laravel 会话

    我在大型 Laravel 项目和 Redis 存储方面遇到问题 我们将会话存储在 Redis 中 我们已经有 28GB 的 RAM 然而 它的运行速度仍然相对较快 达到了极限 因为我们有来自搜索引擎机器人的大量点击 每天超过 250 000
  • Web API 缓存 - 如何使用分布式缓存实现失效

    我有一个 API 目前不使用任何缓存 我确实有一个正在使用的中间件 它可以生成缓存标头 Cache Control Expires ETag Last Modified 使用https github com KevinDockx HttpC
  • 集合成员的 TTL

    Redis 是否可以不为特定键而是为集合的成员设置 TTL 生存时间 我正在使用 Redis 文档提出的标签结构 数据是简单的键值对 标签是包含与每个标签对应的键的集合 例如 gt SETEX id id 1 100 Lorem ipsum
  • 无法使用 ASP.NET 会话状态提供程序连接到 Redis 服务器

    一段时间以来 我一直在尝试用 Redis 替换 ASP NET Session 多个小时与适用于 Redis 的 Microsoft ASP NET 会话状态提供程序 http blogs msdn com b webdev archive
  • .NET Core 依赖注入中的“StackExchange.Redis.ConnectionMultiplexer”应该是“AddSingleton”还是“AddScope”?

    我正在使用以下命令将 Redis 连接添加到 NET CoreStackExchange Redis 目前看起来像这样 public static IServiceCollection AddRedisMultiplexer this IS
  • redis能完全取代mysql吗?

    简单的问题 我是否可以使用 redis 而不是 mysql 来处理各种 Web 应用程序 社交网络 地理位置服务等 IT 领域没有什么是不可能的 但有些事情可能会变得极其复杂 将键值存储用于全文搜索之类的事情可能会非常痛苦 另外 据我所知
  • Redis 客户端忽略其上设置的配置选项并尝试连接到默认 IP 127.0.01

    在AWS中 我使用ElastiCache Redis服务器并使用节点作为后端和 promise redis 包 这就是我尝试连接到我的 redis 服务器端点的方法 client redis createClient host my red

随机推荐

  • 计算机科学与因果关系,因果关系,概率和时间

    计算机 国外科技新书评介 2015年第2期 总第334期 Samantha Kleinberg Causality Probability and Time 2013 Hardback P ISBN978 1 07026483 CAMBRI
  • 使用Fiddler提高前端工作效率 (介绍篇)

    使用Fiddler提高前端工作效率 介绍篇 2010 04 18 by 前端 qhwa 评论 13 前端开发 摘自 http www aliued cn 2010 04 18 use fiddler to improve efficienc
  • APP UI自动化测试常见面试题,或许有用呢~

    1 Android APP 内存不足时 如何获得内存 系统优先结束被挂起 暂停 的进程 释放内存 2 APP 测试常见问题有哪些 原因有哪些 常见的有 crash ANR 应用无响应 卡死 一般由设备碎片化 网络波动大 内存泄漏 代码编写错
  • 华为OD机试 -最小叶子节点(Java)

    题目描述 二叉树也可以用数组来存储 给定一个数组 树的根节点的值储存在下标1 对于储存在下标n的节点 他的左子节点和右子节点分别储存在下标2n和2n 1 并且我们用 1代表一个节点为空 给定一个数组存储的二叉树 试求从根节点到最小的叶子节点
  • protoc 同时编译多个.protoc文件

    官方的示例 只是编译一个文件的命令行 protoc proto path IMPORT PATH cpp out DST DIR java out DST DIR python out DST DIR go out DST DIR ruby
  • Navicat16连接Oracle报错:Oracle library is not loaded

    1 有时候我们在用navicat的时候连接oracle的时候 它会提示我们Oracle library is not loaded 这时候我们要首先验证本机上是否已安装oracle的客户端 如果已安装客户段 navicat中的oci dll
  • 什么是MMU,MMU的作用

    I 什么是MMU MMU的作用 MMU是Memory Management Unit的缩写 针对各种CPU MMU是个可选的配件 MMU负责的是虚拟地址与物理地址的转换 提供硬件机制的内存访问授权 现在的多用户多进程操作系统 需要MMU 才
  • element ui菜单导航栏的动态创建

  • mysql的dba是什么意思_【数据库】mysql dba是什么意思?

    MySQL DBA意思是MySQL数据库管理员 DBA就是数据库管理员的意思 要成为MySQL DBA 不是会哪些东西的问题 而是一定要对MySQL数据库方方面面都得非常精通才行 DBA的职责是 安装和升级数据库服务器 以及应用程序工具 数
  • 前端关于单点登录SSO的知识

    转自前端关于单点登录的知识 什么是单点登录 单点登录 Single Sign On 简称为 SSO 是目前比较流行的企业业务整合的解决方案之一 SSO的定义是在多个应用系统中 用户只需要登录一次就可以访问所有相互信任的应用系统 SSO一般都
  • 前端 HTML空格的六种方式

    HTML提供了5种空格实体 space entity 它们拥有不同的宽度 非断行空格 是常规空格的宽度 可运行于所有主流浏览器 其他几种空格 在不同浏览器中宽度各异 它叫不换行空格 全称No Break Space 它是最常见和我们使用最多
  • Android项目混淆ProGuard详解

    关于混淆 可以借助工具proguardgui bat来了解或者写混淆文件 proguardgui bat是谷歌提供的可视化混淆文件编写工具 proguardgui bat位于android sdk 的tools proguard bin目录
  • python使用opencv对图像添加噪声(高斯/椒盐/泊松/斑点)

    导读 这篇文章主要介绍如何利用opencv来对图像添加各类噪声 原图 1 高斯噪声 高斯噪声就是给图片添加一个服从高斯分布的噪声 可以通过调节高斯分布标准差 sigma 的大小来控制添加噪声程度 sigma越大添加的噪声越多图片损坏的越厉害
  • 新华三数字化转型与实践 附下载地址

    随着数字技术 智能技术的持续发展 市场环境的不断变化 百行百业相继进入了数字化转型的深水区 这是时代发展的必然 更是建设繁荣数字经济的基础 因此 如何进行数字化转型便成为百行百业需要共同面对的问题 作为数字化解决方案领导者 新华三自身的数字
  • 【高频java面试题】JVM的底层结构

    1 问 说说JVM的底层结构 从左图可知 JVM主要包括四个部分 1 类加载器 ClassLoader 在JVM启动时或者在类运行时将需要的class加载到JVM中 右图表示了从java源文件到JVM的整个过程 可配合理解 关于类的加载机制
  • Python基础语法【5】—— 结构数据类型之元组

    文章目录 一 创建元组 1 使用 直接创建元组 2 使用tuple 函数创建元组 二 访问元组元素 1 使用索引方式访问 2 使用切片方式访问 三 修改元组元素 1 对元组重新进行赋值 2 使用 拼接元组 元组概念 元组和列表类似 也是由一
  • 历年计算机科学领域中各大顶会的获奖文章 ICCV、AAAI、CVPR...

    Original address https jeffhuang com best paper awards html By Conference AAAI ACL CHI CIKM CVPR FOCS FSE ICCV ICML ICSE
  • git submodule的使用

    转自 http webfrogs me 2013 03 20 git submodule 开发过程中 经常会有一些通用的部分希望抽取出来做成一个公共库来提供给别的工程来使用 而公共代码库的版本管理是个麻烦的事情 今天无意中发现了git的gi
  • 【sql基础】条件查询

    写在前面 作者简介 鲸海鹿林 博客主页 鲸海鹿林的主页 名言警句 keep calm and carry on SQL6 查找条件 是 例如 查找学生表中来自北京 city 的学生id和name select id name from st
  • Go + Redis 实现分布式锁

    文章目录 一 前言 1 1 需要对交易订单加锁原因 1 2 加锁方案 二 Go Redis 实现分布式锁 2 1 为什么需要分布式锁 2 2 分布式锁需要具备特性 2 3 实现 Redis 锁应先掌握哪些知识点 2 4 golang 连接r