go语言web开发系列之二十三:gin框架用go-redis+redsync实现分布式锁 - Go语言中文社区

go语言web开发系列之二十三:gin框架用go-redis+redsync实现分布式锁


一,安装需要用到的库

1,go-redis的地址:

https://github.com/go-redis/redis

2,安装go-redis

liuhongdi@ku:~$ go get -u github.com/go-redis/redis/v8

3,redsync的地址

https://github.com/go-redsync/redsync

4,安装redsync

liuhongdi@ku:~$ go get -u github.com/go-redsync/redsync/v4

5,gorm的地址

https://gorm.io/

6,安装gorm

liuhongdi@ku:~$ go get -u gorm.io/gorm

说明:刘宏缔的go森林是一个专注golang的博客,
          地址:https://blog.csdn.net/weixin_43881017

说明:作者:刘宏缔 邮箱: 371125307@qq.com

 

二,演示项目的相关信息

1,  地址:

https://github.com/liuhongdi/digv23

2,功能说明:演示了使用分布式锁避免高并发下单减库存时多扣库存

3,  项目结构;如图:

三,数据库的sql

1,建表sql

CREATE TABLE `goods` (
`goodsId` int NOT NULL AUTO_INCREMENT COMMENT 'id',
`goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '商品名称',
`subject` varchar(200) NOT NULL DEFAULT '' COMMENT '标题',
`price` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`stock` int NOT NULL DEFAULT '0' COMMENT '库存数量',
PRIMARY KEY (`goodsId`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表'

2,插入演示数据:

INSERT INTO `goods` (`goodsId`, `goodsName`, `subject`, `price`, `stock`) VALUES
(1, '蜂蜜牛奶手工皂', '深入滋养,肌肤细腻嫩滑', '70.00', 3),
(2, '紫光筷子筒', '紫光智护,干爽防潮更健康', '189.00', 40),
(3, '野性mini便携式蓝牙音箱', '强悍机能,品味豪迈', '499.00', 100),
(4, '乐穿梭茶具', '茶具+茶叶精美端午礼盒', '200.00', 40);

四,go代码说明

1,controller/goodsController.go

package controller

import (
	"github.com/gin-gonic/gin"
	"github.com/liuhongdi/digv23/global"
	"github.com/liuhongdi/digv23/service"
)

type GoodsController struct{}
func NewGoodsController() GoodsController {
	return GoodsController{}
}
//购买一件商品
func (g *GoodsController) BuyOne(c *gin.Context) {
	result := global.NewResult(c)

    var goodsId int64 = 1
    buyNum :=1
	err := service.BuyOneGoods(goodsId,buyNum);
	if err != nil {
		result.Error(404,"数据查询错误")
	} else {
		result.Success("减库存成功");
	}
	return
}
//购买一件商品,by lock
func (g *GoodsController) LockBuyOne(c *gin.Context) {
	result := global.NewResult(c)

	var goodsId int64 = 1
	buyNum :=1
	err := service.LockBuyOneGoods(goodsId,buyNum);
	if err != nil {
		result.Error(404,"数据查询错误")
	} else {
		result.Success("减库存成功");
	}
	return
}

2,dao/goods.go

package dao

import (
	"errors"
	"fmt"
	"github.com/liuhongdi/digv23/global"
	"github.com/liuhongdi/digv23/model"
	"gorm.io/gorm"
)

//decrease stock
func DecreaseOneGoodsStock(goodsId int64,buyNum int) error {
    //查询商品信息
	goodsOne:=&model.Goods{}
	err := global.DBLink.Where("goodsId=?",goodsId).First(&goodsOne).Error
	//fmt.Println(goodsOne)
	if (err != nil) {
		return err
	}
	//得到库存
	stock := goodsOne.Stock
	fmt.Println("当前库存:",stock)
    //fmt.Println(stock)
	if (stock < buyNum || stock <= 0) {
		return errors.New("库存不足")
	}

	//减库存
	result := global.DBLink.Debug().Table("goods").Where("goodsId = ? ", goodsId,buyNum).Update("stock", gorm.Expr("stock - ?", buyNum))
	if (result.Error != nil) {
		return result.Error
	} else {
		fmt.Println("成功减库存一次")
		return nil
	}
}

3,service/goods.go

package service

import (
    "github.com/liuhongdi/digv23/dao"
    "github.com/liuhongdi/digv23/global"
    "strconv"
    "github.com/go-redsync/redsync/v4"
    "github.com/go-redsync/redsync/v4/redis/goredis/v8"
)

//购买一件商品
func BuyOneGoods(goodsId int64,buyNum int) error {
    return	dao.DecreaseOneGoodsStock(goodsId,buyNum);
}

//购买一件商品,by lock
func LockBuyOneGoods(goodsId int64,buyNum int) error {

    pool := goredis.NewPool(global.RedisDb) // or, pool := redigo.NewPool(...)
    // Create an instance of redisync to be used to obtain a mutual exclusion
    // lock.
    rs := redsync.New(pool)
    // Obtain a new mutex by using the same name for all instances wanting the
    // same lock.
    mutexname := "goods_"+strconv.FormatInt(goodsId,10)
    mutex := rs.NewMutex(mutexname)
    // Obtain a lock for our given mutex. After this is successful, no one else
    // can obtain the same lock (the same mutex name) until we unlock it.
    if err := mutex.Lock(); err != nil {
        return err
    } 
    // Do your work that requires the lock.
    errdecre := dao.DecreaseOneGoodsStock(goodsId,buyNum);
    //fmt.Println(errdecre)
    
    // Release the lock so other processes or threads can obtain a lock.
    if ok, err := mutex.Unlock(); !ok || err != nil {
        return err
    }

    if (errdecre!=nil){
        return errdecre
    }
    
    return nil
}

4,其他相关代码可访问github

五,测试效果

1,设置id为1的商品库存为3

2,测试不加锁的访问:

liuhongdi@ku:~$ ab -c 100 -n 100 http://127.0.0.1:8080/goods/buyone

查看控制台的输出:

当前库存: 3

2021/01/21 12:22:17 /data/liuhongdi/digv23/dao/goods.go:29
[1.681ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2
当前库存: 2

2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
[17.357ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
当前库存: 2
当前库存: 1
当前库存: 1
当前库存: 1
当前库存: 1
当前库存: 1
当前库存: 1

2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
[39.838ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
当前库存: 0
当前库存: 0

2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
[85.284ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
当前库存: 0
当前库存: -1
当前库存: -1
当前库存: -1
当前库存: 0
当前库存: -1
当前库存: -1

2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
[95.104ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
当前库存: -2
....

注意因为是并发的访问,数据库同时返回了多个结果:库存是2,
导致后面的多个并发执行减库存,使库存数出现负数

3,把库存数重置为3,

   测试加锁的减库存:

liuhongdi@ku:~$ ab -c 100 -n 100 http://127.0.0.1:8080/goods/lockbuyone

执行会比较慢,因为每个访问都需要先获得锁之后再执行sql

DecreaseOneGoodsStock begin
当前库存: 3

2021/01/21 12:44:16 /data/liuhongdi/digv23/dao/goods.go:30
[2.115ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
DecreaseOneGoodsStock begin
当前库存: 2

2021/01/21 12:44:16 /data/liuhongdi/digv23/dao/goods.go:30
[18.724ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
DecreaseOneGoodsStock begin
当前库存: 1

2021/01/21 12:44:16 /data/liuhongdi/digv23/dao/goods.go:30
[2.782ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1 
成功减库存一次
DecreaseOneGoodsStock begin
当前库存: 0
DecreaseOneGoodsStock begin
当前库存: 0
DecreaseOneGoodsStock begin
当前库存: 0
DecreaseOneGoodsStock begin
.....

注意库存数的返回,

因为获取锁之后才查询,所以没有同时返回多个相同数字以致减库存成负数的情况

 

4,查看redis中的key,注意因为redis的切换很快,不一定可以看到:

root@ku:/data/liuhongdi/digv23# /usr/local/soft/redis6/bin/redis-cli
127.0.0.1:6379> keys *
1) "goods_1"

 

六,查看库的版本:

module github.com/liuhongdi/digv23

go 1.15

require (
	github.com/gin-gonic/gin v1.6.3
	github.com/go-redis/redis/v8 v8.3.3
	gorm.io/driver/mysql v1.0.1
	gorm.io/gorm v1.20.6
	github.com/go-redsync/redsync/v4 v4.0.3
)

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_43881017/article/details/112919597
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-19 08:40:43
  • 阅读 ( 2013 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