zoukankan      html  css  js  c++  java
  • 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.  
      )
  • 相关阅读:
    LOJ.114.K大异或和(线性基)
    BZOJ.2115.[WC2011]Xor(线性基)
    BZOJ.2460.[BeiJing2011]元素(线性基 贪心)
    Codeforces Round #494 (Div 3) (A~E)
    Codeforces Round #493 (Div 2) (A~E)
    BZOJ.3238.[AHOI2013]差异(后缀自动机 树形DP/后缀数组 单调栈)
    BZOJ.4180.字符串计数(后缀自动机 二分 矩阵快速幂/倍增Floyd)
    BZOJ.1396.识别子串(后缀自动机/后缀数组 线段树)
    BZOJ.3489.A simple rmq problem(主席树 Heap)
    BZOJ.4566.[HAOI2016]找相同字符(后缀自动机)
  • 原文地址:https://www.cnblogs.com/ExMan/p/14312078.html
Copyright © 2011-2022 走看看