分布式锁
约 312 字大约 1 分钟
2024-08-18
go锁
var m sync.Mutex
m.Lock()
m.Unlock()
mysql 悲观锁
func (s InventoryServer) Sell(ctx context.Context, request *proto.SellInfo) (*emptypb.Empty, error) {
tx := global.DB.Begin()
for _, item := range request.GoodsInfo {
inventory := model.Inventory{}
// 需要用tx.Clauses(clause.Locking{Strength: "UPDATE"})
// SELECT * FROM `inventory` WHERE goods = ? LIMIT 1 FOR UPDATE
// https://blog.csdn.net/rootzcl/article/details/101597084
// 👇 👇
if result := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("goods = ?", item.GoodsId).First(&inventory); result.RowsAffected == 0 {
tx.Rollback()
return nil, status.Error(codes.InvalidArgument, "没有库存信息")
}
if item.Nums > inventory.Stocks {
tx.Rollback()
return nil, status.Error(codes.ResourceExhausted, "库存不足")
}
inventory.Stocks -= item.Nums
tx.Save(&inventory)
}
tx.Commit()
return &emptypb.Empty{}, nil
}
mysql 乐观锁
// Sell 使用乐观锁
func (s InventoryServer) Sell(ctx context.Context, request *proto.SellInfo) (*emptypb.Empty, error) {
tx := global.DB.Begin()
for _, item := range request.GoodsInfo {
for {
inventory := model.Inventory{}
if result := global.DB.Where("goods = ?", item.GoodsId).First(&inventory); result.RowsAffected == 0 {
tx.Rollback()
return nil, status.Error(codes.InvalidArgument, "没有库存信息")
}
if item.Nums > inventory.Stocks {
tx.Rollback()
return nil, status.Error(codes.ResourceExhausted, "库存不足")
}
inventory.Stocks -= item.Nums
if result := tx.Model(&model.Inventory{}).
// 👇
Where("goods = ? and version = ?", inventory.Goods, inventory.Version).
Select("stocks", "version"). // 避免0被忽略
Updates(model.Inventory{
Stocks: inventory.Stocks,
Version: inventory.Version + 1, // 👈
}); result.RowsAffected == 0 {
zap.S().Info("库存扣减失败 (乐观锁)")
} else {
break
}
}
}
tx.Commit()
return &emptypb.Empty{}, nil
}
redis 分布式锁
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisConfig.Host, global.ServerConfig.RedisConfig.Port),
Password: global.ServerConfig.RedisConfig.Password,
})
pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
global.RedSync = redsync.New(pool)
mutex := global.RedSync.NewMutex(fmt.Sprintf("goods_%d", item.GoodsId))
// 获取锁
if err := mutex.Lock(); err != nil {
return nil, status.Error(codes.Internal, "获取redis分布式锁异常")
}
// 释放锁
if ok, err := mutex.Unlock(); !ok || err != nil {
tx.Rollback()
return nil, status.Error(codes.Internal, "释放redis分布式锁异常")
}