This article is introduced by the golang tutorial column to introduce several implementation methods of Go inventory deduction. I hope it will be helpful to friends in need!
Grpc, proto, gorm, zap, go-redis, go-redsync and other packages are used here
var m sync.Mutexfunc (*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}
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}
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}
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}
Involves services, databases and other environments, this test is pseudo code
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("库存扣减成功")}
The above is the detailed content of Detailed explanation of how inventory deduction is implemented in Go (multiple methods). For more information, please follow other related articles on the PHP Chinese website!