业务难点

设计一个抽奖系统,这个系统并不是具体化,是抽象化,具有以下的几个难点:
1、抽奖业务需要 复杂多变
2、奖品类型和概率设置
3、公平的抽奖和安全的发奖
4、并发安全性问题 一个人不能枪多次
5、高效的抽奖和发奖,提供高并发和性能
6、 如何使用redies进行优化

技术选项

  1. 高并发 Go 协程优先于 PHP多进程,Java的 多线程模型
  2. 高性能编译后的二进制优先于PHP解释性和Java虚拟机
  3. 高效的网络模型 epoll 模型优先于PHPBIO模型和Java NIO模型

抽奖活动

  1. 年会抽奖,彩票刮奖,微信摇一摇,抽奖大转盘,集福卡等活动,本项目以 抽奖大转盘作为一种活动进行设计。
  2. 项目实战内容: 框架/核心代码后台功能 ,合理设置奖品和发送奖品, mysql+优化-使用redies 发奖计划与奖品池, 压力测试和更多的运营策略(系统的性能有更好的了解,运营产品的策略有更多), 引入 thirft 框架(RPC 框架), 设计接口生成代码, 服务端接口和客户端程序

需求分析

1. go mod 配置
2. 配置国内代理: 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 mainimport ("fmt""github.com/kataras/iris/v12""github.com/kataras/iris/v12/mvc""math/rand""strings""sync""time"
)var userList []string // 共享变量读写前后 需要增加 锁的设定 简单方式添加互斥锁
var mu sync.Mutextype 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("当前总共参与抽奖的用户数:%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, ",")// 批量线程导入时候 发现有多线程的问题 数据统计不正确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, 成功导入的用户数:%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 下的数据 更好userList = append(userList[0:index], userList[index+1:]...)return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)} else if count == 1 {user := userList[0]return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)} else {return fmt.Sprintf("当前中奖完毕,没有用户参与中奖\n")}
}

单元测试问题,对于 userList 的 多线程下发生数据竞争问题 :

package mainimport ("fmt""github.com/kataras/iris/v12/httptest""sync""testing"
)func TestMVC(t *testing.T) {app := newApp()e := httptest.New(t, app)// 使用同步等待锁var wg sync.WaitGroupe.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:0\n")for i := 0; i < 100; i++ {wg.Add(1)// 不会出现协程并发性问题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("当前总共参与抽奖的用户数:100\n")e.GET("/lucky").Expect().Status(httptest.StatusOK)e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:99\n")
}

微信摇一摇得抽奖活动

/**
* 微信摇一摇得功能
* wrk -t10 -c10 -d5 http://localhost:8080/lucky 进行压力测试 查看代码是否有竞争异常问题和 接口请求量速度*/
package mainimport ("fmt""github.com/kataras/iris/v12""github.com/kataras/iris/v12/mvc""log""math/rand""os""sync""time"
)var mu sync.Mutexconst (giftTypeCoin      = iota // 虚拟币giftTypeCoupon           // 不同卷giftTypeCouponFix        // 不同卷giftTypeRealSmall        // 实物小奖giftTypeRealLarge        // 十五大奖
)type gift struct {id       intname     stringpic      stringlink     stringgType    intdata     string // 奖品数据(特定得配置信息)dataList []stringtotal    intleft     intinuse    boolrate     int // 万分之NrateMin  intrateMax  int
}// 最大中奖号码
const rateMax = 1000var logger *log.Logger// 奖品类表
var giftList []*gifttype 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:     "优惠卷满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:     "直降优惠卷",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] = &g1giftList[1] = &g2giftList[2] = &g3giftList[3] = &g4giftList[4] = &g5// s数据整理 中奖区间数据rateStart := 0for _, data := range giftList {if !data.inuse {continue}data.rateMin = rateStartdata.rateMax = rateStart + data.rateif data.rateMax >= rateMax {data.rateMax = rateMaxrateStart = 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 := 0total := 0for _, data := range giftList {if data.inuse && (data.total == 0 || (data.total > 0 && data.left > 0)) {count++total += data.left}}return fmt.Sprintf("当前有效奖品种类数量:%d, 限量奖品总数量:%d\n", count, total)
}// GetLucky  http://localhost:8080/lucky
func (c *lotteryController) GetLucky() map[string]interface{} {mu.Lock()defer mu.Unlock()code := luckyCode()ok := falseresult := make(map[string]interface{})result["success"] = ok// 对 code 与 rateMin -rateMax 区间内进行对比 判断是否获奖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"] = trueresult["id"] = data.idresult["name"] = data.nameresult["data"] = sendDatabreak}}}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 -= 1return true, data.data} else {return false, "奖品已经发完"}
}// 不同优惠卷
func sendCoupon(data *gift) (bool, string) {if data.left > 0 {left := data.left - 1data.left = leftreturn true, data.dataList[left%5]} else {return false, "奖品已经发完"}
}func sendCouponFix(data *gift) (bool, string) {if data.total == 0 {// 数量无数return true, data.data} else if data.left > 0 {data.left -= 1return 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 -= 1return true, data.data} else {return false, "奖品已经发完"}
}func sendRealLarge(data *gift) (bool, string) {if data.total == 0 {// 数量无数return true, data.data} else if data.left > 0 {data.left -= 1return true, data.data} else {return false, "奖品已经发完"}
}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)
}

