GoFrame框架抢购红包微信算法无锁超高并发版

390 阅读1分钟

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++ {
		//随机范围: [0.01,剩余人均金额的两倍),左闭右开
		// 初始化随机数生成器
		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多万用户使用没出现什么问题.

用户数据量.png

用户表.png

微信图片_20240627141034.jpg