微信摇一摇
特点:
- 种类多、数量多
- 随机匹配奖品,针对虚拟物品、实物,不一样的规则
- 中奖后,减库存,记录并提示用户。需要考虑线程安全性问题
在测试项目目录下创建main.go文件,内容如下
/**
* 微信摇一摇
* 基础功能
* /lucky 只有一个抽奖接口
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"log"
"math/rand"
"os"
"time"
)
// 奖品类型,枚举值 iota 从0开始
const (
giftTypeCoin = iota //虚拟币
giftTypeCoupon //不同券
giftTypeCouponFix //相同券
giftTypeRealSmall //实物小奖
giftTypeRealLarge //实物大奖
)
// 奖品对象
type gift struct {
id int //奖品标识
name string //奖品名称
pic string //奖品图片
link string //奖品链接
gtype int //奖品类型
data string //奖品数据(特定的配置信息)
datalist []string //奖品数据集合(不同的优惠券的编码)
total int //总数, 0 不限量
left int //剩余数量
inuse bool // 是否使用中
rate int //中将概率,万分之N,0~9999
rateMin int //大于等于最小中奖编码
rateMax int //小于等于最大中奖编码
}
//最大的中奖号码
const rateMax = 10000
var logger *log.Logger
//抽奖前预先配置的奖品列表
var giftList []*gift
type lotterController struct {
Ctx iris.Context
}
//初始化日志
func initLog() {
f, _ := os.Create("/var/log/lottery_demo.log")
logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
}
//初始化奖品列表
func initGift() {
giftList = make([]*gift, 5) //定义五个奖品
g1 := gift{
id: 1,
name: "手机大奖",
pic: "",
link: "",
gtype: giftTypeRealLarge,
data: "",
datalist: nil,
total: 1000,
left: 1000,
inuse: true,
rate: 10000,
rateMin: 0,
rateMax: 0,
}
giftList[0] = &g1
g2 := gift{
id: 2,
name: "充电器",
pic: "",
link: "",
gtype: giftTypeRealSmall,
data: "",
datalist: nil,
total: 5,
left: 5,
inuse: true,
rate: 100,
rateMin: 0,
rateMax: 0,
}
giftList[1] = &g2
g3 := gift{
id: 3,
name: "优惠券满200减50元",
pic: "",
link: "",
gtype: giftTypeCouponFix,
data: "mall-coupon-2018",
datalist: nil,
total: 5,
left: 5,
inuse: true,
rate: 5000,
rateMin: 0,
rateMax: 0,
}
giftList[2] = &g3
g4 := gift{
id: 4,
name: "直降优惠券50元",
pic: "",
link: "",
gtype: giftTypeCoupon,
data: "",
datalist: []string{"c01", "c02", "c03", "c04", "c05"},
total: 5,
left: 5,
inuse: true,
rate: 2000,
rateMin: 0,
rateMax: 0,
}
giftList[3] = &g4
g5 := gift{
id: 5,
name: "金币",
pic: "",
link: "",
gtype: giftTypeCoin,
data: "10金币",
datalist: nil,
total: 5,
left: 5,
inuse: true,
rate: 2000,
rateMin: 0,
rateMax: 0,
}
giftList[4] = &g5
//数据整理,中奖区间数据
rateStart := 0
for _, data := range giftList {
if !data.inuse {
continue
}
data.rateMin = rateStart
data.rateMax = rateStart + data.rate
if data.rateMax >= rateMax {
data.rateMax = rateMax
rateStart = 0
} else {
rateStart += data.rate
}
}
}
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotterController{})
initLog()
initGift()
return app
}
func main() {
app := newApp()
app.Run(iris.Addr(":8080"))
}
//奖品数量的信息 GET http://localhost:8080/
func (c *lotterController) Get() string {
count := 0
total := 0
for _, data := range giftList {
if data.inuse && (data.total == 0 || (data.total > 0 && data.left > 0)) {
count++
total += data.left
}
}
return fmt.Sprintf("当前有效奖品种类数量:%d,限量奖品总数量=%d", count, total)
}
//抽奖 GET http://localhost:8080/lucky
func (c *lotterController) GetLucky() map[string]interface{} {
code := luckyCode()
ok := false
result := make(map[string]interface{})
result["success"] = ok
for _, data := range giftList {
if !data.inuse || (data.total > 0 && data.left <= 0) {
continue
}
if data.rateMin <= int(code) && data.rateMax > int(code) {
// 中奖了,抽奖编码在奖品编码范围内
// 开始发奖
sendData := ""
fmt.Print("4\n")
switch data.gtype {
case giftTypeCoin:
ok, sendData = sendCoin(data)
case giftTypeCoupon:
ok, sendData = sendCoupon(data)
case giftTypeCouponFix:
ok, sendData = sendCouponFix(data)
case giftTypeRealSmall:
ok, sendData = sendRealSmall(data)
case giftTypeRealLarge:
ok, sendData = sendCoinRealLarge(data)
}
if ok {
//中奖后,成功得到奖品
//生成中奖记录
saveLuckData(code, data.id, data.name, data.link, sendData, data.left)
result["success"] = ok
result["id"] = data.id
result["name"] = data.name
result["link"] = data.link
result["data"] = sendData
break
}
}
}
return result
}
func luckyCode() int32 {
seed := time.Now().UnixNano()
code := rand.New(rand.NewSource(seed)).Int31n(int32(rateMax))
return code
}
func sendCoin(data *gift) (bool, string) {
if data.total == 0 {
//数量无限
return true, data.data
} else if data.left > 0 {
//还有剩余
data.left--
return true, data.data
} else {
return false, "奖品已发完"
}
}
//不同值的优惠券
func sendCoupon(data *gift) (bool, string) {
if data.left > 0 {
//还有剩余
left := data.left - 1
data.left = left
return true, data.datalist[left]
} else {
return false, "奖品已发完"
}
}
//固定的优惠券
func sendCouponFix(data *gift) (bool, string) {
if data.total == 0 {
//数量无限
return true, data.data
} else if data.left > 0 {
//还有剩余
data.left--
return true, data.data
} else {
return false, "奖品已发完"
}
}
//实物小奖
func sendRealSmall(data *gift) (bool, string) {
if data.total == 0 {
//数量无限
return true, data.data
} else if data.left > 0 {
//还有剩余
data.left--
return true, data.data
} else {
return false, "奖品已发完"
}
}
//实物大奖
func sendCoinRealLarge(data *gift) (bool, string) {
if data.total == 0 {
//数量无限
return true, data.data
} else if data.left > 0 {
//还有剩余
data.left--
return true, data.data
} else {
return false, "奖品已发完"
}
}
// 记录用户的获奖信息
func saveLuckData(code int32, id int, name string, link string, sendData string, left int) {
logger.Printf("lucky, code=%d, gift=%d, name=%s, link=%s, data=%s, left=%d",
code, id, name, link, sendData, left)
}
访问抽奖:http://localhost:8080/lucky
由于实物大奖手机中奖概率配置的是10000,百分之百中奖
因此我们来改改中奖概率,改成限量10部,万分之10
g1 := gift{
id: 1,
name: "手机大奖",
pic: "",
link: "",
gtype: giftTypeRealLarge,
data: "",
datalist: nil,
total: 10,
left: 10,
inuse: true,
rate: 10,
rateMin: 0,
rateMax: 0,
}
重新运行项目,再次抽奖,发现概率已经变了,抽到奖品如下:
为了使抽奖概率合理,再次调整参数:
g1 := gift{
id: 1,
name: "手机大奖",
pic: "",
link: "",
gtype: giftTypeRealLarge,
data: "",
datalist: nil,
total: 2,
left: 2,
inuse: true,
rate: 1,
rateMin: 0,
rateMax: 0,
}
giftList[0] = &g1
g2 := gift{
id: 2,
name: "充电器",
pic: "",
link: "",
gtype: giftTypeRealSmall,
data: "",
datalist: nil,
total: 5,
left: 5,
inuse: true,
rate: 10,
rateMin: 0,
rateMax: 0,
}
giftList[1] = &g2
g3 := gift{
id: 3,
name: "优惠券满200减50元",
pic: "",
link: "",
gtype: giftTypeCouponFix,
data: "mall-coupon-2018",
datalist: nil,
total: 50,
left: 50,
inuse: true,
rate: 500,
rateMin: 0,
rateMax: 0,
}
giftList[2] = &g3
g4 := gift{
id: 4,
name: "直降优惠券50元",
pic: "",
link: "",
gtype: giftTypeCoupon,
data: "",
datalist: []string{"c01", "c02", "c03", "c04", "c05"},
total: 10,
left: 10,
inuse: true,
rate: 100,
rateMin: 0,
rateMax: 0,
}
giftList[3] = &g4
g5 := gift{
id: 5,
name: "金币",
pic: "",
link: "",
gtype: giftTypeCoin,
data: "10金币",
datalist: nil,
total: 5,
left: 5,
inuse: true,
rate: 5000,
rateMin: 0,
rateMax: 0,
}
访问首页:
第一次抽中金币:
第二次抽中金币:
第三次未中奖:
第四次抽中金币:
再次访问首页,发现奖品数量已经减三了:
在运行过程中可能会出现报错
数组越界,因为gift id为4的datalist是
[]string{"c01", "c02", "c03", "c04", "c05"}
,而它的total设置了10,超过了datalist数量上限了,所以报错。其取值代码如下:
func sendCoupon(data *gift) (bool, string) {
if data.left > 0 {
//还有剩余
left := data.left - 1
data.left = left
return true, data.datalist[left]
} else {
return false, "奖品已发完"
}
}
做下取值判断,不存在时取它本身实际的最大值就好
开头说到,代码其实存在线程不安全的问题,比如数量为5个的奖品,用户抽到的数量可能达到七个八个这样超过配置的奖品数量。为了方便测试,将手机实物大奖total和left设置为20000,rate设置为10000,其他奖品的inuse都置为false。当前使用wrk进行压测,安装如下:
[root@localhost vagrant]# cd /data/Work/
[root@localhost Work]# git clone https://github.com/wg/wrk.git
Cloning into 'wrk'...
remote: Enumerating objects: 1103, done.
remote: Total 1103 (delta 0), reused 0 (delta 0), pack-reused 1103
Receiving objects: 100% (1103/1103), 37.84 MiB | 2.47 MiB/s, done.
Resolving deltas: 100% (343/343), done.
[root@localhost Work]# cd wrk
[root@localhost wrk]# make
......
CC src/aprintf.c
CC src/stats.c
CC src/script.c
CC src/units.c
CC src/ae.c
CC src/zmalloc.c
CC src/http_parser.c
LUAJIT src/wrk.lua
cc -std=c99 -Wall -O2 -D_REENTRANT -D_POSIX_C_SOURCE=200112L -D_BSD_SOURCE -D_DEFAULT_SOURCE -Iobj/include -Iobj/include/luajit-2.1 -c -o obj/bytecode.o obj/bytecode.c
LINK wrk
[root@localhost wrk]# ln -s /data/Work/wrk/wrk /usr/local/bin
测试访问首页:
点两次抽奖,再次访问首页:
查看日志:
[root@localhost log]# pwd
/var/log
[root@localhost log]# wc -l lottery_demo.log
2 lottery_demo.log
[root@localhost log]# more lottery_demo.log
2021/08/09 19:11:33.853586 lucky, code=3818, gift=1, name=手机大奖, link=, data=, left=19999
2021/08/09 19:11:34.628168 lucky, code=8982, gift=1, name=手机大奖, link=, data=, left=19998
清空日志,并进行wrk压力测试:
[root@localhost wrk]# wc -l /var/log/lottery_demo.log
0 /var/log/lottery_demo.log
[root@localhost wrk]# /usr/local/bin/wrk -t15 -c15 -d10 http://localhost:8080/lucky
Running 10s test @ http://localhost:8080/lucky
15 threads and 15 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.80ms 2.53ms 34.89ms 88.46%
Req/Sec 0.95k 246.37 1.99k 67.87%
142802 requests in 10.09s, 20.89MB read
Requests/sec: 14150.74
Transfer/sec: 2.07MB
[root@localhost wrk]# wc -l /var/log/lottery_demo.log
20002 /var/log/lottery_demo.log
发现日志有20002条,正常应该为20000条,超发了两条,线程不安全。为了解决这个问题,加上互斥锁去处理它:
......
//抽奖前预先配置的奖品列表
var giftList []*gift
var mu sync.Mutex //设置互斥锁
......
//抽奖 GET http://localhost:8080/lucky
func (c *lotterController) GetLucky() map[string]interface{} {
mu.Lock() //上锁
defer mu.Unlock() //执行完后解锁
code := luckyCode()
ok := false
result := make(map[string]interface{})
......
清空日志,再次重启应用,进行压测:
[root@localhost wrk]# > /var/log/lottery_demo.log
[root@localhost wrk]# wc -l /var/log/lottery_demo.log
0 /var/log/lottery_demo.log
[root@localhost wrk]# /usr/local/bin/wrk -t15 -c15 -d10 http://localhost:8080/lucky
Running 10s test @ http://localhost:8080/lucky
15 threads and 15 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.79ms 2.45ms 27.22ms 88.30%
Req/Sec 0.94k 259.46 2.40k 70.12%
141254 requests in 10.10s, 20.68MB read
Requests/sec: 13986.88
Transfer/sec: 2.05MB
[root@localhost wrk]# wc -l /var/log/lottery_demo.log
20000 /var/log/lottery_demo.log
发现数量就正常了,可以多压测几次看看效果。
在处理完线程安全性问题后,可以调试下手机奖品数量,调成200,再次压测,可以发现qps可以有些许的提升
[root@localhost wrk]# /usr/local/bin/wrk -t15 -c15 -d10 http://localhost:8080/lucky
Running 10s test @ http://localhost:8080/lucky
15 threads and 15 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.69ms 2.35ms 27.78ms 88.92%
Req/Sec 0.97k 223.10 2.02k 72.53%
145804 requests in 10.10s, 20.17MB read
Requests/sec: 14437.12
Transfer/sec: 2.00MB
[root@localhost wrk]# /usr/local/bin/wrk -t15 -c15 -d10 http://localhost:8080/lucky
Running 10s test @ http://localhost:8080/lucky
15 threads and 15 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.77ms 2.51ms 29.10ms 88.51%
Req/Sec 0.97k 244.48 2.04k 69.67%
145343 requests in 10.10s, 20.10MB read
Requests/sec: 14391.13
Transfer/sec: 1.99MB
再次修改手机奖品数量为2后压测:
[root@localhost wrk]# /usr/local/bin/wrk -t15 -c15 -d10 http://localhost:8080/lucky
Running 10s test @ http://localhost:8080/lucky
15 threads and 15 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.73ms 2.40ms 27.86ms 88.52%
Req/Sec 0.97k 250.99 2.08k 72.80%
146179 requests in 10.09s, 20.21MB read
Requests/sec: 14483.02
Transfer/sec: 2.00MB
[root@localhost wrk]# /usr/local/bin/wrk -t15 -c15 -d10 http://localhost:8080/lucky
Running 10s test @ http://localhost:8080/lucky
15 threads and 15 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.71ms 2.39ms 28.15ms 88.97%
Req/Sec 0.97k 239.73 2.23k 70.38%
145512 requests in 10.08s, 20.12MB read
Requests/sec: 14433.61
Transfer/sec: 2.00MB
发现qps基本不在改变了,说明抽奖逻辑那边开销会比较大一些。
网友评论