微博抢红包

  • 红包得集合,红包内红包数量读写都存在并发安全性问题
  • 第一种方式 使用 Sync.Map 互斥锁得方式
/**
* 微信抢红包 普通得 map 发生 竞争情况 所以需要使用 互斥 sync.Map
* 在大量得写 和 读得情况下会发生 竞争
**/
package mainimport ("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 intfor _, 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("参数数值异常, 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 := moenyTotalleftNum := numfor leftNum > 0 {if leftNum == 1 {list[num-1] = uint(leftMoney)break}// 剩余钱数等于剩余红包数每个红包进行均分if leftMoney == leftNum {for i := num - leftNum; i < num; i++ {list[i] = 1break}}// 随机分配最大值rMoney := int(float64(leftMoney-leftNum) * rMax)m := r.Intn(rMoney)if m < 1 {m = 1}list[num-leftNum] = uint(m)leftMoney -= mleftNum--}// 红包得UUIDid := 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)}// 分配随机数获取红包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 发生 竞争情况 所以需要使用 互斥 sync.Map
* 在大量得写 和 读得情况下会发生 竞争
*
* 单核任务 修改成 16 核心 进行抢红包*/
package mainimport ("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       uint32callback chan uint
}const taskNum = 16 // 初始化队列数量
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 intfor _, 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("参数数值异常, 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 := moenyTotalleftNum := numfor leftNum > 0 {if leftNum == 1 {list[num-1] = uint(leftMoney)break}// 剩余钱数等于剩余红包数每个红包进行均分if leftMoney == leftNum {for i := num - leftNum; i < num; i++ {list[i] = 1break}}// 随机分配最大值rMoney := int(float64(leftMoney-leftNum) * rMax)m := r.Intn(rMoney)if m < 1 {m = 1}list[num-leftNum] = uint(m)leftMoney -= mleftNum--}// 红包得UUIDid := 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)}// 构造一个任务callback := make(chan uint)t := task{id: uint32(id), callback: callback}// 发送任务chTasks := chTaskList[id%taskNum]chTasks <- t// 接受返回结果值money := <-callbackif money <= 0 {return "很遗憾,没有抢到红包\n"} else {return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)}
}// 使用队列方式, 需要不断从chan 通道中获取数据
func fetchPackageListMoney(chTasks chan task) {for {t := <-chTasksid := t.idl, ok := packageList.Load(id)if ok && l != nil {// 分配随机数获取红包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}}
}

抽奖大转盘

  • 后端设置各个奖品得中奖概率和数量限制,更新库存时候发现并发安全性质问题 和微信摇一摇 类似
    使用CAS进行安全代码进行修改,不在使用同步锁,CAS乐观锁比sync.mutSync 会快一些
/*** 大转盘程序* curl http://localhost:8080/* curl http://localhost:8080/debug* curl http://localhost:8080/prize* 固定几个奖品,不同的中奖概率或者总数量限制* 每一次转动抽奖,后端计算出这次抽奖的中奖情况,并返回对应的奖品信息** 增加互斥锁,保证并发库存更新的正常* 压力测试:* wrk -t10 -c100 -d5 "http://localhost:8080/prize"*/
package mainimport ("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    // 总数量限制,0 表示无限数量CodeA int    // 中奖概率起始编码(包含)CodeB int    // 中奖概率终止编码(包含)Left  *int32 // 剩余数使用CAS乐观锁 进行修改
}var left = int32(1000)// 奖品列表
var prizeList []string = []string{"一等奖,火星单程船票","二等奖,凉飕飕南极之旅","三等奖,iPhone一部","", // 没有中奖
}// 奖品的中奖概率设置,与上面的 prizeList 对应的设置
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("大转盘奖品列表:<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")// 第一步,抽奖,根据随机数匹配奖品seed := time.Now().UnixNano()r := rand.New(rand.NewSource(seed))// 得到个人的抽奖编码code := r.Intn(10000)//fmt.Println("GetPrize code=", code)var myPrize stringvar prizeRate *Prate// 从奖品列表中匹配,是否中奖for i, prize := range prizeList {rate := &rateList[i]if code >= rate.CodeA && code <= rate.CodeB {// 满足中奖条件myPrize = prizeprizeRate = ratebreak}}if myPrize == "" {// 没有中奖myPrize = "很遗憾,再来一次"return myPrize}// 第二步,发奖,是否可以发奖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}}// 有限且没有剩余奖品,无法发奖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)
}

抽奖活动总结

  • 并发安全性质问题,互斥锁,队列, CAS递减方式
  • 优化,通过散列减小单个集合得大小

GoLang 抽奖系统 设计相关推荐

  1. 如何设计百万人抽奖系统——面试10多家中大厂后的万字总结

    思维导图 ​ 导图按照由浅入深的方式进行讲解,架构从来不是设计出来的,而是演进而来的 从一个几百人的抽奖系统到几万人,再到到百万人,不断增加新的东西. 最后总结归纳一套设计思想,也是万能模板,这样面试 ...

  2. 如何设计一个百万级用户的抽奖系统?

    目录  1.抽奖系统的背景引入 2.结合具体业务需求分析抽奖系统 3.一个未经过优化的系统架构 4.负载均衡层的限流 5.Tomcat线程数量的优化 6.基于Redis实现抽奖业务逻辑 7.发放礼品环 ...

  3. (转载)如何设计一个百万级用户的抽奖系统?

    1.抽奖系统的背景引入 本文给大家分享一个之前经历过的抽奖系统的流量削峰架构的设计方案. 抽奖.抢红包.秒杀,这类系统其实都有一些共同的特点,那就是在某个时间点会瞬间涌入大量的人来点击系统,给系统造成 ...

  4. 简易抽奖系统(Java课程设计/大作业)

    简易抽奖系统(Java课程设计/大作业) package 简易抽奖系统; import java.awt.*; import java.awt.event.ActionEvent; import ja ...

  5. mysql怎么设计抽奖表_Access设计抽奖系统

    一.系统组成及原理 抽奖系统主要由来宾登记.奖票管理.幸运抽奖.获奖查询四个基本模块构成,登记模块登记参加抽奖人员,奖票管理模块初始化参加抽奖人员名单,抽奖模块实现抽奖功能,获奖查询模块查询获奖结果. ...

  6. Java课程设计抽奖系统

    Java学习笔记(四) 经过前期环境的搭建,和能够对eclipse的熟练使用 而且在学习了for.while.do-while等循环.if语句.switch语句.数组.String类之后,现在开始创建 ...

  7. 从限流削峰到性能优化,谈1号店抽奖系统架构实践

    1.前言 抽奖是一个典型的高并发场景应用,平时流量不多,但遇到大促活动,流量就会暴增,今年的周年庆期间的日均UV就超过百万.在过去的一年里,负责过这个项目的多次重构工作,期间各种踩坑无数,就以此文当做 ...

  8. 抽奖系统的流量削峰方案

    如果观看抽奖或秒杀系统的请求监控曲线,你就会发现这类系统在活动开放的时间段内会出现一个波峰,而在活动未开放时,系统的请求量.机器负载一般都是比较平稳的.为了节省机器资源,我们不可能时时都提供最大化的资 ...

  9. 商场抽奖软件 android,召唤抽奖系统

    召唤抽奖系统是一款角色扮演类手游,召唤抽奖系统游戏背景设定在神武大陆,游戏中玩家从现代社会穿越到一个婴儿身上,16年后玩家要踏下旅程,学习魔法和武道,你可以通过主线,支线,以及召唤线来推进剧情,还等什 ...

最新文章

  1. 一个简单的slider滑块组件
  2. 思科宣布NB-IoT平台实现商用
  3. 什么?还有可以攻击telegram和其他APP的恶意软件
  4. linux apache fcgi,编译安装apache2.2对应的mod_proxy_fcgi.so模块步骤,因为使用apache--phpfpm 需要这个...
  5. [css] css怎样使每个字符宽度一样?
  6. C语言中CY位什么时候才能为1_你真的了解C语言中的整型吗?
  7. mac wordpress php7,Mac 下基于 wordpress 搭建个人博客系统
  8. 详细剖析linux的内存管理方式(分段式、分页式、段页式),以及进程状态的具体关系
  9. mysql上机实验报告3_SQL入门随笔(上机实验报告)
  10. usb接口驱动_技术丨USB接口无法识别设备的处理方法
  11. silverlight控件动画状态的过渡
  12. 圆柱体积怎么算立方公式_圆柱的体积换算立方怎么算
  13. GitLab之docker注册Runner
  14. 在华为工作十年的感悟
  15. cad多段线画圆弧方向_CAD多段线绘制技巧 - CAD自学网
  16. Executor与线程池
  17. Python连接MySQL数据库locahost无法连接的问题
  18. 人工智能、深度学习、机器学习常见面试题56~70
  19. IPFS白皮书中文版
  20. Vue基础实战-敲击瓶子

热门文章

  1. java 判断用户是否关注了公众号
  2. dede模板加入php无法生成栏目,织梦后台更新后栏目无法生成,怎么办?
  3. 加息靴子落地铁矿石继续反弹,甲醇认购大涨,苹果10-01大跳水2022.5.5
  4. 冯扬文:新冠疫情下国际航运市场趋势分析
  5. 【IT职场】如何成为T型甚至A型人物。
  6. 智能ALMP封装推拉力测试机 剪切力强度测试仪器主要功能介绍
  7. SolidWorks学习笔记5创建基准面,基准线,基准点
  8. 35岁以后的Android程序员出路在哪里?大牛最佳总结
  9. Wavefront公司的.obj文件格式及.mtl文件格式
  10. uniapp Apple iOS 绑定解绑