GoLang抽獎(jiǎng)系統(tǒng)簡易實(shí)現(xiàn)流程
業(yè)務(wù)難點(diǎn)
設(shè)計(jì)一個(gè)抽獎(jiǎng)系統(tǒng),這個(gè)系統(tǒng)并不是具體化,是抽象化,具有以下的幾個(gè)難點(diǎn):
1、抽獎(jiǎng)業(yè)務(wù)需要 復(fù)雜多變
2、獎(jiǎng)品類型和概率設(shè)置
3、公平的抽獎(jiǎng)和安全的發(fā)獎(jiǎng)
4、并發(fā)安全性問題 一個(gè)人不能槍多次
5、高效的抽獎(jiǎng)和發(fā)獎(jiǎng),提供高并發(fā)和性能
6、 如何使用redies進(jìn)行優(yōu)化
技術(shù)選項(xiàng)
- 高并發(fā) Go 協(xié)程優(yōu)先于 PHP多進(jìn)程,Java的 多線程模型
- 高性能編譯后的二進(jìn)制優(yōu)先于PHP解釋性和Java虛擬機(jī)
- 高效的網(wǎng)絡(luò)模型 epoll 模型優(yōu)先于PHPBIO模型和Java NIO模型
抽獎(jiǎng)活動(dòng)
- 年會(huì)抽獎(jiǎng),彩票刮獎(jiǎng),微信搖一搖,抽獎(jiǎng)大轉(zhuǎn)盤,集??ǖ然顒?dòng),本項(xiàng)目以 抽獎(jiǎng)大轉(zhuǎn)盤作為一種活動(dòng)進(jìn)行設(shè)計(jì)。
- 項(xiàng)目實(shí)戰(zhàn)內(nèi)容: 框架/核心代碼后臺(tái)功能 ,合理設(shè)置獎(jiǎng)品和發(fā)送獎(jiǎng)品, mysql+優(yōu)化-使用redies 發(fā)獎(jiǎng)計(jì)劃與獎(jiǎng)品池, 壓力測試和更多的運(yùn)營策略(系統(tǒng)的性能有更好的了解,運(yùn)營產(chǎn)品的策略有更多), 引入 thirft 框架(RPC 框架), 設(shè)計(jì)接口生成代碼, 服務(wù)端接口和客戶端程序
需求分析
1. go mod 配置
2. 配置國內(nèi)代理: go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
3. go get -u -v github.com/kataras/iris 下載包在 GOPATH的PKG目錄下
4. iris:功能: 安全認(rèn)證,緩存 cookies 文件 MVC, 模板 豐富的示例代碼
5. https://iris-go.com/v10/recipe
*年會(huì)抽獎(jiǎng)程序
使用的是iris 這個(gè)web 框架 進(jìn)行處理
/**
* curl http://localhost:8080/
* curl --data "users=123,567" http://localhost:8080/import
* curl http://localhost:8080/lucky
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"math/rand"
"strings"
"sync"
"time"
)
var userList []string // 共享變量讀寫前后 需要增加 鎖的設(shè)定 簡單方式添加互斥鎖
var mu sync.Mutex
type lotteryController struct {
Ctx iris.Context
}
// 啟動(dòng)一個(gè) iris 應(yīng)用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
userList = []string{}
mu = sync.Mutex{}
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error: %s\n", err))
return
}
}
func (c *lotteryController) Get() string {
count := len(userList)
return fmt.Sprintf("當(dāng)前總共參與抽獎(jiǎng)的用戶數(shù):%d\n", count)
}
// PostImport POST http://localhost:8090/import
// params : users
func (c *lotteryController) PostImport() string {
strUsers := c.Ctx.FormValue("users")
users := strings.Split(strUsers, ",")
// 批量線程導(dǎo)入時(shí)候 發(fā)現(xiàn)有多線程的問題 數(shù)據(jù)統(tǒng)計(jì)不正確
mu.Lock()
defer mu.Unlock()
count1 := len(userList)
for _, u := range users {
u = strings.TrimSpace(u)
if len(u) > 0 {
userList = append(userList, u)
}
}
count2 := len(userList)
return fmt.Sprintf("當(dāng)前總共參與抽獎(jiǎng)的用戶數(shù):%d, 成功導(dǎo)入的用戶數(shù):%d\n", count2, count2-count1)
}
// GetLucky GET http://localhost:8090/lucky
func (c *lotteryController) GetLucky() string {
// 抽獎(jiǎng)地方進(jìn)行鎖的判斷
mu.Lock()
defer mu.Unlock()
count := len(userList)
if count > 1 {
index := rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(int32(count))
user := userList[index]
// 需要 刪除被挑選過的人 直接可以刪除 當(dāng)前index 下的數(shù)據(jù) 更好
userList = append(userList[0:index], userList[index+1:]...)
return fmt.Sprintf("當(dāng)前中獎(jiǎng)用戶:%s, 剩余用戶數(shù):%d\n", user, count-1)
} else if count == 1 {
user := userList[0]
return fmt.Sprintf("當(dāng)前中獎(jiǎng)用戶:%s, 剩余用戶數(shù):%d\n", user, count-1)
} else {
return fmt.Sprintf("當(dāng)前中獎(jiǎng)完畢,沒有用戶參與中獎(jiǎng)\n")
}
}單元測試問題,對于 userList 的 多線程下發(fā)生數(shù)據(jù)競爭問題 :
package main
import (
"fmt"
"github.com/kataras/iris/v12/httptest"
"sync"
"testing"
)
func TestMVC(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
// 使用同步等待鎖
var wg sync.WaitGroup
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("當(dāng)前總共參與抽獎(jiǎng)的用戶數(shù):0\n")
for i := 0; i < 100; i++ {
wg.Add(1)
// 不會(huì)出現(xiàn)協(xié)程并發(fā)性問題
go func(i int) {
defer wg.Done()
e.POST("/import").WithFormField("users", fmt.Sprintf("test_u%d", i)).Expect().Status(httptest.StatusOK)
}(i)
}
wg.Wait()
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("當(dāng)前總共參與抽獎(jiǎng)的用戶數(shù):100\n")
e.GET("/lucky").Expect().Status(httptest.StatusOK)
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("當(dāng)前總共參與抽獎(jiǎng)的用戶數(shù):99\n")
}微信搖一搖得抽獎(jiǎng)活動(dòng)
/**
* 微信搖一搖得功能
* wrk -t10 -c10 -d5 http://localhost:8080/lucky 進(jìn)行壓力測試 查看代碼是否有競爭異常問題和 接口請求量速度
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"log"
"math/rand"
"os"
"sync"
"time"
)
var mu sync.Mutex
const (
giftTypeCoin = iota // 虛擬幣
giftTypeCoupon // 不同卷
giftTypeCouponFix // 不同卷
giftTypeRealSmall // 實(shí)物小獎(jiǎng)
giftTypeRealLarge // 十五大獎(jiǎng)
)
type gift struct {
id int
name string
pic string
link string
gType int
data string // 獎(jiǎng)品數(shù)據(jù)(特定得配置信息)
dataList []string
total int
left int
inuse bool
rate int // 萬分之N
rateMin int
rateMax int
}
// 最大中獎(jiǎng)號碼
const rateMax = 1000
var logger *log.Logger
// 獎(jiǎng)品類表
var giftList []*gift
type lotteryController struct {
Ctx iris.Context
}
// 啟動(dòng)一個(gè) iris 應(yīng)用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func initLog() {
f, _ := os.Create("G:\\goLandProject\\lottery_demo.log")
logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
}
func initGift() {
giftList = make([]*gift, 5)
g1 := gift{
id: 1,
name: "手機(jī)大獎(jiǎng)",
pic: "",
link: "",
gType: giftTypeRealLarge,
data: "",
dataList: nil,
total: 20000,
left: 20000,
inuse: true,
rate: 10000,
rateMin: 0,
rateMax: 0,
}
g2 := gift{
id: 2,
name: "充電器",
pic: "",
link: "",
gType: giftTypeRealSmall,
data: "",
dataList: nil,
total: 5,
left: 5,
inuse: false,
rate: 10,
rateMin: 0,
rateMax: 0,
}
g3 := gift{
id: 3,
name: "優(yōu)惠卷滿200減50",
pic: "",
link: "",
gType: giftTypeCouponFix,
data: "mall-coupon-2018",
dataList: nil,
total: 50,
left: 50,
inuse: false,
rate: 500,
rateMin: 0,
rateMax: 0,
}
g4 := gift{
id: 4,
name: "直降優(yōu)惠卷",
pic: "",
link: "",
gType: giftTypeCoupon,
data: "",
dataList: []string{"c01", "c02", "c03", "c04", "c05"},
total: 50,
left: 50,
inuse: false,
rate: 100,
rateMin: 0,
rateMax: 0,
}
g5 := gift{
id: 5,
name: "金幣",
pic: "",
link: "",
gType: giftTypeCoin,
data: "10金幣",
dataList: nil,
total: 100,
left: 100,
inuse: false,
rate: 5000,
rateMin: 0,
rateMax: 0,
}
giftList[0] = &g1
giftList[1] = &g2
giftList[2] = &g3
giftList[3] = &g4
giftList[4] = &g5
// s數(shù)據(jù)整理 中獎(jiǎng)區(qū)間數(shù)據(jù)
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 main() {
initLog()
initGift()
mu = sync.Mutex{}
app := newApp()
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get http://localhost:8080
func (c *lotteryController) 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āng)前有效獎(jiǎng)品種類數(shù)量:%d, 限量獎(jiǎng)品總數(shù)量:%d\n", count, total)
}
// GetLucky http://localhost:8080/lucky
func (c *lotteryController) GetLucky() map[string]interface{} {
mu.Lock()
defer mu.Unlock()
code := luckyCode()
ok := false
result := make(map[string]interface{})
result["success"] = ok
// 對 code 與 rateMin -rateMax 區(qū)間內(nèi)進(jìn)行對比 判斷是否獲獎(jiǎng)
for _, data := range giftList {
if !data.inuse || (data.total > 0 && data.left <= 0) {
continue
}
if data.rateMin <= int(code) && data.rateMax > int(code) {
sendData := ""
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 = sendRealLarge(data)
}
if ok {
// 中獎(jiǎng)后得到獎(jiǎng)品 生成中獎(jiǎng)記錄
saveLuckyData(code, data, sendData)
result["success"] = true
result["id"] = data.id
result["name"] = data.name
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 {
// 數(shù)量無數(shù)
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "獎(jiǎng)品已經(jīng)發(fā)完"
}
}
// 不同優(yōu)惠卷
func sendCoupon(data *gift) (bool, string) {
if data.left > 0 {
left := data.left - 1
data.left = left
return true, data.dataList[left%5]
} else {
return false, "獎(jiǎng)品已經(jīng)發(fā)完"
}
}
func sendCouponFix(data *gift) (bool, string) {
if data.total == 0 {
// 數(shù)量無數(shù)
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "獎(jiǎng)品已經(jīng)發(fā)完"
}
}
func sendRealSmall(data *gift) (bool, string) {
if data.total == 0 {
// 數(shù)量無數(shù)
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "獎(jiǎng)品已經(jīng)發(fā)完"
}
}
func sendRealLarge(data *gift) (bool, string) {
if data.total == 0 {
// 數(shù)量無數(shù)
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "獎(jiǎng)品已經(jīng)發(fā)完"
}
}
func saveLuckyData(code int32, g *gift, data string) {
logger.Printf("lucky, code =%d ,id =%d, name =%d, data =%s, left=%d \n", code, g.id, g.name, data, g.left)
}微博搶紅包
- 紅包得集合,紅包內(nèi)紅包數(shù)量讀寫都存在并發(fā)安全性問題
- 第一種方式 使用 Sync.Map 互斥鎖得方式
/**
* 微信搶紅包 普通得 map 發(fā)生 競爭情況 所以需要使用 互斥 sync.Map
* 在大量得寫 和 讀得情況下會(huì)發(fā)生 競爭
*
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"math/rand"
"sync"
"time"
)
// 紅包列表
var packageList *sync.Map = new(sync.Map)
type lotteryController struct {
Ctx iris.Context
}
// 啟動(dòng)一個(gè) iris 應(yīng)用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
// 返回當(dāng)前全部得紅包
rs := make(map[uint32][2]int)
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
}
// GetSet http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
uid, errUid := c.Ctx.URLParamInt("uid")
moeny, errMoney := c.Ctx.URLParamFloat64("money")
num, errNum := c.Ctx.URLParamInt("num")
if errUid != nil || errNum != nil || errMoney != nil {
fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
}
moenyTotal := int(moeny * 100)
if uid < 1 || moenyTotal < num || num < 1 {
return fmt.Sprintf("參數(shù)數(shù)值異常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
}
// 金額分配算法
r := rand.New(rand.NewSource(time.Now().UnixNano()))
rMax := 0.55 // 隨機(jī)分配最大值
if num > 1000 {
rMax = 0.01
} else if num < 10 {
rMax = 0.80
}
list := make([]uint, num)
leftMoney := moenyTotal
leftNum := num
for leftNum > 0 {
if leftNum == 1 {
list[num-1] = uint(leftMoney)
break
}
// 剩余錢數(shù)等于剩余紅包數(shù)每個(gè)紅包進(jìn)行均分
if leftMoney == leftNum {
for i := num - leftNum; i < num; i++ {
list[i] = 1
break
}
}
// 隨機(jī)分配最大值
rMoney := int(float64(leftMoney-leftNum) * rMax)
m := r.Intn(rMoney)
if m < 1 {
m = 1
}
list[num-leftNum] = uint(m)
leftMoney -= m
leftNum--
}
// 紅包得UUID
id := r.Uint32()
packageList.Store(id, list)
return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
id, errid := c.Ctx.URLParamInt("id")
uid, errUid := c.Ctx.URLParamInt("uid")
if errUid != nil || errid != nil {
return fmt.Sprintf("")
}
if uid < 1 || id < 1 {
return fmt.Sprintf("")
}
listq, ok := packageList.Load(uint32(id))
list := listq.([]int)
if !ok || len(list) < 1 {
return fmt.Sprintf("紅包不存在, id =%d \n", id)
}
// 分配隨機(jī)數(shù)獲取紅包
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.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 {
packageList.Delete(uint32(id))
}
return fmt.Sprintf("恭喜你搶到一個(gè)紅包, 紅包金額:%d \n", money)
}第二種方式: chan 隊(duì)列方式 解決線程安全
/**
* 微信搶紅包 普通得 map 發(fā)生 競爭情況 所以需要使用 互斥 sync.Map
* 在大量得寫 和 讀得情況下會(huì)發(fā)生 競爭
*
* 單核任務(wù) 修改成 16 核心 進(jìn)行搶紅包
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"math/rand"
"sync"
"time"
)
// 紅包列表
var packageList *sync.Map = new(sync.Map)
type task struct {
id uint32
callback chan uint
}
const taskNum = 16 // 初始化隊(duì)列數(shù)量
var chTaskList []chan task = make([]chan task, taskNum)
type lotteryController struct {
Ctx iris.Context
}
// 啟動(dòng)一個(gè) iris 應(yīng)用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
err := app.Listen(":8080")
// 啟動(dòng)多個(gè)子線程進(jìn)行 紅包搶
for i := 0; i < taskNum; i++ {
chTaskList[i] = make(chan task)
go fetchPackageListMoney(chTaskList[i])
}
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
// 返回當(dāng)前全部得紅包
rs := make(map[uint32][2]int)
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
}
// GetSet http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
uid, errUid := c.Ctx.URLParamInt("uid")
moeny, errMoney := c.Ctx.URLParamFloat64("money")
num, errNum := c.Ctx.URLParamInt("num")
if errUid != nil || errNum != nil || errMoney != nil {
fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
}
moenyTotal := int(moeny * 100)
if uid < 1 || moenyTotal < num || num < 1 {
return fmt.Sprintf("參數(shù)數(shù)值異常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
}
// 金額分配算法
r := rand.New(rand.NewSource(time.Now().UnixNano()))
rMax := 0.55 // 隨機(jī)分配最大值
if num > 1000 {
rMax = 0.01
} else if num < 10 {
rMax = 0.80
}
list := make([]uint, num)
leftMoney := moenyTotal
leftNum := num
for leftNum > 0 {
if leftNum == 1 {
list[num-1] = uint(leftMoney)
break
}
// 剩余錢數(shù)等于剩余紅包數(shù)每個(gè)紅包進(jìn)行均分
if leftMoney == leftNum {
for i := num - leftNum; i < num; i++ {
list[i] = 1
break
}
}
// 隨機(jī)分配最大值
rMoney := int(float64(leftMoney-leftNum) * rMax)
m := r.Intn(rMoney)
if m < 1 {
m = 1
}
list[num-leftNum] = uint(m)
leftMoney -= m
leftNum--
}
// 紅包得UUID
id := r.Uint32()
packageList.Store(id, list)
return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
id, errid := c.Ctx.URLParamInt("id")
uid, errUid := c.Ctx.URLParamInt("uid")
if errUid != nil || errid != nil {
return fmt.Sprintf("")
}
if uid < 1 || id < 1 {
return fmt.Sprintf("")
}
listq, ok := packageList.Load(uint32(id))
list := listq.([]int)
if !ok || len(list) < 1 {
return fmt.Sprintf("紅包不存在, id =%d \n", id)
}
// 構(gòu)造一個(gè)任務(wù)
callback := make(chan uint)
t := task{id: uint32(id), callback: callback}
// 發(fā)送任務(wù)
chTasks := chTaskList[id%taskNum]
chTasks <- t
// 接受返回結(jié)果值
money := <-callback
if money <= 0 {
return "很遺憾,沒有搶到紅包\n"
} else {
return fmt.Sprintf("恭喜你搶到一個(gè)紅包, 紅包金額:%d \n", money)
}
}
// 使用隊(duì)列方式, 需要不斷從chan 通道中獲取數(shù)據(jù)
func fetchPackageListMoney(chTasks chan task) {
for {
t := <-chTasks
id := t.id
l, ok := packageList.Load(id)
if ok && l != nil {
// 分配隨機(jī)數(shù)獲取紅包
list := l.([]int)
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.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 {
packageList.Delete(uint32(id))
}
t.callback <- uint(money)
} else {
t.callback <- 0
}
}
}抽獎(jiǎng)大轉(zhuǎn)盤
后端設(shè)置各個(gè)獎(jiǎng)品得中獎(jiǎng)概率和數(shù)量限制,更新庫存時(shí)候發(fā)現(xiàn)并發(fā)安全性質(zhì)問題 和微信搖一搖 類似
使用CAS進(jìn)行安全代碼進(jìn)行修改,不在使用同步鎖,CAS樂觀鎖比sync.mutSync 會(huì)快一些
/**
* 大轉(zhuǎn)盤程序
* curl http://localhost:8080/
* curl http://localhost:8080/debug
* curl http://localhost:8080/prize
* 固定幾個(gè)獎(jiǎng)品,不同的中獎(jiǎng)概率或者總數(shù)量限制
* 每一次轉(zhuǎn)動(dòng)抽獎(jiǎng),后端計(jì)算出這次抽獎(jiǎng)的中獎(jiǎng)情況,并返回對應(yīng)的獎(jiǎng)品信息
*
* 增加互斥鎖,保證并發(fā)庫存更新的正常
* 壓力測試:
* wrk -t10 -c100 -d5 "http://localhost:8080/prize"
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"log"
"math/rand"
"strings"
"sync/atomic"
"time"
)
// Prate 獎(jiǎng)品中獎(jiǎng)概率
type Prate struct {
Rate int // 萬分之N的中獎(jiǎng)概率
Total int // 總數(shù)量限制,0 表示無限數(shù)量
CodeA int // 中獎(jiǎng)概率起始編碼(包含)
CodeB int // 中獎(jiǎng)概率終止編碼(包含)
Left *int32 // 剩余數(shù)使用CAS樂觀鎖 進(jìn)行修改
}
var left = int32(1000)
// 獎(jiǎng)品列表
var prizeList []string = []string{
"一等獎(jiǎng),火星單程船票",
"二等獎(jiǎng),涼颼颼南極之旅",
"三等獎(jiǎng),iPhone一部",
"", // 沒有中獎(jiǎng)
}
// 獎(jiǎng)品的中獎(jiǎng)概率設(shè)置,與上面的 prizeList 對應(yīng)的設(shè)置
var rateList []Prate = []Prate{
//Prate{1, 1, 0, 0, 1},
//Prate{2, 2, 1, 2, 2},
Prate{5, 1000, 0, 9999, &left},
//Prate{100,0, 0, 9999, 0},
}
type lotteryController struct {
Ctx iris.Context
}
// 啟動(dòng)一個(gè) iris 應(yīng)用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get GET http://localhost:8080/
func (c *lotteryController) Get() string {
c.Ctx.Header("Content-Type", "text/html")
return fmt.Sprintf("大轉(zhuǎn)盤獎(jiǎng)品列表:<br/> %s", strings.Join(prizeList, "<br/>\n"))
}
// GetPrize GET http://localhost:8080/prize
func (c *lotteryController) GetPrize() string {
c.Ctx.Header("Content-Type", "text/html")
// 第一步,抽獎(jiǎng),根據(jù)隨機(jī)數(shù)匹配獎(jiǎng)品
seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed))
// 得到個(gè)人的抽獎(jiǎng)編碼
code := r.Intn(10000)
//fmt.Println("GetPrize code=", code)
var myPrize string
var prizeRate *Prate
// 從獎(jiǎng)品列表中匹配,是否中獎(jiǎng)
for i, prize := range prizeList {
rate := &rateList[i]
if code >= rate.CodeA && code <= rate.CodeB {
// 滿足中獎(jiǎng)條件
myPrize = prize
prizeRate = rate
break
}
}
if myPrize == "" {
// 沒有中獎(jiǎng)
myPrize = "很遺憾,再來一次"
return myPrize
}
// 第二步,發(fā)獎(jiǎng),是否可以發(fā)獎(jiǎng)
if prizeRate.Total == 0 {
// 無限獎(jiǎng)品
fmt.Println("中獎(jiǎng): ", myPrize)
return myPrize
} else if *prizeRate.Left > 0 {
// 還有剩余獎(jiǎng)品
left := atomic.AddInt32(prizeRate.Left, -1)
if left >= 0 {
log.Printf("獎(jiǎng)品:%s", myPrize)
return myPrize
}
}
// 有限且沒有剩余獎(jiǎng)品,無法發(fā)獎(jiǎng)
myPrize = "很遺憾,再來一次"
return myPrize
}
// GetDebug GET http://localhost:8080/debug
func (c *lotteryController) GetDebug() string {
c.Ctx.Header("Content-Type", "text/html")
return fmt.Sprintf("獲獎(jiǎng)概率: %v", rateList)
}抽獎(jiǎng)活動(dòng)總結(jié)
- 并發(fā)安全性質(zhì)問題,互斥鎖,隊(duì)列, CAS遞減方式
- 優(yōu)化,通過散列減小單個(gè)集合得大小
到此這篇關(guān)于GoLang抽獎(jiǎng)系統(tǒng)簡易實(shí)現(xiàn)流程的文章就介紹到這了,更多相關(guān)Go抽獎(jiǎng)系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用gin框架搭建簡易服務(wù)的實(shí)現(xiàn)方法
go語言web框架挺多的,本文就介紹了一下如何使用gin框架搭建簡易服務(wù)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
go?micro微服務(wù)proto開發(fā)安裝及使用規(guī)則
這篇文章主要為大家介紹了go?micro微服務(wù)proto開發(fā)中安裝Protobuf及基本規(guī)范字段的規(guī)則詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
go語言通過反射獲取和設(shè)置結(jié)構(gòu)體字段值的方法
這篇文章主要介紹了go語言通過反射獲取和設(shè)置結(jié)構(gòu)體字段值的方法,實(shí)例分析了Go語言反射的使用技巧,需要的朋友可以參考下2015-03-03
使用golang引入外部包的三種方式:go get, go module, ve
這篇文章主要介紹了使用golang引入外部包的三種方式:go get, go module, vendor目錄,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01

