package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync"
"nd/inventory_srv/proto"
"nd/inventory_srv/tests"
)
var invClient proto.InventoryClient
var conn *grpc.ClientConn
func Init() {
var err error
conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
invClient = proto.NewInventoryClient(conn)
}
func TestSell(wg *sync.WaitGroup) {
/*
1. 第一件扣减成功: 第二件: 1. 没有库存信息 2. 库存不足
2. 两件都扣减成功
*/
defer wg.Done()
_, err := invClient.Sell(context.Background(), &proto.SellInfo{
GoodsInfo: []*proto.GoodsInvInfo{
{GoodsId: 1, Num: 1},
//{GoodsId: 422, Num: 30},
},
})
if err != nil {
panic(err)
}
fmt.Println("库存扣减成功")
}
func main() {
Init()
//var i int32
//for i = 1; i <= 9; i++ {
// TestSetInv(i, 90)
//}
//TestInvDetail(2)
//TestSell()
//TestReback()
//并发情况之下,库存无法正确的扣减
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go TestSell(&wg)
}
wg.Wait()
conn.Close()
}


var m sync.Mutex
func (*InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
// 扣减库存,本地事务
// 数据库基本的一个应用场景:数据库事务
// 并发情况之下 可能会出现超卖 1
tx := global.DB.Begin()
m.Lock() // 获取锁
for _, goodInfo := range req.GoodsInfo {
var inv model.Inventory
if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
tx.Rollback() // 回滚之前的操作
return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
}
// 判断库存是否充足
if inv.Stocks < goodInfo.Num {
tx.Rollback() // 回滚之前的操作
return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
}
// 扣减,这里会出现数据不一致的问题
inv.Stocks -= goodInfo.Num
tx.Save(&inv) // 一旦使用了事务的,保存修改数据库的操作就需要使用事务的tx,而不能使用db
}
tx.Commit() // 需要自己手动提交操作
m.Unlock()
return &emptypb.Empty{}, nil
}

悲观锁与乐观锁是人们定义出来的概念,可以理解为一种思想,是处理并发资源的常用手段;
不要把他们与mysql提供的锁机制(表锁、行锁、排它锁、共享锁)混为一谈

for update
set autocommit=0;(注意这个只针对当前窗口有效,不是全局的);(查询select @@autocommit;)select * from inventary where goods=1 for update;commit;where goods=1和where goods=2这2个是不会触发锁的tx := global.DB.Begin()这个就相当于关闭了autocommitglobal.DB替换为tx.Clauses(clause.Locking{Strength: "UPDATE"})func (*InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
// 扣减库存,本地事务
// 数据库基本的一个应用场景:数据库事务
// 并发情况之下 可能会出现超卖 1
tx := global.DB.Begin()
for _, goodInfo := range req.GoodsInfo {
var inv model.Inventory
if result := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
tx.Rollback() // 回滚之前的操作
return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
}
// 判断库存是否充足
if inv.Stocks < goodInfo.Num {
tx.Rollback() // 回滚之前的操作
return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
}
// 扣减,这里会出现数据不一致的问题
inv.Stocks -= goodInfo.Num
tx.Save(&inv) // 一旦使用了事务的,保存修改数据库的操作就需要使用事务的tx,而不能使用db
}
tx.Commit() // 需要自己手动提交操作
return &emptypb.Empty{}, nil
}

func (*InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
// 扣减库存,本地事务
// 数据库基本的一个应用场景:数据库事务
// 并发情况之下 可能会出现超卖 1
tx := global.DB.Begin()
for _, goodInfo := range req.GoodsInfo {
var inv model.Inventory
for {
if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
tx.Rollback() // 回滚之前的操作
return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
}
// 判断库存是否充足
if inv.Stocks < goodInfo.Num {
tx.Rollback() // 回滚之前的操作
return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
}
// 扣减,这里会出现数据不一致的问题
inv.Stocks -= goodInfo.Num
//update inventory set stocks = stocks-1, version=version+1 where goods=goods and version=version
//这种写法有瑕疵,为什么?
//零值 对于int类型来说 默认值是0 这种会被gorm给忽略掉
if result := tx.Model(&model.Inventory{}).Select("Stocks", "Version").Where("goods = ? and version= ?",
goodInfo.GoodsId, inv.Version).Updates(model.Inventory{Stocks: inv.Stocks, Version: inv.Version + 1});
result.RowsAffected == 0 {
zap.S().Info("库存扣减失败")
} else {
break
}
}
}
tx.Commit() // 需要自己手动提交操作
return &emptypb.Empty{}, nil
}
业务开发的最常用的方案:基于redis实现分布式锁
package main
import (
"fmt"
goredislib "github.com/go-redis/redis/v8"
"github.com/go-redsync/redsync/v4"
"github.com/go-redsync/redsync/v4/redis/goredis/v8"
"sync"
"time"
)
func main() {
// Create a pool with go-redis (or redigo) which is the pool redisync will
// use while communicating with Redis. This can also be any pool that
// implements the `redis.Pool` interface.
client := goredislib.NewClient(&goredislib.Options{
Addr: "192.168.91.129:6379", // 这个自己修改成redis的ip地址
})
pool := goredis.NewPool(client) // 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.
gNum := 2 // 启动2个协程
mutexname := "1" // 假设以goods的id来命名锁
var wg sync.WaitGroup
wg.Add(gNum)
for i := 0; i < gNum; i++ {
go func() {
defer wg.Done()
mutex := rs.NewMutex(mutexname)
//zookeeper的分布式锁 -
fmt.Println("开始获取锁")
if err := mutex.Lock(); err != nil {
panic(err)
}
fmt.Println("获取锁成功")
time.Sleep(time.Second * 3)
fmt.Println("开始释放锁")
if ok, err := mutex.Unlock(); !ok || err != nil {
panic("unlock failed")
}
fmt.Println("释放锁成功")
}()
}
wg.Wait()
}

func (*InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
client := goredislib.NewClient(&goredislib.Options{
Addr: "192.168.91.129:6379",
})
pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
rs := redsync.New(pool)
tx := global.DB.Begin()
for _, goodInfo := range req.GoodsInfo {
var inv model.Inventory
mutex := rs.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId))
if err := mutex.Lock(); err != nil {
return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
}
if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
tx.Rollback() //回滚之前的操作
return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
}
//判断库存是否充足
if inv.Stocks < goodInfo.Num {
tx.Rollback() //回滚之前的操作
return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
}
//扣减, 会出现数据不一致的问题 - 锁,分布式锁
inv.Stocks -= goodInfo.Num
tx.Save(&inv)
if ok, err := mutex.Unlock(); !ok || err != nil {
return nil, status.Errorf(codes.Internal, "释放redis分布式锁异常")
}
}
tx.Commit() // 需要自己手动提交操作
//m.Unlock() //释放锁
return &emptypb.Empty{}, nil
}









