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

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

2,插入演示数据:

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

四,go代码说明

1,controller/goodsController.go

  1.  
    package controller
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    "github.com/liuhongdi/digv23/global"
  6.  
    "github.com/liuhongdi/digv23/service"
  7.  
    )
  8.  
     
  9.  
    type GoodsController struct{}
  10.  
    func NewGoodsController() GoodsController {
  11.  
    return GoodsController{}
  12.  
    }
  13.  
    //购买一件商品
  14.  
    func (g *GoodsController) BuyOne(c *gin.Context) {
  15.  
    result := global.NewResult(c)
  16.  
     
  17.  
    var goodsId int64 = 1
  18.  
    buyNum :=1
  19.  
    err := service.BuyOneGoods(goodsId,buyNum);
  20.  
    if err != nil {
  21.  
    result.Error(404,"数据查询错误")
  22.  
    } else {
  23.  
    result.Success("减库存成功");
  24.  
    }
  25.  
    return
  26.  
    }
  27.  
    //购买一件商品,by lock
  28.  
    func (g *GoodsController) LockBuyOne(c *gin.Context) {
  29.  
    result := global.NewResult(c)
  30.  
     
  31.  
    var goodsId int64 = 1
  32.  
    buyNum :=1
  33.  
    err := service.LockBuyOneGoods(goodsId,buyNum);
  34.  
    if err != nil {
  35.  
    result.Error(404,"数据查询错误")
  36.  
    } else {
  37.  
    result.Success("减库存成功");
  38.  
    }
  39.  
    return
  40.  
    }

2,dao/goods.go

  1.  
    package dao
  2.  
     
  3.  
    import (
  4.  
    "errors"
  5.  
    "fmt"
  6.  
    "github.com/liuhongdi/digv23/global"
  7.  
    "github.com/liuhongdi/digv23/model"
  8.  
    "gorm.io/gorm"
  9.  
    )
  10.  
     
  11.  
    //decrease stock
  12.  
    func DecreaseOneGoodsStock(goodsId int64,buyNum int) error {
  13.  
    //查询商品信息
  14.  
    goodsOne:=&model.Goods{}
  15.  
    err := global.DBLink.Where("goodsId=?",goodsId).First(&goodsOne).Error
  16.  
    //fmt.Println(goodsOne)
  17.  
    if (err != nil) {
  18.  
    return err
  19.  
    }
  20.  
    //得到库存
  21.  
    stock := goodsOne.Stock
  22.  
    fmt.Println("当前库存:",stock)
  23.  
    //fmt.Println(stock)
  24.  
    if (stock < buyNum || stock <= 0) {
  25.  
    return errors.New("库存不足")
  26.  
    }
  27.  
     
  28.  
    //减库存
  29.  
    result := global.DBLink.Debug().Table("goods").Where("goodsId = ? ", goodsId,buyNum).Update("stock", gorm.Expr("stock - ?", buyNum))
  30.  
    if (result.Error != nil) {
  31.  
    return result.Error
  32.  
    } else {
  33.  
    fmt.Println("成功减库存一次")
  34.  
    return nil
  35.  
    }
  36.  
    }

3,service/goods.go

  1.  
    package service
  2.  
     
  3.  
    import (
  4.  
    "github.com/liuhongdi/digv23/dao"
  5.  
    "github.com/liuhongdi/digv23/global"
  6.  
    "strconv"
  7.  
    "github.com/go-redsync/redsync/v4"
  8.  
    "github.com/go-redsync/redsync/v4/redis/goredis/v8"
  9.  
    )
  10.  
     
  11.  
    //购买一件商品
  12.  
    func BuyOneGoods(goodsId int64,buyNum int) error {
  13.  
    return dao.DecreaseOneGoodsStock(goodsId,buyNum);
  14.  
    }
  15.  
     
  16.  
    //购买一件商品,by lock
  17.  
    func LockBuyOneGoods(goodsId int64,buyNum int) error {
  18.  
     
  19.  
    pool := goredis.NewPool(global.RedisDb) // or, pool := redigo.NewPool(...)
  20.  
    // Create an instance of redisync to be used to obtain a mutual exclusion
  21.  
    // lock.
  22.  
    rs := redsync.New(pool)
  23.  
    // Obtain a new mutex by using the same name for all instances wanting the
  24.  
    // same lock.
  25.  
    mutexname := "goods_"+strconv.FormatInt(goodsId,10)
  26.  
    mutex := rs.NewMutex(mutexname)
  27.  
    // Obtain a lock for our given mutex. After this is successful, no one else
  28.  
    // can obtain the same lock (the same mutex name) until we unlock it.
  29.  
    if err := mutex.Lock(); err != nil {
  30.  
    return err
  31.  
    }
  32.  
    // Do your work that requires the lock.
  33.  
    errdecre := dao.DecreaseOneGoodsStock(goodsId,buyNum);
  34.  
    //fmt.Println(errdecre)
  35.  
     
  36.  
    // Release the lock so other processes or threads can obtain a lock.
  37.  
    if ok, err := mutex.Unlock(); !ok || err != nil {
  38.  
    return err
  39.  
    }
  40.  
     
  41.  
    if (errdecre!=nil){
  42.  
    return errdecre
  43.  
    }
  44.  
     
  45.  
    return nil
  46.  
    }

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

五,测试效果

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

2,测试不加锁的访问:

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

