秒杀要不要用锁?五种方案的代码实践和超卖演示

2022-03-06  本文已影响0人  张清柏

https://www.jianshu.com/p/a2bd89e0d24b

工欲善其事必先利其器,我们先来装一下相关工具

jmeter

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 2> /dev/null
brew install jmeter
 /usr/local/Cellar/jmeter/5.4.2/bin/jmeter 

使用go做代码演示(安装忽略)

 go mod init acurd.com/m

使用MySQL作为数据存储

create database `go-project`;
use `go-project`;
drop table if exists goods;
CREATE TABLE `goods`
(
    `id`      int(11) unsigned NOT NULL AUTO_INCREMENT,
    `name`    varchar(50)      NOT NULL DEFAULT '' COMMENT '名称',
    `count`   int(11)          NOT NULL COMMENT '库存',
    `sale`    int(11)          NOT NULL COMMENT '已售',
    `version` int(11)          NOT NULL COMMENT '乐观锁,版本号',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 COMMENT '商品表';

drop table if exists goods_order;
CREATE TABLE `goods_order`
(
    `id`          int(11) unsigned NOT NULL AUTO_INCREMENT,
    `gid`         int(11)          NOT NULL COMMENT '库存ID',
    `name`        varchar(30)      NOT NULL DEFAULT '' COMMENT '商品名称',
    `create_time` timestamp        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 COMMENT '订单表';

insert into goods (`id`,`name`,`count`,`sale`,`version`) values (1,'华为p40',10,0,0);

相关代码[有点粗糙]

package main

import (
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "log"
    "net/http"
    "strconv"
    "time"
)

// 商品表
type Goods struct {
    Id      uint   `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
    Name    string `gorm:"column:name;type:varchar(50);NOT NULL" json:"name"`   // 名称
    Count   int    `gorm:"column:count;type:int(11);NOT NULL" json:"count"`     // 库存
    Sale    int    `gorm:"column:sale;type:int(11);NOT NULL" json:"sale"`       // 已售
    Version int    `gorm:"column:version;type:int(11);NOT NULL" json:"version"` // 乐观锁,版本号
}
// 订单表
type GoodsOrder struct {
    Id         uint      `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
    Gid        int       `gorm:"column:gid;type:int(11);NOT NULL" json:"gid"`                                             // 库存ID
    Name       string    `gorm:"column:name;type:varchar(30);NOT NULL" json:"name"`                                       // 商品名称
    CreateTime time.Time `gorm:"column:create_time;type:timestamp;default:CURRENT_TIMESTAMP;NOT NULL" json:"create_time"` // 创建时间
}
//实际表名
func (m *GoodsOrder) TableName() string {
    return "goods_order"
}

func main() {
    http.HandleFunc("/", addOrder)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

func getDb() *gorm.DB {
    connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", "guofu", "guofu", "localhost", 13306, "go-project")

    db, err := gorm.Open("mysql", connArgs)
    if err != nil {
        panic(err)
    }
    db.LogMode(true) //打印sql语句
    //开启连接池
    db.DB().SetMaxIdleConns(100)   //最大空闲连接
    db.DB().SetMaxOpenConns(100)   //最大连接数
    db.DB().SetConnMaxLifetime(30) //最大生存时间(s)
    return db
}

func addOrder(w http.ResponseWriter, r *http.Request) {
    db := getDb()
    defer db.Close()

    // 先去查看商品表还有没有库存
    var goods Goods
    db.Where("id = ?", "1").First(&goods)
    //fmt.Printf("%+v", goods)
    if goods.Count >0 {
        tx := db.Begin()
        defer func() {
            if r := recover()
                r != nil {
                tx.Rollback()
            }
        }()

        goods.Sale+=1
        goods.Count-=1
        //更新数据库
        if err := tx.Save(&goods).Error; err != nil {
            tx.Rollback()
            panic(err)
        }

        order:= GoodsOrder{
            Gid: 1,
            Name:strconv.Itoa(int(time.Now().Unix())),
        }

        if err := tx.Create(&order).Error; err != nil {
            tx.Rollback()
            panic(err)
        }
        tx.Commit()
        w.Write([]byte(fmt.Sprintf("the count i read is %d",goods.Count)))
    }else{
        w.Write([]byte("我啥子都么抢到"))

    }

    //如果有库存插入到订单表
}

解决超卖

悲观锁

func addOrder(w http.ResponseWriter, r *http.Request) {
    db := getDb()
    defer db.Close()

    // 先去查看商品表还有没有库存
    var goods Goods
    tx := db.Begin()
    if err := tx.Set("gorm:query_option", "FOR UPDATE").First(&goods, 1).Error; err != nil {
        tx.Rollback()
        panic(err)
    }
    defer func() {
        if r := recover()
            r != nil {
            tx.Rollback()
        }
    }()
    //fmt.Printf("%+v", goods)
    if goods.Count >0 {
        goods.Sale+=1
        goods.Count-=1
        //更新数据库
        if err := tx.Save(&goods).Error; err != nil {
            tx.Rollback()
            panic(err)
        }

        order:= GoodsOrder{
            Gid: 1,
            Name:strconv.Itoa(int(time.Now().Unix())),
        }

        if err := tx.Create(&order).Error; err != nil {
            tx.Rollback()
            panic(err)
        }
        tx.Commit()
        w.Write([]byte(fmt.Sprintf("the count i read is %d",goods.Count)))
    }else{
        tx.Rollback()
        w.Write([]byte("我啥子都么抢到"))
    }

    //如果有库存插入到订单表
}

乐观锁

func addOrder(w http.ResponseWriter, r *http.Request) {
    db := getDb()
    defer db.Close()

    // 先去查看商品表还有没有库存
    var goods Goods
    tx := db.Begin()
    if err := tx.Where("ID=?", "1").First(&goods).Error; err != nil {
        tx.Rollback()
        return
    }
    defer func() {
        if r := recover()
            r != nil {
            tx.Rollback()
            return
        }
    }()
    //fmt.Printf("%+v", goods)
    if goods.Count >0 {
        goods.Sale+=1
        goods.Count-=1
        oldVerson:=goods.Version
        goods.Version+=1
        //更新数据库
        column:=tx.Model(&goods).Where("version=?",oldVerson).Updates(&goods)
        if column.RowsAffected==0 {//没有更新成功
            tx.Rollback()
            w.Write([]byte("我没有抢过别人"))
            return
        }

        order:= GoodsOrder{
            Gid: 1,
            Name:strconv.Itoa(int(time.Now().Unix())),
        }

        if err := tx.Create(&order).Error; err != nil {
            tx.Rollback()
            w.Write([]byte("创建订单失败"))
            return
        }
        tx.Commit()
        w.Write([]byte(fmt.Sprintf("the count i read is %d",goods.Count)))
    }else{
        tx.Rollback()
        w.Write([]byte("我啥子都么抢到"))
    }

    //如果有库存插入到订单表
}
在这里插入图片描述

redis 锁

那么下面的案例,我们就基于Redis来实现秒杀的功能

配置Redis的相关数据
package main

import (
    "fmt"
    "github.com/go-redis/redis"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", addOrder)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

func addOrder(w http.ResponseWriter, r *http.Request) {
    //从redis里面读取数据,如果读取到了,就进入下单环节
    var list = "goodslist"
    var orderList="orderlist"
    client := getRedis()
    /** 用于初始化一个1000库存的队列
    client.LTrim(list, 1, 0)     //先初始化一个空队列
    for i := 1; i <= 1000; i++ { //队列里面放1000个库存
        client.LPush(list, i)
    }
    return
    **/

    var res = client.RPop(list)
    val := res.Val()
    if len(val) > 0 {
        //抢到后把用户的id 存入 另外一个队列,用于创建订单
        r.ParseForm()
        uid:=r.FormValue("uid")

        //fmt.Println(uid)
        //return
        client.LPush(orderList,uid)
        msg:=fmt.Sprintf("我抢到了,我是第%v抢到的 我的用户id是 %v \n", val,uid)
        _, _ = w.Write([]byte(msg))
        fmt.Print(msg)
    } else {
        msg:="我啥子都没得抢到\n"
        _, _ = w.Write([]byte(msg))
        fmt.Print(msg)
    }
    return
}

func getRedis() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr: "127.0.0.1:6379",
    })
    return client
}
使用redis incrby decrby来控制下单人数
func addOrder(w http.ResponseWriter, r *http.Request) {
    //从redis里面读取数据,如果读取到了,就进入下单环节
    var inckey="inc-count"
    var orderList="inc-orderlist"
    var total int64=1000
    client := getRedis()
    //defer client.Close()
    //defer r.Body.Close()
    var res = client.IncrBy(inckey,1)
    val := res.Val()
    if res.Err()!=nil{
        fmt.Print(res.Err())
        return
    }
    fmt.Println("我的值现在是",val);
    //return
    if val <= total {
        //抢到后把用户的id 存入 另外一个队列,用于创建订单
        r.ParseForm()
        uid:=r.FormValue("uid")
        client.LPush(orderList,uid)
        msg:=fmt.Sprintf("我抢到了,我是第%d抢到的 我的用户id是 %v \n", val,uid)
        _, _ = w.Write([]byte(msg))
        fmt.Print(msg)
    } else {
        msg:="我啥子都没得抢到\n"
        _, _ = w.Write([]byte(msg))
    }
    return
}
插曲
在这里插入图片描述

redis分布式锁

在开始之前,我们先来熟悉一下这几个命令(从 Redis 2.6.12 版本开始支持)

package main

import (
    "fmt"
    "github.com/go-redis/redis"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "log"
    "net/http"
    "strconv"
    "time"
)

// 商品表
type Goods struct {
    Id      uint   `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
    Name    string `gorm:"column:name;type:varchar(50);NOT NULL" json:"name"`   // 名称
    Count   int    `gorm:"column:count;type:int(11);NOT NULL" json:"count"`     // 库存
    Sale    int    `gorm:"column:sale;type:int(11);NOT NULL" json:"sale"`       // 已售
    Version int    `gorm:"column:version;type:int(11);NOT NULL" json:"version"` // 乐观锁,版本号
}

// 订单表
type GoodsOrder struct {
    Id         uint      `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
    Gid        int       `gorm:"column:gid;type:int(11);NOT NULL" json:"gid"`                                             // 库存ID
    Name       string    `gorm:"column:name;type:varchar(30);NOT NULL" json:"name"`                                       // 商品名称
    CreateTime time.Time `gorm:"column:create_time;type:timestamp;default:CURRENT_TIMESTAMP;NOT NULL" json:"create_time"` // 创建时间
}

//实际表名
func (m *GoodsOrder) TableName() string {
    return "goods_order"
}

func main() {

    http.HandleFunc("/", addOrder)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

func getDb() *gorm.DB {
    connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", "guofu", "guofu", "localhost", 13306, "go-project")

    db, err := gorm.Open("mysql", connArgs)
    if err != nil {
        panic(err)
    }
    db.LogMode(false) //打印sql语句
    //开启连接池
    db.DB().SetMaxIdleConns(100)   //最大空闲连接
    db.DB().SetMaxOpenConns(100)   //最大连接数
    db.DB().SetConnMaxLifetime(30) //最大生存时间(s)
    return db
}

func addOrder(w http.ResponseWriter, r *http.Request) {
    key := "order"
    client := getRedis()
    defer client.Close()
    cmd := client.SetNX(key, "1", time.Second*30)//这里会有一个问题,就是 我里面的程序执行过长,导致锁释放,那么程序末尾的删除锁 就会删除其他请求的锁,导致不可用
    if cmd.Val() == true {

        db := getDb()
        defer db.Close()

        // 先去查看商品表还有没有库存
        var goods Goods
        db.Where("id = ?", "1").First(&goods)
        fmt.Println(goods.Count)
        if goods.Count > 0 {
            tx := db.Begin()
            defer func() {
                if r := recover()
                    r != nil {
                    tx.Rollback()
                }
            }()

            goods.Sale += 1
            goods.Count -= 1
            //更新数据库
            if err := tx.Save(&goods).Error; err != nil {
                tx.Rollback()
                panic(err)
            }

            order := GoodsOrder{
                Gid:  1,
                Name: strconv.Itoa(int(time.Now().Unix())),
            }

            if err := tx.Create(&order).Error; err != nil {
                tx.Rollback()
                panic(err)
            }
            tx.Commit()
            w.Write([]byte(fmt.Sprintf("the count i read is %d", goods.Count)))
        } else {
            w.Write([]byte("我啥子都么抢到"))

        }
        client.Del(key)
    }

}

func getRedis() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr: "127.0.0.1:6379",
    })
    return client
}
package main

