go模拟5W个并发请求抢购,解决并发情况下库存异常问题

go模拟5W个并发请求抢购,解决并发情况下库存异常问题,第1张

      高并发下怎么保证不超卖?

     下面是我在某云买的2核2G内存,装了一堆软件ngix,mysql redis docker etcd等一堆东西测试机的测试结果,开启5万个协程去模拟并发抢购,大概消耗2-3秒处理完毕,这样的机器都能做到就2万QPS,给我几台机弄个集群的话我能整它十万QPS呢

正常的中小公司有500张下单每秒都一年已经赚几十亿了,以前在的公司,100-200张单每秒,一个月十多万张单,ab测试并发也就一百多,一个月月PV 4-5000万的都已经一年赚几个亿了

CREATE TABLE `stock` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `pid` int(10) unsigned DEFAULT NULL,
  `name` varchar(255) DEFAULT '',
  `stock` int(10) unsigned DEFAULT NULL COMMENT '库存',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4;

废话说完,上代码:

package main

import (
	"errors"
	"fmt"
	"github.com/go-redis/redis"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"strconv"
	"sync"
	"time"
)


type Stock struct {
	ID int64 `gorm:"column:id"`// 主键
	Pid int64 `gorm:"column:pid"`
	//例如 `gorm:"column:name"` 标签说明含义是: Mysql表的列名(字段名)为name
	Name string `gorm:"column:name"`
	Stock int64 `gorm:"column:stock"`
}

//设置表名,可以通过给struct类型定义 TableName函数,返回当前struct绑定的mysql表名是什么
func (s Stock) TableName() string {
	//绑定MYSQL表名为user
	return "stock"
}

var (
	WaitGroup = sync.WaitGroup{}
)
func InitRedis()(client *redis.Client){
	client = redis.NewClient(&redis.Options{
		Addr:     "localhost:6400", // redis地址
		Password: "", // redis密码,没有则留空
		DB:       0,  // 默认数据库,默认是0
	})
	return
}
func main() {

	//配置MySQL连接参数
	username := "root"  //账号
	password := "root"      //密码
	host := "127.0.0.1" //数据库地址,可以是Ip或者域名
	port := 3306        //数据库端口
	Dbname := "gf"      //数据库名

	//通过前面的数据库参数,拼接MYSQL DSN, 其实就是数据库连接串(数据源名称)
	//MYSQL dsn格式: {username}:{password}@tcp({host}:{port})/{Dbname}?charset=utf8&parseTime=True&loc=Local
	//类似{username}使用花括号包着的名字都是需要替换的参数
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", username, password, host, port, Dbname)
	//连接MYSQL
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})


	if err != nil {
		fmt.Println("连接数据库失败, error=" + err.Error())
		return
	}


	s := Stock{
		Name:   "iphone13",
		Stock:   1000,
		Pid: 50,
	}

	//插入一条用户数据
	//下面代码会自动生成SQL语句:INSERT INTO `stock` (`name`,`stock`,`pid`) VALUES ('iphone13','1000','50')
	if err := db.Create(&s).Error; err != nil {
		fmt.Println("插入失败", err)
		return
	}

	//查询并返回第一条数据
	//定义需要保存数据的struct变量
	res := Stock{}
	//自动生成sql: SELECT * FROM `stock`  WHERE (name = 'iphone13') LIMIT 1
	result := db.Where("name = ?", "iphone13").First(&res)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		fmt.Println("找不到记录")
		return
	}
	//打印查询到的数据
	fmt.Println(res.Name, res.Stock)


	redisCli := InitRedis()
	redisCli.Set("stock",2,3600*time.Second)

	//更新
	for i := 0; i < 50000; i++ {
		WaitGroup.Add(1)
		go LockKey(db, i,redisCli) //加锁,保证库存正常

		//go UnlockUpdate(db, i) //不加锁,库存异常
	}


	WaitGroup.Wait()//等待协程执行完毕
}

var LockIt = sync.Mutex{}

//加锁,库存正常
func LockKey(db *gorm.DB, i int,redisCli *redis.Client) {
	defer WaitGroup.Done()

	res := Stock{}

	//模拟处理数据需要1秒时间
	//time.Sleep(1 * time.Second)

	LockIt.Lock()
	defer LockIt.Unlock()


	stockNum,err:=redisCli.Get("stock").Result()
	if err!=nil{
		fmt.Println("get key error ",err)
		return
	}

	num,_:= strconv.Atoi(stockNum)
	if num<=0{
		//库存不足
		//fmt.Println("库存不足",i)
		return
	}


	res01 := db.Where("id = ?", 2).First(&res)
	if errors.Is(res01.Error, gorm.ErrRecordNotFound) {
		fmt.Println("找不到记录")
		return
	}
	//打印查询到的数据
	fmt.Println(res.Name, res.Stock)

	stock := res.Stock - 1//减少数据库库存
	cacheStock := num-1//减少redis库存

	redisErr := redisCli.Set("stock",cacheStock,3600*time.Second).Err()
	if redisErr!=nil{
        //to do
		return
	}

    //开启事务
    tx := db.Begin()
	row, err := db.Model(&Stock{}).Where("id = ?", 2).Update("stock", stock).Rows()


	if err != nil {
        tx.Rollback()
		fmt.Println("update error:", i, err)
	} else {
        tx.Commit()
		fmt.Println("update success", i, row.Err())
	}
}

运行结果

[root@VM-0-15-centos demo]# go run    redis.go 
iphone13 1000
[{1 1649255108}]
update success 2 
update success 0 
消耗时间秒: 3
[root@VM-0-15-centos demo]# go run    redis.go       
iphone13 1000
update success 3 
[{1 1649255108}]
update success 0 
消耗时间秒: 2
[root@VM-0-15-centos demo]# go run    redis.go 
  hone13 1000
update success 4 
[{1 1649255108}]
update success 0 
消耗时间秒: 3
[root@VM-0-15-centos demo]# go run    redis.go 
iphone13 1000
update success 0 
[{1 1649255108}]
update success 1 
消耗时间秒: 2
[root@VM-0-15-centos demo]# go run    redis.go 
iphone13 1000
update success 0 
[{1 1649255108}]
update success 1 
消耗时间秒: 3
[root@VM-0-15-centos demo]# go run    redis.go 
iphone13 1000
update success 1 
[{1 1649255108}]
update success 0 
消耗时间秒: 3
[root@VM-0-15-centos demo]# go run    redis.go 
iphone13 1000
update success 0 
[{1 1649255108}]
update success 35 
消耗时间秒: 3

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/990488.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存