go语言里面实现并发安全扣减库存的几种方式
一、基本数据准备1、数据表的创建-- ---------------- -- 库存表 -- ---------------- DROP TABLE IF EXISTS inventory; CREATE TABLE inventory ( id int NOT NULL AUTO_INCREMENT primary key COMMENT 主键id, goods_id int(11) default 1 comment 商品id, stocks int(11) default 1 comment 商品库存, version int(11) default 0 comment 商品版本号, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted_at timestamp NULL DEFAULT NULL COMMENT 软删除时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存表;2、根据实体类创建数据模型3、手动在数据库中插入商品库存数据4、创建一个基本的连接gorm的方法packageutilsimport(fmt_github.com/go-sql-driver/mysqlgorm.io/driver/mysqlgorm.io/gormgorm.io/gorm/loggergorm.io/gorm/schema)varGormDb*gorm.DBfuncinit(){varerrerrorsqlStr:root:123456tcp(localhost:3306)/gorm_demo?charsetutf8mb4parseTimetruelocLocalGormDb,errgorm.Open(mysql.Open(sqlStr),gorm.Config{Logger:logger.Default.LogMode(logger.Info),//DisableForeignKeyConstraintWhenMigrating: true, // 禁止创建外键NamingStrategy:schema.NamingStrategy{SingularTable:true,// 全部的表名前面加前缀//TablePrefix: mall_,},})iferr!nil{fmt.Println(数据库连接错误,err)return}}二、模拟并发下单1、模拟并发下单扣减库存typeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{// 扣减库存inventoryEntity:model.InventoryEntity{}iferr:tx.Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()return}// 库存减少stocks:inventoryEntity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)tx.Rollback()return}fmt.Println(开始扣减库存...)iferr:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).UpdateColumn(stocks,stocks).Error;err!nil{fmt.Println(扣减库存失败,err)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}2、查看数据库库存信息执行了20个并发但是实际没有扣减20个库存三、使用加锁的方式来实现并发安全1、使用go的锁的机制来实现并发安全packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}varmsync.Mutex{}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{m.Lock()deferm.Unlock()// 扣减库存inventoryEntity:model.InventoryEntity{}iferr:tx.Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()return}// 库存减少stocks:inventoryEntity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)tx.Rollback()return}fmt.Println(开始扣减库存...)iferr:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).UpdateColumn(stocks,stocks).Error;err!nil{fmt.Println(扣减库存失败,err)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}2、查看数据库库存这次是每次减少20个了四、使用mysql的悲观锁来实现并发安全1、使用行锁类似这样的SELECT * FROM user WHERE id 1 FOR UPDATE;2、在gorm中使用行锁packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{// 扣减库存inventoryEntity:model.InventoryEntity{}iferr:tx.Clauses(clause.Locking{Strength:UPDATE}).Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()return}// 库存减少stocks:inventoryEntity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)tx.Rollback()return}fmt.Println(开始扣减库存...)iferr:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).UpdateColumn(stocks,stocks).Error;err!nil{fmt.Println(扣减库存失败,err)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}3、使用悲观锁的另外一种实现方式packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{fmt.Println(开始扣减库存...)result:tx.Model(model.InventoryEntity{}).Where(goods_id ? and stocks ?,item.GoodsID,item.Num).Update(stocks,gorm.Expr(stocks - ?,item.Num))ifresult.Error!nil{fmt.Println(扣减库存失败,result.Error)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}五、使用mysql的乐观锁来实现1、官方插件optimisticlock2、修改数据库模型version的数据类型constTableNameInventoryEntityinventory// InventoryEntity 库存表typeInventoryEntitystruct{IDint64gorm:column:id;type:int;primaryKey;autoIncrement:true;comment:主键id json:id,string// 主键idGoodsIDint64gorm:column:goods_id;type:int;default:1;comment:商品id json:goodsId// 商品idStocksint64gorm:column:stocks;type:int;default:1;comment:商品库存 json:stocks// 商品库存Version optimisticlock.Versiongorm:column:version;type:int;comment:商品版本号 json:version// 商品版本号CreatedAt LocalTimegorm:column:created_at;comment:创建时间 json:createdAt// 创建时间UpdatedAt LocalTimegorm:column:updated_at;comment:更新时间 json:updatedAt// 更新时间DeletedAt gorm.DeletedAtgorm:column:deleted_at;type:timestamp;comment:软删除时间 json:-// 软删除时间}// TableName InventoryEntitys table namefunc(*InventoryEntity)TableName()string{returnTableNameInventoryEntity}3、具体乐观锁的实现packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsIdNumint64json:num}funcSell(ws*sync.WaitGroup){deferws.Done()reqList:[]InventoryDto{{GoodsID:1,Num:1},}for_,item:rangereqList{for{entity:model.InventoryEntity{}iferr:utils.GormDb.Where(goods_id ?,item.GoodsID).First(entity).Error;err!nil{fmt.Println(查询错误:,err)break}stocks:entity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)break}fmt.Println(开始扣减库存...)result:utils.GormDb.Model(model.InventoryEntity{}).Where(goods_id ? AND version ?,item.GoodsID,entity.Version).Updates(map[string]interface{}{stocks:stocks,version:gorm.Expr(version 1),})ifresult.Error!nil{fmt.Println(更新错误:,result.Error)}ifresult.RowsAffected!0{break}}}}funcmain(){wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}六、使用redis分布式锁来实现1、文档地址2、简单的案例实现packagemainimport(fmtsynctimegoredislibgithub.com/go-redis/redis/v8github.com/go-redsync/redsync/v4github.com/go-redsync/redsync/v4/redis/goredis/v8)funcmain(){// 配置redis连接client:goredislib.NewClient(goredislib.Options{Addr:localhost:6379,})// 关闭客户端连接deferclient.Close()// 创建 redsync 需要的池pool:goredis.NewPool(client)// 创建 redsync 实例rs:redsync.New(pool)// 设置锁名称mutexname:my-global-mutexgNum:2varwg sync.WaitGroup wg.Add(gNum)fori:0;igNum;i{gofunc(idint){deferwg.Done()mutex:rs.NewMutex(mutexname,redsync.WithExpiry(10*time.Second),redsync.WithTries(50),// 重试直到成功redsync.WithRetryDelay(200*time.Millisecond))fmt.Printf(goroutine %d: 开始获取锁...\n,id)iferr:mutex.Lock();err!nil{fmt.Printf(goroutine %d: 获取锁失败: %v\n,id,err)return}fmt.Printf(goroutine %d: 获取锁成功...\n,id)time.Sleep(time.Second*5)fmt.Printf(goroutine %d: 开始释放锁...\n,id)ifok,err:mutex.Unlock();!ok||err!nil{fmt.Printf(goroutine %d: 释放锁失败: %v\n,id,err)return}fmt.Printf(goroutine %d: 释放锁成功\n,id)}(i)}wg.Wait()fmt.Println(主进程结束..)}3、简单粗暴的实现packagemainimport(fmtgin-admin-api/modelgoredislibgithub.com/go-redis/redis/v8github.com/go-redsync/redsync/v4github.com/go-redsync/redsync/v4/redis/goredis/v8sync)typeInventoryDto1struct{GoodsIDint64json:goodsIdNumint64json:num}// DeductStock1 使用Redis分布式锁扣减库存优化版funcDeductStock1(ws*sync.WaitGroup){client:goredislib.NewClient(goredislib.Options{Addr:localhost:6379,})pool:goredis.NewPool(client)// or, pool : redigo.NewPool(...)rs:redsync.New(pool)// 锁名要加上单号mutex:rs.NewMutex(cell_order)iferr:mutex.Lock();err!nil{fmt.Println(获取锁失败)return}tx:utils.GormDb.Begin()deferws.Done()reqList:[]InventoryDto1{{GoodsID:1,Num:2},}for_,item:rangereqList{inventoryEntity:model.InventoryEntity{}iferr:utils.GormDb.Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()break}ifinventoryEntity.Stocksitem.Num{fmt.Printf(库存不足剩余%d个按实际扣减%d个\n,inventoryEntity.Stocks,item.Num)tx.Rollback()break}fmt.Println(开始扣减库存...)stocks:inventoryEntity.Stocks-item.Num result:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).Updates(map[string]interface{}{stocks:stocks,})ifresult.Error!nil{fmt.Println(扣减库存失败,result.Error)tx.Rollback()break}}tx.Commit()// 先提交事务mutex.Unlock()}funcmain(){wg:sync.WaitGroup{}// 模拟20并发fori:0;i20;i{wg.Add(1)goDeductStock1(wg)}wg.Wait()fmt.Println(全部执行完成)}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2483085.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!