GoFrame框架抢购红包微信算法无锁超高并发版
红包这个玩法主要分为以下几个点
发包 ===> 抢购 ===> 记录
首先我们来看看发包
注意发包我们采用的是微信的二倍均值算法,直接上代码
func (s *sRedPacket) DivideRedPacket(totalAmount float64, totalPeopleNum int) (out []*model.Ticket) {
mRedPacketList := make([]*model.Ticket, 0)
restAmount := totalAmount
restPeopleNum := totalPeopleNum
for i := 0; i < totalPeopleNum-1; i++ {
rand.Seed(time.Now().UnixNano())
amount := rand.Float64() * 2 * restAmount / gconv.Float64(restPeopleNum)
amount = math.Max(0.01, amount)
restAmount -= amount
restPeopleNum--
amount = gconv.Float64(fmt.Sprintf("%.2f", amount))
redPacket := &model.Ticket{
Id: rand.Int(),
Value: amount,
}
mRedPacketList = append(mRedPacketList, redPacket)
}
restAmount = gconv.Float64(fmt.Sprintf("%.2f", restAmount))
redPacket := &model.Ticket{
Id: rand.Int(),
Value: restAmount,
}
mRedPacketList = append(mRedPacketList, redPacket)
return mRedPacketList
}
接下来就是抢购了
抢购我们利用redis的原子性操作
```
func (s *sRedPacket) ReceiveRedPacket(ctx context.Context, redPacketId string, user_id string) error {
luaScript := `
local userKey = KEYS[1]
local redPacketKey = KEYS[2]
if redis.call("exists", userKey) == 1 then
return "ALREADY_RECEIVED"
else
local redPacket = redis.call("rpop", redPacketKey)
if redPacket then
redis.call("set", userKey, redPacket)
return redPacket
else
return "OUT_OF_STOCK"
end
end
`
userRedPacketKey := consts.RedPacketConsumer + redPacketId + ":" + user_id
redPacketListKey := consts.RedPacketKey + redPacketId
// 调用 Eval 方法,传递 Lua 脚本和参数
client, err := s.GetClient()
if err != nil {
return err
}
result, errs := client.Eval(ctx, luaScript, []string{userRedPacketKey, redPacketListKey}).Result()
if errs != nil {
return errors.New("执行 Lua 脚本失败: " + errs.Error())
}
// 判断 Lua 脚本的返回结果
if result == "ALREADY_RECEIVED" {
return errors.New("已经抢过这个红包了")
} else if result == "OUT_OF_STOCK" {
return errors.New("抱歉红包已经被抢完")
} else {
fmt.Println("恭喜您抢到红包: ", result)
return nil
}
}
```
整套操作下来是不加锁的,并发性能超高,需要代码的可以私聊我
目前已经用到生成项目,600多万用户使用没出现什么问题.