业务难点

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

技术选项

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

抽奖活动

抽奖大转盘

需求分析

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 main

import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"math/rand"
	"strings"
	"sync"
	"time"
)

var userList []string // 共享变量读写前后 需要增加 锁的设定 简单方式添加互斥锁
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("当前总共参与抽奖的用户数:%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 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("当前总共参与抽奖的用户数: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 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 // 奖品数据(特定得配置信息)
	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:     "优惠卷满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] = &g1
	giftList[1] = &g2
	giftList[2] = &g3
	giftList[3] = &g4
	giftList[4] = &g5
	// s数据整理 中奖区间数据
	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, 限量奖品总数量:%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 区间内进行对比 判断是否获奖
	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 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		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%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 -= 1
		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 -= 1
		return 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 -= 1
		return 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 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("参数数值异常, 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
		}
		// 剩余钱数等于剩余红包数每个红包进行均分
		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)
	}
	// 分配随机数获取红包
	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 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 // 初始化队列数量
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("参数数值异常, 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
		}
		// 剩余钱数等于剩余红包数每个红包进行均分
		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)
	}
	// 构造一个任务
	callback := make(chan uint)
	t := task{id: uint32(id), callback: callback}
	// 发送任务
	chTasks := chTaskList[id%taskNum]
	chTasks <- t
	// 接受返回结果值
	money := <-callback
	if money <= 0 {
		return "很遗憾,没有抢到红包\n"
	} else {
		return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)
	}
}

// 使用队列方式, 需要不断从chan 通道中获取数据
func fetchPackageListMoney(chTasks chan task) {
	for {
		t := <-chTasks
		id := t.id
		l, 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 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    // 总数量限制,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 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
	}
	// 第二步,发奖,是否可以发奖
	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递减方式
  • 优化,通过散列减小单个集合得大小