import (
    "fmt"
    "github.com/go-redis/redis"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "github.com/satori/go.uuid"
    "log"
    "net/http"
    "strconv"
    "time"
)

// 商品表
type Goods struct {
    Id      uint   `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
    Name    string `gorm:"column:name;type:varchar(50);NOT NULL" json:"name"`   // 名称
    Count   int    `gorm:"column:count;type:int(11);NOT NULL" json:"count"`     // 库存
    Sale    int    `gorm:"column:sale;type:int(11);NOT NULL" json:"sale"`       // 已售
    Version int    `gorm:"column:version;type:int(11);NOT NULL" json:"version"` // 乐观锁,版本号
}

// 订单表
type GoodsOrder struct {
    Id         uint      `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
    Gid        int       `gorm:"column:gid;type:int(11);NOT NULL" json:"gid"`                                             // 库存ID
    Name       string    `gorm:"column:name;type:varchar(30);NOT NULL" json:"name"`                                       // 商品名称
    CreateTime time.Time `gorm:"column:create_time;type:timestamp;default:CURRENT_TIMESTAMP;NOT NULL" json:"create_time"` // 创建时间
}

//实际表名
func (m *GoodsOrder) TableName() string {
    return "goods_order"
}

func main() {

    http.HandleFunc("/", addOrder)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

func getDb() *gorm.DB {
    connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", "guofu", "guofu", "localhost", 13306, "go-project")

    db, err := gorm.Open("mysql", connArgs)
    if err != nil {
        panic(err)
    }
    db.LogMode(false) //打印sql语句
    //开启连接池
    db.DB().SetMaxIdleConns(100)   //最大空闲连接
    db.DB().SetMaxOpenConns(100)   //最大连接数
    db.DB().SetConnMaxLifetime(30) //最大生存时间(s)
    return db
}

func addOrder(w http.ResponseWriter, r *http.Request) {
    value:=GetUUID()
    key := "order"
    client := getRedis()
    defer client.Close()
    cmd := client.SetNX(key,value , time.Second*30)//这里会有一个问题,就是 我里面的程序执行过长,导致锁释放,那么程序末尾的删除锁 就会删除其他请求的锁,导致不可用
    if cmd.Val() == true {

        db := getDb()
        defer db.Close()

        // 先去查看商品表还有没有库存
        var goods Goods
        db.Where("id = ?", "1").First(&goods)
        fmt.Println(goods.Count)
        if goods.Count > 0 {
            tx := db.Begin()
            defer func() {
                if r := recover()
                    r != nil {
                    tx.Rollback()
                }
            }()

            goods.Sale += 1
            goods.Count -= 1
            //更新数据库
            if err := tx.Save(&goods).Error; err != nil {
                tx.Rollback()
                panic(err)
            }

            order := GoodsOrder{
                Gid:  1,
                Name: strconv.Itoa(int(time.Now().Unix())),
            }

            if err := tx.Create(&order).Error; err != nil {
                tx.Rollback()
                panic(err)
            }
            tx.Commit()
            w.Write([]byte(fmt.Sprintf("the count i read is %d", goods.Count)))
        } else {
            w.Write([]byte("我啥子都么抢到"))

        }
        if client.Get(key).Val()==value {
            client.Del(key)
        }
    }
}

func GetUUID() (string) {
    u2 := uuid.NewV4()
    return u2.String()
}


func getRedis() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr: "127.0.0.1:6379",
    })
    return client
}

