文章目录

  • 一、前言
    • 1.1 需要对交易订单加锁原因
    • 1.2 加锁方案
  • 二、Go + Redis 实现分布式锁
    • 2.1 为什么需要分布式锁
    • 2.2 分布式锁需要具备特性
    • 2.3 实现 Redis 锁应先掌握哪些知识点
    • 2.4 golang 连接redis
    • 2.5 golang + redis实现分布式锁
    • 2.6 总结

一、前言

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 *BoolCmdif 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 redisimport ("github.com/go-redis/redis""wallet/config""wallet/pkg/logger"
)// RedisDB Redis的DB对象
var RedisDB *redis.Clientfunc 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() {//连接redisredis.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该keylogger.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

Go + Redis 实现分布式锁相关推荐

  1. 基于 Redis 实现分布式锁思考

    以下文章来源方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/xuan_lu/article/details/111600302 分布式锁 基于redis实 ...

  2. Redis实现分布式锁的深入探究

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.分布式锁简介 锁 是一种用来解决多个执行线程 访问共享资源 错 ...

  3. nx set 怎么实现的原子性_基于Redis的分布式锁实现

    前言 本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结 分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问 ...

  4. Zookeeper和Redis实现分布式锁,附我的可靠性分析

    作者:今天你敲代码了吗 链接:https://www.jianshu.com/p/b6953745e341 在分布式系统中,为保证同一时间只有一个客户端可以对共享资源进行操作,需要对共享资源加锁来实现 ...

  5. Redis——由分布式锁造成的重大事故

    作者:浪漫先生 原文:juejin.im/post/6854573212831842311 前言 基于Redis使用分布式锁在当今已经不是什么新鲜事了.本篇文章主要是基于我们实际项目中因为redis分 ...

  6. 基于Redis的分布式锁和Redlock算法

    来自:后端技术指南针 1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手 ...

  7. 《Redis官方文档》用Redis构建分布式锁

    <Redis官方文档>用Redis构建分布式锁 用Redis构建分布式锁 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现 ...

  8. 《Redis官方文档》用Redis构建分布式锁(悲观锁)

    2019独角兽企业重金招聘Python工程师标准>>> **用Redis构建分布式锁 ** 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章 ...

  9. redis 实现分布式锁

    为什么80%的码农都做不了架构师?>>>    redis 实现分布式锁 伪代码 lock(){if(jedis.setNx("key",timestamp)){ ...

  10. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

最新文章

  1. w7计算机屏幕保护设置方法,W7屏保怎么设置
  2. 求1-10000所有的素数
  3. 把strassen乘法调出来了...
  4. NuGet是什么?理解与使用(下)
  5. 容器编排技术 -- Kubernetes 为 Namespace 设置最小和最大内存限制
  6. 没有样式重置_我不能没有的5个Vue.js库
  7. 1命名规则 sentinel_Sentinel实战:为系统做限流保护
  8. dot全称_游戏dot是什么
  9. 云计算商家必争之地 推荐几款云平台
  10. js 利用数组队列模拟多线程操作
  11. Spring boot学习(七)Spring boot配置slf4j+logback日志框架
  12. 2020-12-04
  13. 机器学习防止模型过拟合的方法知识汇总
  14. HTML实现W3school导航栏(附带重置样式表reset.css)
  15. 一个Web前端实习生的简历
  16. 毕设:后台管理系统基础模板
  17. 14、文件的上传和下载
  18. linux怎么调显卡风扇速度,Ubuntu下无显示器(服务器)手动调节Nvidia显卡风扇转速...
  19. python tkinter怎么读_Python初学 Tkinter
  20. 英伟达3090Ti旗舰显卡,21Gbps速率,450W功耗和新接口

热门文章

  1. 2000门课程名称翻译大全,写英文简历时用得着的
  2. iOS-高德地图点击地图获取点击点对应的地理位置,并添加自定义的大头针
  3. 其实你对三维设计一点也不陌生,这些竟然都属于它!
  4. EM算法·最大期望算法
  5. uni-app通过wifi连接设备并进行命令交互
  6. 抖音直播新号怎么引流,抖音直播间起号如何渡过冷启动期?
  7. 世界上最著名也最危险的APT恶意软件清单
  8. C++中函数的重载,重写,重定义
  9. 汉中至巴中至南充铁路(汉巴南线)顺利开通
  10. 樊登读书会极致读后感_【学习部落】樊登读书会可复制的领导力感悟