美文网首页
iris 抽奖实例5

iris 抽奖实例5

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

    微博抢红包
    在测试项目目录下创建main.go文件,内容如下:

    /**
     * 微博抢红包
     * 两个步骤
     * 1 抢红包,设置红包总金额,红包个数,返回抢红包的地址
     * curl "http://localhost:8080/set?uid=1&money=100&num=100"
     * 2 抢红包,先到先得,随机得到红包金额
     * curl "http://localhost:8080/get?id=1&uid=1"
     * 注意:
     * 线程不安全1,红包列表 packageList map 的并发读写会产生异常
     * 测试方法: wrk -t10 -c10 -d5  "http://localhost:8080/set?uid=1&money=100&num=10"
     * fatal error: concurrent map writes
     * 线程不安全2,红包里面的金额切片 packageList map[uint32][]uint 并发读写不安全,虽然不会报错
     */
    package main
    
    import (
        "fmt"
        "github.com/kataras/iris/v12"
        "github.com/kataras/iris/v12/mvc"
        "log"
        "math/rand"
        "os"
        "time"
    )
    // 文件日志
    var logger *log.Logger
    // 当前有效红包列表,int64是红包唯一ID,[]uint是红包里面随机分到的金额(单位分)
    var packageList map[uint32][]uint = make(map[uint32][]uint)
    
    func main() {
        app := newApp()
        app.Run(iris.Addr(":8080"))
    }
    
    // 初始化Application
    func newApp() *iris.Application {
        app := iris.New()
        mvc.New(app.Party("/")).Handle(&lotteryController{})
        return app
    }
    
    // 初始化日志
    func initLog() {
        f, _ := os.Create("/var/logs/lottery_demo.log")
        logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
    }
    
    // 抽奖的控制器
    type lotteryController struct {
        Ctx iris.Context
    }
    
    // 返回全部红包地址
    // GET http://localhost:8080/
    func (c *lotteryController) Get() map[uint32][2]int {
        rs := make(map[uint32][2]int)
        for id, list := range packageList {
            var money int
            for _, v := range list {
                money += int(v)
            }
            rs[id] = [2]int{len(list),money}
        }
        return rs
    }
    
    // 发红包
    // GET http://localhost:8080/set?uid=1&money=100&num=100
    func (c *lotteryController) GetSet() string {
        uid, errUid := c.Ctx.URLParamInt("uid")
        money, errMoney := c.Ctx.URLParamFloat64("money")
        num, errNum := c.Ctx.URLParamInt("num")
        if errUid != nil || errMoney != nil || errNum != nil {
            return fmt.Sprintf("参数格式异常,errUid=%s, errMoney=%s, errNum=%s\n", errUid, errMoney, errNum)
        }
        moneyTotal := int(money * 100)
        if uid < 1 || moneyTotal < num || num < 1 {
            return fmt.Sprintf("参数数值异常,uid=%d, money=%d, num=%d\n", uid, money, num)
        }
        // 金额分配算法
        leftMoney := moneyTotal
        leftNum := num
        // 分配的随机数
        r := rand.New(rand.NewSource(time.Now().UnixNano()))
        rMax := 0.55 // 随机分配最大比例
        list := make([]uint, num)
        // 大循环开始,只要还有没分配的名额,继续分配
        for leftNum > 0 {
            if leftNum == 1 {
                // 最后一个名额,把剩余的全部给它
                list[num-1] = uint(leftMoney)
                break
            }
            // 剩下的最多只能分配到1分钱时,不用再随机
            if leftMoney == leftNum {
                for i:=num-leftNum; i < num ; i++ {
                    list[i] = 1
                }
                break
            }
            // 每次对剩余金额的1%-55%随机,最小1,最大就是剩余金额55%(需要给剩余的名额留下1分钱的生存空间)
            rMoney := int(float64(leftMoney-leftNum) * rMax)
            m := r.Intn(rMoney)
            if m < 1 {
                m = 1
            }
            list[num-leftNum] = uint(m)
            leftMoney -= m
            leftNum--
        }
        // 最后再来一个红包的唯一ID
        id := r.Uint32()
        packageList[id] = list
        // 返回抢红包的URL
        return fmt.Sprintf("/get?id=%d&uid=%d&num=%d\n", id, uid, num)
    }
    
    // 抢红包
    // GET http://localhost:8080/get?id=1&uid=1
    func (c *lotteryController) GetGet() string {
        uid, errUid := c.Ctx.URLParamInt("uid")
        id, errId := c.Ctx.URLParamInt("id")
        if errUid != nil || errId != nil {
            return fmt.Sprintf("参数格式异常,errUid=%s, errId=%s\n", errUid, errId)
        }
        if uid < 1 || id < 1 {
            return fmt.Sprintf("参数数值异常,uid=%d, id=%d\n", uid, id)
        }
        list, ok := packageList[uint32(id)]
        if !ok || len(list) < 1 {
            return fmt.Sprintf("红包不存在,id=%d\n", id)
        }
        // 分配的随机数
        r := rand.New(rand.NewSource(time.Now().UnixNano()))
        // 从红包金额中随机得到一个
        i := r.Intn(len(list))
        money := list[i]
        // 更新红包列表中的信息
        if len(list) > 1 {
            if i == len(list) - 1 {
                packageList[uint32(id)] = list[:i]
            } else if i == 0 {
                packageList[uint32(id)] = list[1:]
            } else {
                packageList[uint32(id)] = append(list[:i], list[i+1:]...)
            }
        } else {
            delete(packageList, uint32(id))
        }
        return fmt.Sprintf("恭喜你抢到一个红包,金额为:%d\n", money)
    }
    

    启动:

    [root@localhost lottery]# go run _deamon/5weiboRedPacket/main.go 
    Now listening on: http://localhost:8080
    Application started. Press CTRL+C to shut down.
    
    

    curl设置红包并抽红包:

    [root@localhost Work]# curl "http://localhost:8080/set?uid=1&money=100&num=100"
    /get?id=1620842754&uid=1&num=100
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1300
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    ......
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:4
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:16
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    

    查看剩余红包数量和金额:

    [root@localhost Work]# curl "http://localhost:8080/"
    {
     "1620842754": [
      76,
      6046
     ]
    }
    

    抽到红包金额为空时:

    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/"
    {}
    [root@localhost Work]# curl "http://localhost:8080/get?id=1620842754&uid=1"
    红包不存在,id=1620842754
    

    可以发现抽到1的居多,抽奖不均,为了使抽奖金额均匀点,将rMax分配的更细致一些,加入一些条件判断:

    ......
        rMax := 0.55 // 随机分配最大比例
        if num > 1000 {
            rMax = 0.01
        } else if num > 100 {
            rMax = 0.1
        } else if num > 10 {
            rMax = 0.3
        }
    ......
    

    重启再次尝试设置红包并抽奖:

    [root@localhost Work]# curl "http://localhost:8080/set?uid=1&money=100&num=100"
    /get?id=686930033&uid=1&num=100
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:6
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:385
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:12
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:92
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:4
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:1600
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:1
    [root@localhost Work]# curl "http://localhost:8080/get?id=686930033&uid=1"
    恭喜你抢到一个红包,金额为:1
    ......
    

    发现红包金额已经变得均匀了一些了。

    在未作任何并发性问题优化的条件下,先尝试进行压力测试看看结果:

    [root@localhost Work]# /usr/local/bin/wrk -t10 -c10 -d5  "http://localhost:8080/set?uid=1&money=100&num=10"
    Running 5s test @ http://localhost:8080/set?uid=1&money=100&num=10
      10 threads and 10 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   683.77us    1.10ms  18.78ms   95.54%
        Req/Sec     1.11k   639.36     1.92k    61.54%
      2939 requests in 5.10s, 426.89KB read
      Socket errors: connect 0, read 10, write 714018, timeout 0
    Requests/sec:    576.03
    Transfer/sec:     83.67KB
    

    终端看到一堆报错:

    [root@localhost lottery]# go run _deamon/5weiboRedPacket/main.go 
    Now listening on: http://localhost:8080
    Application started. Press CTRL+C to shut down.
    fatal error: concurrent map writes
    
    goroutine 85 [running]:
    runtime.throw(0xbc24d2, 0x15)
        /usr/local/go/src/runtime/panic.go:1117 +0x72 fp=0xc000239470 sp=0xc000239440 pc=0x438032
    runtime.mapassign_fast32(0xaf2d20, 0xc0004df1a0, 0xcfbdcabc, 0xc000789950)
        /usr/local/go/src/runtime/map_fast32.go:101 +0x33e fp=0xc0002394b0 sp=0xc000239470 pc=0x4138be
    main.(*lotteryController).GetSet(0xc0005416a0, 0x0, 0x0)
        /data/Work/lottery/_deamon/5weiboRedPacket/main.go:121 +0x425 fp=0xc0002395d8 sp=0xc0002394b0 pc=0xa66ea5
    runtime.call32(0xc00005c240, 0xc000011808, 0xc0007d90e0, 0x800000018)
        /usr/local/go/src/runtime/asm_amd64.s:551 +0x3e fp=0xc000239608 sp=0xc0002395d8 pc=0x46cafe
    reflect.Value.call(0xb08a00, 0xc0005416a0, 0xa13, 0xbb41b8, 0x4, 0x10e4aa0, 0x0, 0x0, 0x3, 0xc000239880, ...)
        /usr/local/go/src/reflect/value.go:476 +0x8e7 fp=0xc000239810 sp=0xc000239608 pc=0x49a887
    reflect.Value.Call(0xb08a00, 0xc0005416a0, 0xa13, 0x10e4aa0, 0x0, 0x0, 0xa13, 0xe, 0x0)
        /usr/local/go/src/reflect/value.go:337 +0xb9 fp=0xc000239890 sp=0xc000239810 pc=0x499d59
    github.com/kataras/iris/v12/mvc.(*ControllerActivator).handlerOf.func2(0xcd0310, 0xc000764120)
        /data/GOPATH/pkg/mod/github.com/kataras/iris/v12@v12.1.8/mvc/controller.go:497 +0x3cb fp=0xc000239988 sp=0xc000239890 pc=0xa65e4b
    github.com/kataras/iris/v12/context.Do(0xcd0310, 0xc000764120, 0xc000011ad0, 0x1, 0x1)
        /data/GOPATH/pkg/mod/github.com/kataras/iris/v12@v12.1.8/context/context.go:1030 +0x82 fp=0xc0002399b8 sp=0xc000239988 pc=0x8b0f82
    github.com/kataras/iris/v12/context.(*context).Do(0xc000764120, 0xc000011ad0, 0x1, 0x1)
        /data/GOPATH/pkg/mod/github.com/kataras/iris/v12@v12.1.8/context/context.go:1217 +0x55 fp=0xc0002399f0 sp=0xc0002399b8 pc=0x8b1455
    github.com/kataras/iris/v12/core/router.(*routerHandler).HandleRequest(0xc0004ec240, 0xcd0310, 0xc000764120)
        /data/GOPATH/pkg/mod/github.com/kataras/iris/v12@v12.1.8/core/router/handler.go:250 +0x55f fp=0xc000239ae8 sp=0xc0002399f0 pc=0x91a19f
    github.com/kataras/iris/v12/core/router.(*Router).BuildRouter.func1(0xcbfa88, 0xc000287340, 0xc000542c00)
        /data/GOPATH/pkg/mod/github.com/kataras/iris/v12@v12.1.8/core/router/router.go:135 +0x8f fp=0xc000239b48 sp=0xc000239ae8 pc=0x92466f
    github.com/kataras/iris/v12/core/router.(*Router).ServeHTTP(0xc000081d10, 0xcbfa88, 0xc000287340, 0xc000542c00)
        /data/GOPATH/pkg/mod/github.com/kataras/iris/v12@v12.1.8/core/router/router.go:227 +0x48 fp=0xc000239b70 sp=0xc000239b48 pc=0x91fc88
    net/http.serverHandler.ServeHTTP(0xc0004f2000, 0xcbfa88, 0xc000287340, 0xc000542c00)
        /usr/local/go/src/net/http/server.go:2887 +0xa3 fp=0xc000239ba0 sp=0xc000239b70 pc=0x7089a3
    net/http.(*conn).serve(0xc0000e6280, 0xcc16c0, 0xc00043cbc0)
        /usr/local/go/src/net/http/server.go:1952 +0x8cd fp=0xc000239fc8 sp=0xc000239ba0 pc=0x703ecd
    runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc000239fd0 sp=0xc000239fc8 pc=0x46e4c1
    created by net/http.(*Server).Serve
        /usr/local/go/src/net/http/server.go:3013 +0x39b
    
    goroutine 1 [IO wait]:
    internal/poll.runtime_pollWait(0x7f81ef9f5538, 0x72, 0x0)
        /usr/local/go/src/runtime/netpoll.go:222 +0x55
    internal/poll.(*pollDesc).wait(0xc0004af098, 0x72, 0x0, 0x0, 0xbb73a7)
        /usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x45
    internal/poll.(*pollDesc).waitRead(...)
    ......
    

    看到fatal error: concurrent map writes的相关错误,是因为线程不安全,可以考虑加互斥锁,或者将packageList改为sync.map类型去处理,保证线程的安全性。然后packageList里[]uint切边也存在安全性问题,可以加入任务队列去处理它,改造后的代码如下:

    /**
     * 微博抢红包
     * 两个步骤
     * 1 抢红包,设置红包总金额,红包个数,返回抢红包的地址
     * curl "http://localhost:8080/set?uid=1&money=100&num=100"
     * 2 抢红包,先到先得,随机得到红包金额
     * curl "http://localhost:8080/get?id=1&uid=1"
     * 注意:
     * 线程不安全1,红包列表 packageList map 的并发读写会产生异常
     * 测试方法: wrk -t10 -c10 -d5  "http://localhost:8080/set?uid=1&money=100&num=10"
     * fatal error: concurrent map writes
     * 线程不安全2,红包里面的金额切片 packageList map[uint32][]uint 并发读写不安全,虽然不会报错
     */
    package main
    
    import (
        "fmt"
        "github.com/kataras/iris/v12"
        "github.com/kataras/iris/v12/mvc"
        "log"
        "math/rand"
        "os"
        "sync"
        "time"
    )
    
    // 文件日志
    var logger *log.Logger
    // 当前有效红包列表,int64是红包唯一ID,[]uint是红包里面随机分到的金额(单位分)
    //var packageList map[uint32][]uint = make(map[uint32][]uint)
    var packageList *sync.Map = new(sync.Map)
    var chTasks chan task = make(chan task)
    
    func main() {
        app := newApp()
        app.Run(iris.Addr(":8080"))
    }
    
    // 初始化Application
    func newApp() *iris.Application {
        app := iris.New()
        mvc.New(app.Party("/")).Handle(&lotteryController{})
    
        initLog()
        go fetchPackageMoney()
        return app
    }
    
    // 初始化日志
    func initLog() {
        f, _ := os.Create("/var/log/lottery_demo.log")
        logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
    }
    
    // 单线程死循环,专注处理各个红包中金额切片的数据更新(移除指定位置的金额)
    func fetchPackageMoney() {
        for {
            t := <-chTasks
            // 分配的随机数
            r := rand.New(rand.NewSource(time.Now().UnixNano()))
            id := t.id
            l, ok := packageList.Load(id)
            if ok && l != nil {
                list := l.([]uint)
                // 从红包金额中随机得到一个
                i := r.Intn(len(list))
                money := list[i]
                //if i == len(list) - 1 {
                //  packageList[uint32(id)] = list[:i]
                //} else if i == 0 {
                //  packageList[uint32(id)] = list[1:]
                //} else {
                //  packageList[uint32(id)] = append(list[:i], list[i+1:]...)
                //}
                if len(list) > 1 {
                    if i == len(list) - 1 {
                        packageList.Store(uint32(id), list[:i])
                    } else if i == 0 {
                        packageList.Store(uint32(id), list[1:])
                    } else {
                        packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
                    }
                } else {
                    //delete(packageList, uint32(id))
                    packageList.Delete(uint32(id))
                }
                // 回调channel返回
                t.callback <- money
            } else {
                t.callback <- 0
            }
        }
    }
    // 任务结构
    type task struct {
        id uint32
        callback chan uint
    }
    
    // 抽奖的控制器
    type lotteryController struct {
        Ctx iris.Context
    }
    
    // 返回全部红包地址
    // GET http://localhost:8080/
    func (c *lotteryController) Get() map[uint32][2]int {
        rs := make(map[uint32][2]int)
        //for id, list := range packageList {
        //  var money int
        //  for _, v := range list {
        //      money += int(v)
        //  }
        //  rs[id] = [2]int{len(list),money}
        //}
        packageList.Range(func(key, value interface{}) bool {
            id := key.(uint32)
            list := value.([]uint)
            var money int
            for _, v := range list {
                money += int(v)
            }
            rs[id] = [2]int{len(list),money}
            return true
        })
        return rs
    }
    
    // 发红包
    // GET http://localhost:8080/set?uid=1&money=100&num=100
    func (c *lotteryController) GetSet() string {
        uid, errUid := c.Ctx.URLParamInt("uid")
        money, errMoney := c.Ctx.URLParamFloat64("money")
        num, errNum := c.Ctx.URLParamInt("num")
        if errUid != nil || errMoney != nil || errNum != nil {
            return fmt.Sprintf("参数格式异常,errUid=%s, errMoney=%s, errNum=%s\n", errUid, errMoney, errNum)
        }
        moneyTotal := int(money * 100)
        if uid < 1 || moneyTotal < num || num < 1 {
            return fmt.Sprintf("参数数值异常,uid=%d, money=%d, num=%d\n", uid, money, num)
        }
        // 金额分配算法
        leftMoney := moneyTotal
        leftNum := num
        // 分配的随机数
        r := rand.New(rand.NewSource(time.Now().UnixNano()))
        // 随机分配最大比例
        rMax := 0.55
        if num >= 1000 {
            rMax = 0.01
        }else if num >= 100 {
            rMax = 0.1
        } else if num >= 10 {
            rMax = 0.3
        }
        list := make([]uint, num)
        // 大循环开始,只要还有没分配的名额,继续分配
        for leftNum > 0 {
            if leftNum == 1 {
                // 最后一个名额,把剩余的全部给它
                list[num-1] = uint(leftMoney)
                break
            }
            // 剩下的最多只能分配到1分钱时,不用再随机
            if leftMoney == leftNum {
                for i:=num-leftNum; i < num ; i++ {
                    list[i] = 1
                }
                break
            }
            // 每次对剩余金额的1%-55%随机,最小1,最大就是剩余金额55%(需要给剩余的名额留下1分钱的生存空间)
            rMoney := int(float64(leftMoney-leftNum) * rMax)
            m := r.Intn(rMoney)
            if m < 1 {
                m = 1
            }
            list[num-leftNum] = uint(m)
            leftMoney -= m
            leftNum--
        }
        // 最后再来一个红包的唯一ID
        id := r.Uint32()
        //packageList[id] = list
        packageList.Store(id, list)
        // 返回抢红包的URL
        return fmt.Sprintf("/get?id=%d&uid=%d&num=%d\n", id, uid, num)
    }
    
    // 抢红包
    // GET http://localhost:8080/get?id=1&uid=1
    func (c *lotteryController) GetGet() string {
        uid, errUid := c.Ctx.URLParamInt("uid")
        id, errId := c.Ctx.URLParamInt("id")
        if errUid != nil || errId != nil {
            return fmt.Sprintf("参数格式异常,errUid=%s, errId=%s\n", errUid, errId)
        }
        if uid < 1 || id < 1 {
            return fmt.Sprintf("参数数值异常,uid=%d, id=%d\n", uid, id)
        }
        //list, ok := packageList[uint32(id)]
        l, ok := packageList.Load(uint32(id))
        if !ok {
            return fmt.Sprintf("红包不存在,id=%d\n", id)
        }
        list := l.([]uint)
        if len(list) < 1 {
            return fmt.Sprintf("红包不存在,id=%d\n", id)
        }
        // 更新红包列表中的信息(移除这个金额),构造一个任务
        callback := make(chan uint)
        t := task{id: uint32(id), callback: callback}
        // 把任务发送给channel
        chTasks <- t
        // 回调的channel等待处理结果
        money := <- callback
        if money <= 0 {
            fmt.Println(uid, "很遗憾,没能抢到红包")
            return fmt.Sprintf("很遗憾,没能抢到红包\n")
        } else {
            fmt.Println(uid, "抢到一个红包,金额为:", money)
            logger.Printf("weiboReadPacket success uid=%d, id=%d, money=%d\n", uid, id, money)
            return fmt.Sprintf("恭喜你抢到一个红包,金额为:%d\n", money)
        }
    }
    

    重启,再次设置红包:

    [root@localhost Work]# curl "http://localhost:8080/set?uid=1&money=10000&num=100000"
    /get?id=3595445127&uid=1&num=100000
    

    压测:

    [root@localhost Work]# /usr/local/bin/wrk -t10 -c10 -d5  "http://localhost:8080/get?id=3595445127&uid=1"
    Running 5s test @ http://localhost:8080/get?id=3595445127&uid=1
      10 threads and 10 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     2.17ms    2.79ms  35.62ms   88.36%
        Req/Sec   700.46    158.28     1.27k    66.80%
      35116 requests in 5.07s, 5.33MB read
    Requests/sec:   6932.81
    Transfer/sec:      1.05MB
    

    可以看到有35116个请求,也就是有35116个人抢了红包,查看剩余:

    [root@localhost Work]# curl "http://localhost:8080/"
    {
     "3595445127": [
      64884,
      649010
     ]
    }
    

    还有64884个红包,64884+35116刚好为100000

    优化:
    上述队列任务是单个队列进行的,可以将它改成16个队列去处理,修改后的代码如下:

    /**
     * 微博抢红包
     * 两个步骤
     * 1 抢红包,设置红包总金额,红包个数,返回抢红包的地址
     * curl "http://localhost:8080/set?uid=1&money=100&num=100"
     * 2 抢红包,先到先得,随机得到红包金额
     * curl "http://localhost:8080/get?id=1&uid=1"
     * 注意:
     * 线程不安全1,红包列表 packageList map 的并发读写会产生异常
     * 测试方法: wrk -t10 -c10 -d5  "http://localhost:8080/set?uid=1&money=100&num=10"
     * fatal error: concurrent map writes
     * 线程不安全2,红包里面的金额切片 packageList map[uint32][]uint 并发读写不安全,虽然不会报错
     * 优化 channel 的吞吐量,启动多个处理协程来执行 channel 的消费
     */
    package main
    
    import (
        "fmt"
        "github.com/kataras/iris/v12"
        "github.com/kataras/iris/v12/mvc"
        "log"
        "math/rand"
        "os"
        "sync"
        "time"
    )
    
    // 文件日志
    var logger *log.Logger
    // 当前有效红包列表,int64是红包唯一ID,[]uint是红包里面随机分到的金额(单位分)
    //var packageList map[uint32][]uint = make(map[uint32][]uint)
    var packageList *sync.Map = new(sync.Map)
    //var chTasks chan task = make(chan task)
    const taskNum = 16
    var chTaskList []chan task = make([]chan task, taskNum)
    
    func main() {
        app := newApp()
        app.Run(iris.Addr(":8080"))
    }
    
    // 初始化Application
    func newApp() *iris.Application {
        app := iris.New()
        mvc.New(app.Party("/")).Handle(&lotteryController{})
    
        initLog()
        for i:=0; i<taskNum; i++ {
            chTaskList[i] = make(chan task)
            go fetchPackageMoney(chTaskList[i])
        }
        return app
    }
    
    // 初始化日志
    func initLog() {
        f, _ := os.Create("/var/log/lottery_demo.log")
        logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
    }
    
    // 单线程死循环,专注处理各个红包中金额切片的数据更新(移除指定位置的金额)
    func fetchPackageMoney(chTasks chan task) {
        for {
            t := <-chTasks
            // 分配的随机数
            r := rand.New(rand.NewSource(time.Now().UnixNano()))
            id := t.id
            l, ok := packageList.Load(id)
            if ok && l != nil {
                list := l.([]uint)
                // 从红包金额中随机得到一个
                i := r.Intn(len(list))
                money := list[i]
                //if i == len(list) - 1 {
                //  packageList[uint32(id)] = list[:i]
                //} else if i == 0 {
                //  packageList[uint32(id)] = list[1:]
                //} else {
                //  packageList[uint32(id)] = append(list[:i], list[i+1:]...)
                //}
                if len(list) > 1 {
                    if i == len(list) - 1 {
                        packageList.Store(uint32(id), list[:i])
                    } else if i == 0 {
                        packageList.Store(uint32(id), list[1:])
                    } else {
                        packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
                    }
                } else {
                    //delete(packageList, uint32(id))
                    packageList.Delete(uint32(id))
                }
                // 回调channel返回
                t.callback <- money
            } else {
                t.callback <- 0
            }
        }
    }
    // 任务结构
    type task struct {
        id uint32
        callback chan uint
    }
    
    // 抽奖的控制器
    type lotteryController struct {
        Ctx iris.Context
    }
    
    // 返回全部红包地址
    // GET http://localhost:8080/
    func (c *lotteryController) Get() map[uint32][2]int {
        rs := make(map[uint32][2]int)
        //for id, list := range packageList {
        //  var money int
        //  for _, v := range list {
        //      money += int(v)
        //  }
        //  rs[id] = [2]int{len(list),money}
        //}
        packageList.Range(func(key, value interface{}) bool {
            id := key.(uint32)
            list := value.([]uint)
            var money int
            for _, v := range list {
                money += int(v)
            }
            rs[id] = [2]int{len(list),money}
            return true
        })
        return rs
    }
    
    // 发红包
    // GET http://localhost:8080/set?uid=1&money=100&num=100
    func (c *lotteryController) GetSet() string {
        uid, errUid := c.Ctx.URLParamInt("uid")
        money, errMoney := c.Ctx.URLParamFloat64("money")
        num, errNum := c.Ctx.URLParamInt("num")
        if errUid != nil || errMoney != nil || errNum != nil {
            return fmt.Sprintf("参数格式异常,errUid=%s, errMoney=%s, errNum=%s\n", errUid, errMoney, errNum)
        }
        moneyTotal := int(money * 100)
        if uid < 1 || moneyTotal < num || num < 1 {
            return fmt.Sprintf("参数数值异常,uid=%d, money=%d, num=%d\n", uid, money, num)
        }
        // 金额分配算法
        leftMoney := moneyTotal
        leftNum := num
        // 分配的随机数
        r := rand.New(rand.NewSource(time.Now().UnixNano()))
        // 随机分配最大比例
        rMax := 0.55
        if num >= 1000 {
            rMax = 0.01
        }else if num >= 100 {
            rMax = 0.1
        } else if num >= 10 {
            rMax = 0.3
        }
        list := make([]uint, num)
        // 大循环开始,只要还有没分配的名额,继续分配
        for leftNum > 0 {
            if leftNum == 1 {
                // 最后一个名额,把剩余的全部给它
                list[num-1] = uint(leftMoney)
                break
            }
            // 剩下的最多只能分配到1分钱时,不用再随机
            if leftMoney == leftNum {
                for i:=num-leftNum; i < num ; i++ {
                    list[i] = 1
                }
                break
            }
            // 每次对剩余金额的1%-55%随机,最小1,最大就是剩余金额55%(需要给剩余的名额留下1分钱的生存空间)
            rMoney := int(float64(leftMoney-leftNum) * rMax)
            m := r.Intn(rMoney)
            if m < 1 {
                m = 1
            }
            list[num-leftNum] = uint(m)
            leftMoney -= m
            leftNum--
        }
        // 最后再来一个红包的唯一ID
        id := r.Uint32()
        //packageList[id] = list
        packageList.Store(id, list)
        // 返回抢红包的URL
        return fmt.Sprintf("/get?id=%d&uid=%d&num=%d\n", id, uid, num)
    }
    
    // 抢红包
    // GET http://localhost:8080/get?id=1&uid=1
    func (c *lotteryController) GetGet() string {
        uid, errUid := c.Ctx.URLParamInt("uid")
        id, errId := c.Ctx.URLParamInt("id")
        if errUid != nil || errId != nil {
            return fmt.Sprintf("参数格式异常,errUid=%s, errId=%s\n", errUid, errId)
        }
        if uid < 1 || id < 1 {
            return fmt.Sprintf("参数数值异常,uid=%d, id=%d\n", uid, id)
        }
        //list, ok := packageList[uint32(id)]
        l, ok := packageList.Load(uint32(id))
        if !ok {
            return fmt.Sprintf("红包不存在,id=%d\n", id)
        }
        list := l.([]uint)
        if len(list) < 1 {
            return fmt.Sprintf("红包不存在,id=%d\n", id)
        }
        // 更新红包列表中的信息(移除这个金额),构造一个任务
        callback := make(chan uint)
        t := task{id: uint32(id), callback: callback}
        // 把任务发送给channel
        chTasks := chTaskList[id % taskNum]
        chTasks <- t
        // 回调的channel等待处理结果
        money := <- callback
        if money <= 0 {
            fmt.Println(uid, "很遗憾,没能抢到红包")
            return fmt.Sprintf("很遗憾,没能抢到红包\n")
        } else {
            fmt.Println(uid, "抢到一个红包,金额为:", money)
            logger.Printf("weiboReadPacket success uid=%d, id=%d, money=%d\n", uid, id, money)
            return fmt.Sprintf("恭喜你抢到一个红包,金额为:%d\n", money)
        }
    }
    

    相关文章

      网友评论

          本文标题:iris 抽奖实例5

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