lua+redis 实现分布式锁

为什么使用lua
学习使用lua
#   ------------------------------------------  script-----------------------------numkeys--key-----arg--arg
EVAL "redis.call('SET',KEYS[1],ARGV[1]);redis.call('EXPIRE',KEYS[1],ARGV[2]);return 1;" 1    age   18   60

127.0.0.1:6379> EVAL "redis.call('SET',KEYS[1],ARGV[1]);redis.call('EXPIRE',KEYS[1],ARGV[2]);return 1;" 1    age   18   60
(integer) 1
127.0.0.1:6379> get age
"18"

其中的 引号内的部分是脚本, 1代表key的数量,就是只有1个key ,这个key是谁呢?就是age 后面跟了两个参数 argv[1] 是18 argv[2]是60
上面的这个命令的意思和set age 18 EX 60是一样的,设置key1的值是10 过期时间是60秒

SCRIPT LOAD
127.0.0.1:6379> SCRIPT LOAD "redis.call('SET',KEYS[1],ARGV[1]);redis.call('EXPIRE',KEYS[1],ARGV[2]);return 1;"
"6cc501292668ceef3dd487b3e4e889dc08d07587"
EVALSHA

命令格式:EVALSHA sha1 numkeys key [key …] arg [arg …]
刚才脚本不是被缓存了吗,怎么执行呢?在任何客户端通过EVALSHA命令,可以使用脚本的 SHA1 校验和来调用这个脚本。脚本可以在缓存中保留无限长的时间,直到执行SCRIPT FLUSH为止。