查看控制台的输出:

  1.  
    当前库存: 3
  2.  
     
  3.  
    2021/01/21 12:22:17 /data/liuhongdi/digv23/dao/goods.go:29
  4.  
    [1.681ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  5.  
    成功减库存一次
  6.  
    当前库存: 2
  7.  
    当前库存: 2
  8.  
    当前库存: 2
  9.  
    当前库存: 2
  10.  
    当前库存: 2
  11.  
    当前库存: 2
  12.  
    当前库存: 2
  13.  
    当前库存: 2
  14.  
    当前库存: 2
  15.  
    当前库存: 2
  16.  
    当前库存: 2
  17.  
    当前库存: 2
  18.  
    当前库存: 2
  19.  
    当前库存: 2
  20.  
    当前库存: 2
  21.  
    当前库存: 2
  22.  
    当前库存: 2
  23.  
    当前库存: 2
  24.  
    当前库存: 2
  25.  
    当前库存: 2
  26.  
    当前库存: 2
  27.  
    当前库存: 2
  28.  
    当前库存: 2
  29.  
    当前库存: 2
  30.  
    当前库存: 2
  31.  
    当前库存: 2
  32.  
    当前库存: 2
  33.  
    当前库存: 2
  34.  
    当前库存: 2
  35.  
    当前库存: 2
  36.  
    当前库存: 2
  37.  
    当前库存: 2
  38.  
    当前库存: 2
  39.  
    当前库存: 2
  40.  
    当前库存: 2
  41.  
    当前库存: 2
  42.  
    当前库存: 2
  43.  
    当前库存: 2
  44.  
    当前库存: 2
  45.  
    当前库存: 2
  46.  
    当前库存: 2
  47.  
    当前库存: 2
  48.  
    当前库存: 2
  49.  
    当前库存: 2
  50.  
    当前库存: 2
  51.  
    当前库存: 2
  52.  
    当前库存: 2
  53.  
    当前库存: 2
  54.  
    当前库存: 2
  55.  
    当前库存: 2
  56.  
    当前库存: 2
  57.  
    当前库存: 2
  58.  
    当前库存: 2
  59.  
    当前库存: 2
  60.  
    当前库存: 2
  61.  
    当前库存: 2
  62.  
    当前库存: 2
  63.  
    当前库存: 2
  64.  
     
  65.  
    2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
  66.  
    [17.357ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  67.  
    成功减库存一次
  68.  
    当前库存: 2
  69.  
    当前库存: 1
  70.  
    当前库存: 1
  71.  
    当前库存: 1
  72.  
    当前库存: 1
  73.  
    当前库存: 1
  74.  
    当前库存: 1
  75.  
     
  76.  
    2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
  77.  
    [39.838ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  78.  
    成功减库存一次
  79.  
    当前库存: 0
  80.  
    当前库存: 0
  81.  
     
  82.  
    2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
  83.  
    [85.284ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  84.  
    成功减库存一次
  85.  
    当前库存: 0
  86.  
    当前库存: -1
  87.  
    当前库存: -1
  88.  
    当前库存: -1
  89.  
    当前库存: 0
  90.  
    当前库存: -1
  91.  
    当前库存: -1
  92.  
     
  93.  
    2021/01/21 12:22:18 /data/liuhongdi/digv23/dao/goods.go:29
  94.  
    [95.104ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  95.  
    成功减库存一次
  96.  
    当前库存: -2
  97.  
    ....

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

3,把库存数重置为3,

   测试加锁的减库存:

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

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

  1.  
    DecreaseOneGoodsStock begin
  2.  
    当前库存: 3
  3.  
     
  4.  
    2021/01/21 12:44:16 /data/liuhongdi/digv23/dao/goods.go:30
  5.  
    [2.115ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  6.  
    成功减库存一次
  7.  
    DecreaseOneGoodsStock begin
  8.  
    当前库存: 2
  9.  
     
  10.  
    2021/01/21 12:44:16 /data/liuhongdi/digv23/dao/goods.go:30
  11.  
    [18.724ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  12.  
    成功减库存一次
  13.  
    DecreaseOneGoodsStock begin
  14.  
    当前库存: 1
  15.  
     
  16.  
    2021/01/21 12:44:16 /data/liuhongdi/digv23/dao/goods.go:30
  17.  
    [2.782ms] [rows:1] UPDATE `goods` SET `stock`=stock - 1 WHERE goodsId = 1
  18.  
    成功减库存一次
  19.  
    DecreaseOneGoodsStock begin
  20.  
    当前库存: 0
  21.  
    DecreaseOneGoodsStock begin
  22.  
    当前库存: 0
  23.  
    DecreaseOneGoodsStock begin
  24.  
    当前库存: 0
  25.  
    DecreaseOneGoodsStock begin
  26.  
    .....

注意库存数的返回,

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

 

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

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

 

六,查看库的版本:

  1.  
    module github.com/liuhongdi/digv23
  2.  
     
  3.  
    go 1.15
  4.  
     
  5.  
    require (
  6.  
    github.com/gin-gonic/gin v1.6.3
  7.  
    github.com/go-redis/redis/v8 v8.3.3
  8.  
    gorm.io/driver/mysql v1.0.1
  9.  
    gorm.io/gorm v1.20.6
  10.  
    github.com/go-redsync/redsync/v4 v4.0.3
  11.  
    )
  12.  

posted on 2021-01-22 10:53  ExplorerMan  阅读(1582)  评论(0编辑  收藏  举报

导航