美文网首页
iris 抽奖实例3

iris 抽奖实例3

作者: 码农工号9527 | 来源:发表于2021-08-09 19:37 被阅读0次

微信摇一摇
特点:

  • 种类多、数量多
  • 随机匹配奖品,针对虚拟物品、实物,不一样的规则
  • 中奖后,减库存,记录并提示用户。需要考虑线程安全性问题
    在测试项目目录下创建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/


访问抽奖: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基本不在改变了,说明抽奖逻辑那边开销会比较大一些。

相关文章

  • iris 抽奖实例3

    微信摇一摇特点: 种类多、数量多 随机匹配奖品,针对虚拟物品、实物,不一样的规则 中奖后,减库存,记录并提示用户。...

  • iris 抽奖实例6

    大转盘 固定几个奖品,不同的中奖概率或者总数量限制 每一次转动抽奖,后端计算出这次抽奖的中奖情况,并返回对应的奖品...

  • iris 抽奖实例2

    1. 简单实现抽奖 安装并创建好iris测试项目在测试项目目录下创建main.go文件,内容如下 启动: 刮刮乐类...

  • iris 抽奖实例5

    微博抢红包在测试项目目录下创建main.go文件,内容如下: 启动: curl设置红包并抽红包: 查看剩余红包数量...

  • iris 抽奖实例1

    1. 简单实现抽奖 安装并创建好iris测试项目在测试项目目录下创建main.go文件,内容如下 测试请求当前总共...

  • iris 抽奖实例4

    支付宝集福卡 奖品是虚拟的5个福字,没数量限制 先识别图片,确定福字的获得概率 不存在线程安全问题在测试项目目录下...

  • 第六十九章 使用 REST API 监控 IRIS

    第六十九章 使用 REST API 监控 IRIS 每个 IRIS® 数据平台实例都包含一个提供实例统计信息的 R...

  • golang iris mvc框架的服务端加载过程

    整个iris框架共三层结构: 应用的配置和注册信息,如路由、中间件、日志。 中间的服务端实例,从iris实例拿配置...

  • 第六十七章 使用 Web 服务监控 IRIS - 监控 Web

    第六十七章 使用 Web 服务监控 IRIS - 监控 Web 服务的 URL 对于给定的 IRIS 实例,日志...

  • 第七十章 使用 REST API 监控 IRIS - 互操作性指

    第七十章 使用 REST API 监控 IRIS - 互操作性指标 除了上一节中描述的指标外, IRIS 实例还可...

网友评论

      本文标题:iris 抽奖实例3

      本文链接:https://www.haomeiwen.com/subject/apsivltx.html