go如何利用orm简单实现接口分布式锁

 更新时间:2018年07月23日 09:36:06   投稿:zx  
本篇文章主要介绍了go如何利用orm简单实现接口分布式锁,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

在开发中有些敏感接口,例如用户余额提现接口,需要考虑在并发情况下接口是否会发生问题。如果用户将自己的多条提现请求同时发送到服务器,代码能否扛得住呢?一旦没做锁,那么就真的会给用户多次提现,给公司带来损失。我来简单介绍一下在这种接口开发过程中,我的做法。

第一阶段:

我们使用的orm为xorm,提现表对应的结构体如下

type Participating struct {
 ID   uint     `xorm:"autoincr id" json:"id,omitempty"`
 Openid string    `xorm:"openid" json:"openid"`
 Hit   uint     `xorm:"hit" json:"hit"`
 Orderid string    `xorm:"order_id" json:"order_id"`
 Redpack uint     `xorm:"redpack" json:"redpack"`
 Status uint     `xorm:"status" json:"status"`
 Ctime  tool.JsonTime `xorm:"ctime" json:"ctime,omitempty"`
 Utime  tool.JsonTime `xorm:"utime" json:"utime,omitempty"`
 PayTime tool.JsonTime `xorm:"pay_time" json:"pay_time,omitempty"`
}

在Participating表中,是以Openid去重的,当一个Openid对应的Hit为1时,可以按照Redpack的数额提现,成功后将Status改为1,简单来说这就是提现接口的业务逻辑。

起初我并没有太在意并发的问题,我在MySQL的提现表中设置一个字段status来记录提现状态,我只是在提现时将状态修改为2(体现中),提现完成后将status修改为1(已提现)。然后事实证明,我太天真了,用ab做了测试1s发送了1000个请求到服务器,结果。。。成功提现了6次。部分代码如下

p_info := &Participating{}
// 查找具体提现数额
has, _ := db.Dalmore.Where("openid = ? and hit = 1 and status = 0", openid).Get(p_info)
if !has {
 resp.Error(errcode.NO_REDPACK_FOUND, nil, nil)
 return
}

// 改status为提现中
p_info.Status = 2
db.Dalmore.Cols("status").Where("openid = ? and hit = 1 and status = 0", openid).Update(p_info)

// 提现p_info.Redpack

第二阶段:

既然出现了并发问题,那第一反应肯定的加锁啊,代码如下:

type Set struct {
 m map[string]bool
 sync.RWMutex
}

func New() *Set {
 return &Set{
 m: map[string]bool{},
 }
}

var nodelock = set.New()

// 加锁
nodelock.Lock()

p_info := &Participating{}
// 查找具体提现数额
has, _ := db.Dalmore.Where("openid = ? and hit = 1 and status = 0", openid).Get(p_info)
if !has {
 resp.Error(errcode.NO_REDPACK_FOUND, nil, nil)
 return
}

// 改status为提现中
p_info.Status = 2
db.Dalmore.Cols("status").Where("openid = ? and hit = 1 and status = 0", openid).Update(p_info)

// 释放锁
nodelock.Unlock()

// 提现p_info.Redpack

加了锁以后。。。emem,允许多次提现的问题解决了,但是这个锁限制的范围太多了,直接让这段加锁代码变成串行,这大大降低了接口性能。而且,一旦部署多个服务端,这个锁又会出现多次提现的问题,因为他只能拦住这一个服务的并发。看来得搞一个不影响性能的分布式才是王道啊。

第三阶段:

利用redis,设置一个key为openid的分布式锁,并设置一个过期时间可以解决当前的这个问题。但是难道就没别的办法了吗?当然是有的,golang的xorm中Update函数其实是有返回值的:num,err,我就是利用num做了个分布式锁。

//记录update修改条数
num, err := db.Dalmore.Cols("status").Where("openid = ? and status = 0 and hit = 1", openid).Update(p_update)
if err != nil {
 logger.Runtime().Debug(map[string]interface{}{"error": err.Error()}, "error while updating")
 resp.Error(errcode.INTERNAL_ERROR, nil, nil)
 return
}

// 查看update操作到底修改了多少条数据,起到了分布式锁的作用
if num != 1 {
 resp.Error(errcode.NO_REDPACK_FOUND, nil, nil)
 return
}

p_info := &Participating{}
_, err := db.Dalmore.Where("openid = ? and status = 2", openid).Get(p_info)
if err != nil {
 logger.Runtime().Debug(map[string]interface{}{"error": err.Error()}, "error while selecting")
 resp.Error(errcode.INTERNAL_ERROR, nil, nil)
 return
}

// 提现p_info.Redpack

其实有点投机取巧的意思,利用xorm的Update函数,我们将核对并发处理请求下数据准确性的问题抛给了MySQL,毕竟MySQL是经过千锤百炼的。再用ab测试,嗯,锁成功了只有,只提现了一次,大功告成~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Golang并发编程深入分析

    Golang并发编程深入分析

    golang中的并发,是函数相互独立运行的能力,goroutines是并发运行的函数。golang提供了goroutines作为并发处理的一种方式
    2022-11-11
  • go语言中json处理方式详解

    go语言中json处理方式详解

    这篇文章主要介绍了go语言中json处理方式,文中通过实例代码讲解的非常详细,对大家的学习或工作有一定的帮助,感兴趣的小伙伴跟着小编一起来看看吧
    2024-05-05
  • Golang HTTP服务超时控制实现原理分析

    Golang HTTP服务超时控制实现原理分析

    这篇文章主要介绍了Golang HTTP服务超时控制实现原理,HTTP服务的超时控制是保障服务高可用性的重要措施之一,由于HTTP服务可能会遇到网络延迟,资源瓶颈等问题,因此需要对请求进行超时控制,以避免服务雪崩等问题,需要的朋友可以参考下
    2023-05-05
  • Go语言时间处理必备技巧全解析

    Go语言时间处理必备技巧全解析

    Golang 的时间处理是 Golang 编程中的一个重要方面,它涉及到了时间类型、时间格式化、时间计算、时区处理以及定时器和超时机制等多个方面。在本文中,我们将从更深入的角度来探讨 Golang 的时间处理
    2023-04-04
  • go+redis实现消息队列发布与订阅的详细过程

    go+redis实现消息队列发布与订阅的详细过程

    这篇文章主要介绍了go+redis实现消息队列发布与订阅,redis做消息队列的缺点:没有持久化,一旦消息没有人消费,积累到一定程度后就会丢失,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • golang通过http访问外部网址的操作方法

    golang通过http访问外部网址的操作方法

    这篇文章主要介绍了golang通过http访问外部网址的操作方法,分为 get方式访问外部的接口,Post方式请求外部接口,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • golang 后台进程的启动和停止操作

    golang 后台进程的启动和停止操作

    这篇文章主要介绍了golang 后台进程的启动和停止操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • 解读rand.Seed(time.Now().UnixNano())的作用及说明

    解读rand.Seed(time.Now().UnixNano())的作用及说明

    这篇文章主要介绍了关于rand.Seed(time.Now().UnixNano())的作用及说明,具有很好的参考价值,希望对大家有所帮助。
    2023-03-03
  • Go timer如何调度

    Go timer如何调度

    本篇文章剖析下 Go 定时器的相关内容。定时器不管是业务开发,还是基础架构开发,都是绕不过去的存在,由此可见定时器的重要程度,感兴趣的可以了解一下
    2021-06-06
  • 10个现代网站开发必备的Go软件包工具盘点

    10个现代网站开发必备的Go软件包工具盘点

    这篇文章主要为大家介绍了10个现代网站开发必备的Go软件包,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10

最新评论