127.0.0.1:6379> EVALSHA 6cc501292668ceef3dd487b3e4e889dc08d07587 1 name jimy 10
(integer) 1
127.0.0.1:6379> get name 
"jimy"
127.0.0.1:6379> get name 
(nil)

127.0.0.1:6379> EVALSHA 6cc501292668ceef3dd487b3e4e889dc08d07587 1 name jimy 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 7
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
SCRIPT FLUSH

命令格式:SCRIPT FLUSH
清除Redis服务端所有 Lua 脚本缓存,注意是所有

127.0.0.1:6379> SCRIPT EXISTS 6cc501292668ceef3dd487b3e4e889dc08d07587
1) (integer) 1
127.0.0.1:6379> SCRIPT FLUSH
OK
127.0.0.1:6379> SCRIPT EXISTS 6cc501292668ceef3dd487b3e4e889dc08d07587
1) (integer) 0
SCRIPT KILL

命令格式:SCRIPT KILL
杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效。 这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本,或者因为读取的key过大导致阻塞等等。
假如当前正在运行的脚本已经执行过写操作,那么即使执行SCRIPT KILL,也无法将它杀死,因为这是违反 Lua 脚本的原子性执行原则的。在这种情况下,唯一可行的办法是使用SHUTDOWN NOSAVE命令,通过停止整个 Redis 进程来停止脚本的运行,并防止不完整(half-written)的信息被写入数据库中。

