一,安裝需要用到的庫
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的地址
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
-
-
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
-
)
-