Go 库存扣减的几种实现

本文最后更新于:2 年前

Go 库存扣减的几种实现方法

Go Mutex 实现

这里使用了 grpc、proto、gorm、zap、go-redis、go-redsync 等 package

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var m sync.Mutex
func (*InventoryServer) LockSell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
tx := global.DB.Begin()

m.Lock()

for _, good := range req.GoodsInfo {
var i model.Inventory
if result := global.DB.Where(&model.Inventory{Goods: good.GoodsId}).First(&i);
result.RowsAffected == 0{

tx.Rollback() // 回滚
return nil, status.Errorf(codes.InvalidArgument, "未找到此商品的库存信息。")
}

if i.Stocks < good.Num {
tx.Rollback()
return nil, status.Errorf(codes.ResourceExhausted, "此商品的库存不足")
}

i.Stocks -= good.Num
tx.Save(&i)
}

tx.Commit()

m.Unlock()

return &emptypb.Empty{}, nil
}
MySQL 悲观锁实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (*InventoryServer) ForUpdateSell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
tx := global.DB.Begin()
for _, good := range req.GoodsInfo {
var i model.Inventory
if result := tx.Clauses(clause.Locking{
Strength: "UPDATE",
}).Where(&model.Inventory{Goods: good.GoodsId}).First(&i);
result.RowsAffected == 0 {
tx.Rollback()
return nil, status.Errorf(codes.InvalidArgument, "未找到此商品的库存信息。")
}

if i.Stocks < good.Num {
tx.Rollback()
return nil, status.Errorf(codes.ResourceExhausted, "此商品的库存不足")
}

i.Stocks -= good.Num
tx.Save(&i)
}

tx.Commit()
return &emptypb.Empty{}, nil
}
MySQL 乐观锁实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func (*InventoryServer) VersionSell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
tx := global.DB.Begin()
for _, good := range req.GoodsInfo {
var i model.Inventory
for { // 并发请求相同条件比较多,防止放弃掉一些请求
if result := global.DB.Where(&model.Inventory{Goods: good.GoodsId}).First(&i);
result.RowsAffected == 0 {

tx.Rollback()
return nil, status.Errorf(codes.InvalidArgument, "未找到此商品的库存信息.")
}

if i.Stocks < good.Num {
tx.Rollback() // 回滚
return nil, status.Errorf(codes.ResourceExhausted, "此商品的库存不足")
}

i.Stocks -= good.Num
version := i.Version + 1
if result := tx.Model(&model.Inventory{}).
Select("Stocks", "Version").
Where("goods = ? and version= ?", good.GoodsId, i.Version).
Updates(model.Inventory{Stocks: i.Stocks, Version: version});
result.RowsAffected == 0 {

zap.S().Info("库存扣减失败!")
} else {
break
}
}
}
tx.Commit() // 提交
return &emptypb.Empty{}, nil
}
Redis 分布式锁实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func (*InventoryServer) RedisSell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
// redis 分布式锁
pool := goredis.NewPool(global.Redis)
rs := redsync.New(pool)

tx := global.DB.Begin()
for _, good := range req.GoodsInfo {
mutex := rs.NewMutex(fmt.Sprintf("goods_%d", good.GoodsId))
if err := mutex.Lock(); err != nil {
return nil, status.Errorf(codes.Internal, "redis:分布式锁获取异常")
}

var i model.Inventory
if result := global.DB.Where(&model.Inventory{Goods: good.GoodsId}).First(&i); result.RowsAffected == 0 {
tx.Rollback()
return nil, status.Errorf(codes.InvalidArgument, "未找到此商品的库存信息")
}

if i.Stocks < good.Num {
tx.Rollback()
return nil, status.Errorf(codes.ResourceExhausted, "此商品的库存不足")
}

i.Stocks -= good.Num
tx.Save(&i)

if ok, err := mutex.Unlock(); !ok || err != nil {
return nil, status.Errorf(codes.Internal, "redis:分布式锁释放异常")
}
}

tx.Commit()
return &emptypb.Empty{}, nil
}

测试

涉及到服务、数据库等环境,此测试为伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
var w sync.WaitGroup
w.Add(20)
for i := 0; i < 20; i++ {
go TestForUpdateSell(&w) // 模拟并发请求
}
w.Wait()
}

func TestForUpdateSell(wg *sync.WaitGroup) {
defer wg.Done()
_, err := invClient.Sell(context.Background(), &proto.SellInfo{
GoodsInfo: []*proto.GoodsInvInfo{
{GoodsId: 16, Num: 1},
//{GoodsId: 16, Num: 10},
},
})
if err != nil {
panic(err)
}
fmt.Println("库存扣减成功")
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!