使用redis-cli客户端执行lua文件
-- 这个命令等价于 set key1 argv1 EX argv2
-- 比如下面这个栗子,设置age是18过期时间是60
-- set age 18 EX 60
redis.call('SET',KEYS[1],ARGV[1])
redis.call('EXPIRE',KEYS[1],ARGV[3])

redis.call('SET',KEYS[2],ARGV[2])
redis.call('EXPIRE',KEYS[2],ARGV[3])
return 1
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli  --eval /Users/zhangguofu/website/goproject/script.lua name age , jimy  18 60
(integer) 1
在这里插入图片描述
在go中使用lua
--用户id
local userId    = tostring(KEYS[1])
--订单集合
local orderSet=tostring(KEYS[2])
-- 商品库存key
local goodsTotal=tostring(ARGV[1])
--订单队列
local orderList=tostring(ARGV[2])

-- 是否已经抢购到了,如果是返回
local hasBuy = tonumber(redis.call("sIsMember", orderSet, userId))
if hasBuy ~= 0 then
    return 0
end

-- 库存的数量
local total=tonumber(redis.call("GET", goodsTotal))
--return total
-- 是否已经没有库存了,如果是返回
if total <= 0 then
    return 0
end

-- 可以下单
local flag

-- 增加至订单队列
flag = redis.call("LPUSH", orderList, userId)

