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
/Users/zhangguofu/website/goproject
go mod init acurd.com/m
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("我啥子都么抢到"))
}
//如果有库存插入到订单表
}
运行jmeter,100个线程,每个线程10c
我们查看一下数据库,发现10个库存是没了。而且订单下了902个,老板哭晕在厕所
而且我们通过结果树发现,有好几个请求返回的结果都是the count i read is 7
说明在同一时刻,有很多线程都读到了库存有7个,那就是说,在同一个时刻,很多进程读到了一样的数据,然后开始了下订单,并最终导致超卖问题。
我们看一下jmeter的聚合报告,对比后面的结果使用
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("我啥子都么抢到"))
}
//如果有库存插入到订单表
}
发现这次没有超卖
但是这样每个线程更新请求的时候都会先去锁表的这一行(悲观锁),更新完库存后再释放锁。导致处理请求速度变慢,接下来我们来看看乐观锁,我们来看一下jmeter的聚合报告
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("我啥子都么抢到"))
}
//如果有库存插入到订单表
}
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
}
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
}
ERR max number of clients reached
原因是我使用完Redis并没有关闭资源connect: can't assign requested addressdial tcp
,这个是因为response没有关闭导致的,所以,小伙伴们一定要记得关闭资源啊!在开始之前,我们先来熟悉一下这几个命令(从 Redis 2.6.12 版本开始支持)
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
我们来看一下代码
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
}
# ------------------------------------------ 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秒
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 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 清除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 杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效。 这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本,或者因为读取的key过大导致阻塞等等。 假如当前正在运行的脚本已经执行过写操作,那么即使执行SCRIPT KILL,也无法将它杀死,因为这是违反 Lua 脚本的原子性执行原则的。在这种情况下,唯一可行的办法是使用SHUTDOWN NOSAVE命令,通过停止整个 Redis 进程来停止脚本的运行,并防止不完整(half-written)的信息被写入数据库中。
完整命令
redis-cli -h host -p port -a password -n db -–eval demo.lua k1 k2 , a1 a2
解释:-h 后面跟远程Redis的ip; -p后面跟远程Redis端口号; -a后面跟密码; -n 后接的参数为选择的redis db;k1 k2 , a1 a2” 的在lua 脚本中获取的方式是使用全局变量KEYS 和ARGV。
我们来举个栗子验证一下
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
--用户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
}
查看代码运行结果
查看Redis结果
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>