GoLang抽獎系統(tǒng)簡易實現(xiàn)流程
業(yè)務難點
設(shè)計一個抽獎系統(tǒng),這個系統(tǒng)并不是具體化,是抽象化,具有以下的幾個難點:
1、抽獎業(yè)務需要 復雜多變
2、獎品類型和概率設(shè)置
3、公平的抽獎和安全的發(fā)獎
4、并發(fā)安全性問題 一個人不能槍多次
5、高效的抽獎和發(fā)獎,提供高并發(fā)和性能
6、 如何使用redies進行優(yōu)化
技術(shù)選項
- 高并發(fā) Go 協(xié)程優(yōu)先于 PHP多進程,Java的 多線程模型
- 高性能編譯后的二進制優(yōu)先于PHP解釋性和Java虛擬機
- 高效的網(wǎng)絡(luò)模型 epoll 模型優(yōu)先于PHPBIO模型和Java NIO模型
抽獎活動
- 年會抽獎,彩票刮獎,微信搖一搖,抽獎大轉(zhuǎn)盤,集福卡等活動,本項目以 抽獎大轉(zhuǎn)盤作為一種活動進行設(shè)計。
- 項目實戰(zhàn)內(nèi)容: 框架/核心代碼后臺功能 ,合理設(shè)置獎品和發(fā)送獎品, mysql+優(yōu)化-使用redies 發(fā)獎計劃與獎品池, 壓力測試和更多的運營策略(系統(tǒng)的性能有更好的了解,運營產(chǎn)品的策略有更多), 引入 thirft 框架(RPC 框架), 設(shè)計接口生成代碼, 服務端接口和客戶端程序
需求分析
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:功能: 安全認證,緩存 cookies 文件 MVC, 模板 豐富的示例代碼
5. https://iris-go.com/v10/recipe
*年會抽獎程序
使用的是iris 這個web 框架 進行處理
/** * 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 } // 啟動一個 iris 應用 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("當前總共參與抽獎的用戶數(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, ",") // 批量線程導入時候 發(fā)現(xiàn)有多線程的問題 數(shù)據(jù)統(tǒng)計不正確 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("當前總共參與抽獎的用戶數(shù):%d, 成功導入的用戶數(shù):%d\n", count2, count2-count1) } // GetLucky GET http://localhost:8090/lucky func (c *lotteryController) GetLucky() string { // 抽獎地方進行鎖的判斷 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] // 需要 刪除被挑選過的人 直接可以刪除 當前index 下的數(shù)據(jù) 更好 userList = append(userList[0:index], userList[index+1:]...) return fmt.Sprintf("當前中獎用戶:%s, 剩余用戶數(shù):%d\n", user, count-1) } else if count == 1 { user := userList[0] return fmt.Sprintf("當前中獎用戶:%s, 剩余用戶數(shù):%d\n", user, count-1) } else { return fmt.Sprintf("當前中獎完畢,沒有用戶參與中獎\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("當前總共參與抽獎的用戶數(shù):0\n") for i := 0; i < 100; i++ { wg.Add(1) // 不會出現(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("當前總共參與抽獎的用戶數(shù):100\n") e.GET("/lucky").Expect().Status(httptest.StatusOK) e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("當前總共參與抽獎的用戶數(shù):99\n") }
微信搖一搖得抽獎活動
/** * 微信搖一搖得功能 * wrk -t10 -c10 -d5 http://localhost:8080/lucky 進行壓力測試 查看代碼是否有競爭異常問題和 接口請求量速度 */ 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 // 實物小獎 giftTypeRealLarge // 十五大獎 ) type gift struct { id int name string pic string link string gType int data string // 獎品數(shù)據(jù)(特定得配置信息) dataList []string total int left int inuse bool rate int // 萬分之N rateMin int rateMax int } // 最大中獎號碼 const rateMax = 1000 var logger *log.Logger // 獎品類表 var giftList []*gift type lotteryController struct { Ctx iris.Context } // 啟動一個 iris 應用 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: "手機大獎", 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ù)整理 中獎區(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("當前有效獎品種類數(shù)量:%d, 限量獎品總數(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)進行對比 判斷是否獲獎 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 { // 中獎后得到獎品 生成中獎記錄 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, "獎品已經(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, "獎品已經(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, "獎品已經(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, "獎品已經(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, "獎品已經(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 * 在大量得寫 和 讀得情況下會發(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 } // 啟動一個 iris 應用 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 { // 返回當前全部得紅包 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 // 隨機分配最大值 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ù)每個紅包進行均分 if leftMoney == leftNum { for i := num - leftNum; i < num; i++ { list[i] = 1 break } } // 隨機分配最大值 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) } // 分配隨機數(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("恭喜你搶到一個紅包, 紅包金額:%d \n", money) }
第二種方式: chan 隊列方式 解決線程安全
/** * 微信搶紅包 普通得 map 發(fā)生 競爭情況 所以需要使用 互斥 sync.Map * 在大量得寫 和 讀得情況下會發(fā)生 競爭 * * 單核任務 修改成 16 核心 進行搶紅包 */ 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 // 初始化隊列數(shù)量 var chTaskList []chan task = make([]chan task, taskNum) type lotteryController struct { Ctx iris.Context } // 啟動一個 iris 應用 func newApp() *iris.Application { app := iris.New() mvc.New(app.Party("/")).Handle(&lotteryController{}) return app } func main() { app := newApp() err := app.Listen(":8080") // 啟動多個子線程進行 紅包搶 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 { // 返回當前全部得紅包 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 // 隨機分配最大值 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ù)每個紅包進行均分 if leftMoney == leftNum { for i := num - leftNum; i < num; i++ { list[i] = 1 break } } // 隨機分配最大值 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)造一個任務 callback := make(chan uint) t := task{id: uint32(id), callback: callback} // 發(fā)送任務 chTasks := chTaskList[id%taskNum] chTasks <- t // 接受返回結(jié)果值 money := <-callback if money <= 0 { return "很遺憾,沒有搶到紅包\n" } else { return fmt.Sprintf("恭喜你搶到一個紅包, 紅包金額:%d \n", money) } } // 使用隊列方式, 需要不斷從chan 通道中獲取數(shù)據(jù) func fetchPackageListMoney(chTasks chan task) { for { t := <-chTasks id := t.id l, ok := packageList.Load(id) if ok && l != nil { // 分配隨機數(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 } } }
抽獎大轉(zhuǎn)盤
后端設(shè)置各個獎品得中獎概率和數(shù)量限制,更新庫存時候發(fā)現(xiàn)并發(fā)安全性質(zhì)問題 和微信搖一搖 類似
使用CAS進行安全代碼進行修改,不在使用同步鎖,CAS樂觀鎖比sync.mutSync 會快一些
/** * 大轉(zhuǎn)盤程序 * curl http://localhost:8080/ * curl http://localhost:8080/debug * curl http://localhost:8080/prize * 固定幾個獎品,不同的中獎概率或者總數(shù)量限制 * 每一次轉(zhuǎn)動抽獎,后端計算出這次抽獎的中獎情況,并返回對應的獎品信息 * * 增加互斥鎖,保證并發(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 獎品中獎概率 type Prate struct { Rate int // 萬分之N的中獎概率 Total int // 總數(shù)量限制,0 表示無限數(shù)量 CodeA int // 中獎概率起始編碼(包含) CodeB int // 中獎概率終止編碼(包含) Left *int32 // 剩余數(shù)使用CAS樂觀鎖 進行修改 } var left = int32(1000) // 獎品列表 var prizeList []string = []string{ "一等獎,火星單程船票", "二等獎,涼颼颼南極之旅", "三等獎,iPhone一部", "", // 沒有中獎 } // 獎品的中獎概率設(shè)置,與上面的 prizeList 對應的設(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 } // 啟動一個 iris 應用 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)盤獎品列表:<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") // 第一步,抽獎,根據(jù)隨機數(shù)匹配獎品 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 } // 第二步,發(fā)獎,是否可以發(fā)獎 if prizeRate.Total == 0 { // 無限獎品 fmt.Println("中獎: ", myPrize) return myPrize } else if *prizeRate.Left > 0 { // 還有剩余獎品 left := atomic.AddInt32(prizeRate.Left, -1) if left >= 0 { log.Printf("獎品:%s", myPrize) return myPrize } } // 有限且沒有剩余獎品,無法發(fā)獎 myPrize = "很遺憾,再來一次" return myPrize } // GetDebug GET http://localhost:8080/debug func (c *lotteryController) GetDebug() string { c.Ctx.Header("Content-Type", "text/html") return fmt.Sprintf("獲獎概率: %v", rateList) }
抽獎活動總結(jié)
- 并發(fā)安全性質(zhì)問題,互斥鎖,隊列, CAS遞減方式
- 優(yōu)化,通過散列減小單個集合得大小
到此這篇關(guān)于GoLang抽獎系統(tǒng)簡易實現(xiàn)流程的文章就介紹到這了,更多相關(guān)Go抽獎系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go?micro微服務proto開發(fā)安裝及使用規(guī)則
這篇文章主要為大家介紹了go?micro微服務proto開發(fā)中安裝Protobuf及基本規(guī)范字段的規(guī)則詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01go語言通過反射獲取和設(shè)置結(jié)構(gòu)體字段值的方法
這篇文章主要介紹了go語言通過反射獲取和設(shè)置結(jié)構(gòu)體字段值的方法,實例分析了Go語言反射的使用技巧,需要的朋友可以參考下2015-03-03使用golang引入外部包的三種方式:go get, go module, ve
這篇文章主要介紹了使用golang引入外部包的三種方式:go get, go module, vendor目錄,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01