-- 增加至用户集合
flag = redis.call("SADD", orderSet, userId)

-- 库存数减1
flag = redis.call("DECR", goodsTotal)
-- 返回当时缓存的数量
return total

--[[


--  多行注释
]]
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli  --eval /Users/zhangguofu/website/goproject/lua-case/script.lua sxxxxxx orderSet , goodsTotal orderList
(integer) 100
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli  --eval /Users/zhangguofu/website/goproject/lua-case/script.lua sxxxxx1 orderSet , goodsTotal orderList
(integer) 99
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli  --eval /Users/zhangguofu/website/goproject/lua-case/script.lua sxxxxx2 orderSet , goodsTotal orderList
(integer) 98
package main

import (
    "fmt"
    "github.com/go-redis/redis/v8"
    "io/ioutil"
    "log"
    "net/http"
    "sync"
)

const orderSet = "orderSet"     //用户id的集合
const goodsTotal = "goodsTotal" //商品库存的key
const orderList = "orderList"   //订单队列
func createScript() *redis.Script {
    str, err := ioutil.ReadFile("./lua-case/script.lua")
    if err != nil {
        fmt.Println("Script read error", err)
        log.Println(err)
    }
    scriptStr := fmt.Sprintf("%s", str)
    script := redis.NewScript(scriptStr)
    return script
}

func evalScript(client *redis.Client, userId string, wg *sync.WaitGroup) {
    defer wg.Done()
    script := createScript()
    //fmt.Printf("%+v",script)
    //return
    sha, err := script.Load(client.Context(), client).Result()
    if err != nil {
        log.Fatalln(err)
    }
    ret := client.EvalSha(client.Context(), sha, []string{
        userId,
        orderSet,
    }, []string{
        goodsTotal,
        orderList,
    })
    if result, err := ret.Result(); err != nil {
        log.Fatalf("Execute Redis fail: %v", err.Error())
    } else {
        total:=result.(int64)
        if total==0{
            fmt.Printf("userid: %s, 什么都没抢到 \n", userId)
        }else{
            fmt.Printf("userid: %s 抢到了, 库存: %d \n", userId, total)

        }
    }
}

func main() {
    http.HandleFunc("/", addOrder)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

func addOrder(w http.ResponseWriter, r *http.Request) {
    var wg sync.WaitGroup
    wg.Add(1)
    client := getRedis()

    defer r.Body.Close()
    defer client.Close()

    r.ParseForm()
    uid := r.FormValue("uid")

    go evalScript(client, uid, &wg)
    wg.Wait()
}

func getRedis() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr: "127.0.0.1:6379",
    })
    return client
}

127.0.0.1:6379> set goodsTotal 100
OK
127.0.0.1:6379> get goodsTotal
"0"
127.0.0.1:6379> keys *
1) "goodsTotal"
2) "orderSet"
3) "orderList"
127.0.0.1:6379> llen orderList
(integer) 100
127.0.0.1:6379> scard orderSet
(integer) 100
127.0.0.1:6379>  
上一篇下一篇

猜你喜欢

热点阅读