大转盘
- 固定几个奖品,不同的中奖概率或者总数量限制
- 每一次转动抽奖,后端计算出这次抽奖的中奖情况,并返回对应的奖品信息
在测试项目目录下创建main.go文件,内容如下:
/**
* 大转盘程序
* curl http://localhost:8080/
* curl http://localhost:8080/debug
* curl http://localhost:8080/prize
* 固定几个奖品,不同的中奖概率或者总数量限制
* 每一次转动抽奖,后端计算出这次抽奖的中奖情况,并返回对应的奖品信息
*
* 线程不安全,因为获奖概率低,并发更新库存的冲突很少能出现,不容易发现线程安全性问题
* 压力测试:
* wrk -t10 -c100 -d5 "http://localhost:8080/prize"
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"strings"
"time"
"math/rand"
)
// 奖品中奖概率
type Prate struct {
Rate int // 万分之N的中奖概率
Total int // 总数量限制,0 表示无限数量
CodeA int // 中奖概率起始编码(包含)
CodeB int // 中奖概率终止编码(包含)
Left int // 剩余数
}
// 奖品列表
var prizeList []string = []string{
"一等奖,火星单程船票",
"二等奖,凉飕飕南极之旅",
"三等奖,iPhone一部",
"", // 没有中奖
}
// 奖品的中奖概率设置,与上面的 prizeList 对应的设置
var rateList []Prate = []Prate{
Prate{1, 1, 0, 0, 1},
Prate{2, 2, 1, 2, 2},
Prate{5, 10, 3, 5, 10},
Prate{100,0, 0, 9999, 0},
}
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
// http://localhost:8080
app.Run(iris.Addr(":8080"))
}
// 抽奖的控制器
type lotteryController struct {
Ctx iris.Context
}
// GET http://localhost:8080/
func (c *lotteryController) Get() string {
c.Ctx.Header("Content-Type", "text/html")
return fmt.Sprintf("大转盘奖品列表:<br/> %s", strings.Join(prizeList, "<br/>\n"))
}
// GET http://localhost:8080/prize
func (c *lotteryController) GetPrize() string {
c.Ctx.Header("Content-Type", "text/html")
// 第一步,抽奖,根据随机数匹配奖品
seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed))
// 得到个人的抽奖编码
code := r.Intn(10000)
//fmt.Println("GetPrize code=", code)
var myprize string
var prizeRate *Prate
// 从奖品列表中匹配,是否中奖
for i, prize := range prizeList {
rate := &rateList[i]
if code >= rate.CodeA && code <= rate.CodeB {
// 满足中奖条件
myprize = prize
prizeRate = rate
break
}
}
if myprize == "" {
// 没有中奖
myprize = "很遗憾,再来一次"
return myprize
}
// 第二步,发奖,是否可以发奖
if prizeRate.Total == 0 {
// 无限奖品
fmt.Println("中奖:", myprize)
return myprize
} else if prizeRate.Left > 0 {
// 还有剩余奖品
prizeRate.Left -= 1
fmt.Println("中奖:", myprize)
return myprize
} else {
// 有限且没有剩余奖品,无法发奖
myprize = "很遗憾,再来一次"
return myprize
}
}
// GET http://localhost:8080/debug
func (c *lotteryController) GetDebug() string {
c.Ctx.Header("Content-Type", "text/html")
return fmt.Sprintf("获奖概率: %v \n", rateList)
}
把运行起来:
[root@localhost lottery]# go run _deamon/6wheel/main.go
Now listening on: http://localhost:8080
Application started. Press CTRL+C to shut down.
访问首页,获奖概率那些:
[root@localhost Work]# curl "http://localhost:8080/"
大转盘奖品列表:<br/> 一等奖,火星单程船票<br/>
二等奖,凉飕飕南极之旅<br/>
三等奖,iPhone一部<br/>
[root@localhost Work]# curl "http://localhost:8080/debug"
获奖概率: [{1 1 0 0 1} {2 2 1 2 2} {5 10 3 5 10} {100 0 0 9999 0}]
试试抽奖:
[root@localhost Work]# curl "http://localhost:8080/prize"
很遗憾,再来一次
[root@localhost Work]# curl "http://localhost:8080/prize"
很遗憾,再来一次
[root@localhost Work]# curl "http://localhost:8080/prize"
很遗憾,再来一次
[root@localhost Work]# curl "http://localhost:8080/prize"
很遗憾,再来一次
[root@localhost Work]# curl "http://localhost:8080/prize"
很遗憾,再来一次
[root@localhost Work]# curl "http://localhost:8080/prize"
很遗憾,再来一次
当前抽奖存在并发问题,先进行压测看看效果,在进行压测前做一些修改
奖品列表只保留一个奖品,并设置中奖概率:
var rateList []Prate = []Prate{
Prate{100, 1000, 0, 9999, 1000},
//Prate{1, 1, 0, 0, 1},
//Prate{2, 2, 1, 2, 2},
//Prate{5, 10, 3, 5, 10},
//Prate{100,0, 0, 9999, 0},
}
加入日志打印:
...
var logger *log.Logger
//初始化日志
func initLog() {
f, _ := os.Create("/var/log/lottery_demo.log")
logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
}
......
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
initLog()
return app
}
......
if prizeRate.Total == 0 {
// 无限奖品
fmt.Println("中奖:", myprize)
saveLuckData(myprize)
return myprize
} else if prizeRate.Left > 0 {
// 还有剩余奖品
prizeRate.Left -= 1
fmt.Println("中奖:", myprize)
saveLuckData(myprize)
return myprize
} else {
// 有限且没有剩余奖品,无法发奖
myprize = "很遗憾,再来一次"
return myprize
}
......
func saveLuckData(prize string) {
logger.Printf("lucky, prize=%s", prize)
}
启动程序:
[root@localhost lottery]#
[root@localhost lottery]# go run _deamon/6wheel/main.go
Now listening on: http://localhost:8080
Application started. Press CTRL+C to shut down.
清空日志并压测抽奖:
[root@localhost vagrant]# > /var/log/lottery_demo.log
[root@localhost vagrant]# wc -l /var/log/lottery_demo.log
0 /var/log/lottery_demo.log
[root@localhost vagrant]# /usr/local/bin/wrk -t10 -c100 -d5 "http://localhost:8080/prize"
Running 5s test @ http://localhost:8080/prize
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 5.55ms 4.62ms 38.60ms 76.30%
Req/Sec 2.06k 491.62 5.17k 75.80%
103334 requests in 5.07s, 13.80MB read
Requests/sec: 20362.61
Transfer/sec: 2.72MB
[root@localhost vagrant]# wc -l /var/log/lottery_demo.log
1001 /var/log/lottery_demo.log
加上互斥锁:
......
var mu sync.Mutex = sync.Mutex{}
......
mu.Lock()
prizeRate.Left -= 1
defer mu.Unlock()
fmt.Println("中奖: ", myprize)
......
再次测试就看不到多出来的中奖数量了,或者可以采取第二种方法,使用go的原子操作。
原子操作即是进行过程中不能被中断的操作。也就是说,针对某个值的原子操作在被进行的过程当中,CPU绝不会再去进行其它的针对该值的操作。无论这些其它的操作是否为原子操作都会是这样。为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成。只有这样才能够在并发环境下保证原子操作的绝对安全。
修改代码如下:
......
type Prate struct {
Rate int // 万分之N的中奖概率
Total int // 总数量限制,0 表示无限数量
CodeA int // 中奖概率起始编码(包含)
CodeB int // 中奖概率终止编码(包含)
//Left int // 剩余数
Left *int32 // 剩余数
}
......
var giftLeft = int32(1000)
// 奖品的中奖概率设置,与上面的 prizeList 对应的设置
var rateList []Prate = []Prate{
Prate{100, 1000, 0, 9999, &giftLeft},
//Prate{1, 1, 0, 0, 1},
//Prate{2, 2, 1, 2, 2},
//Prate{5, 10, 3, 5, 10},
//Prate{100,0, 0, 9999, 0},
}
......
func (c *lotteryController) GetPrize() string {
c.Ctx.Header("Content-Type", "text/html")
// 第一步,抽奖,根据随机数匹配奖品
seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed))
// 得到个人的抽奖编码
code := r.Intn(10000)
//fmt.Println("GetPrize code=", code)
var myprize string
var prizeRate *Prate
// 从奖品列表中匹配,是否中奖
for i, prize := range prizeList {
rate := &rateList[i]
if code >= rate.CodeA && code <= rate.CodeB {
// 满足中奖条件
myprize = prize
prizeRate = rate
break
}
}
if myprize == "" {
// 没有中奖
myprize = "很遗憾,再来一次"
return myprize
}
// 第二步,发奖,是否可以发奖
if prizeRate.Total == 0 {
// 无限奖品
fmt.Println("中奖: ", myprize)
saveLuckData(myprize)
return myprize
} else if *prizeRate.Left > 0 {
// 还有剩余奖品
left := atomic.AddInt32(prizeRate.Left, -1)
if left >= 0 {
fmt.Println("中奖: ", myprize)
saveLuckData(myprize)
return myprize
}
}
// 有限且没有剩余奖品,无法发奖
myprize = "很遗憾,再来一次"
return myprize
}
......
网友评论