微信红包设计流程讲解与实战分析

前言

微信红包作为大家耳熟能详的一种互动方式,其背后的技术支持包含多个方面。从用户发出红包到红包被抢完,涉及到的流程包括发红包、红包存储、红包拆分以及抢红包等。本文将详细介绍这一系列流程,并通过代码案例来实践讲解,特别重点分析红包的拆分算法。

微信红包设计流程

红包主要有三点限制

  1. 抢到的总额 = 红包的总额,不能多也不能少了
  2. 最小值是0.01元,即每个人都有份
  3. 每个人抢到的红包金额,尽量平均

整个红包流程按照发红包、红包拆分、抢红包的顺序来设计。在数据结构的选择上,考虑到抢红包的高并发特性和即时响应要求,采用Redis非关系数据库进行设计是优于MySQL的。Redis的每个命令都是单线程执行,保证原子性操作,无需额外的锁机制。

1. 发红包

一个红包通常会被拆分成多个小红包,例如100元可以拆分为20元、20元、20元、30元和10元。这种情况下,可以使用Redis的list结构来存储这些拆分后的小红包。

2. 抢红包

在高并发环境下,如何保证多线程抢红包时不加锁且保持原子性是关键。Redis的特性使得每个命令都是单线程且原子性的,因此使用LPOP命令即可实现。

3. 记红包

为了确保同一个用户不能抢夺同一个红包两次,需要记录哪些红包被哪些用户抢过。这里可以使用Redis的hash结构来进行存储。

拆红包算法

在拆红包算法中,较为合理的是采用“二倍均值算法”。

该算法的核心思想是每次拆分红包时,取一个随机区间,其最大值为剩余红包金额的两倍与未被抢的剩余红包个数的乘积。 这样可以保证拆分的随机性和公平性。

代码实现

实现逻辑

  1. 假设总金额是M元,N个人,每次抢的金额=(0, (M/N) *2)

  2. 比如,金额100,人数10,第一个人抢的金额是 (0,20),抢到的数值,根据正态分布,应该是10左右,远低于10的概率很小,同样远大于10的概率和很小,这里假设第一个人抢到的数值是10

  3. 第二个人抢的金额是(0,90/9*2)=(0,20),同第一个人,第二个人红包金额也应该是10附近;剩下的人,以此类推。

二倍均值算法的具体代码实现如下:

package main

import (
	"errors"
	"math/rand"
)

//RED_PACKET_MIN_MONEY 红包最小金额(单位:分)
const RED_PACKET_MIN_MONEY = 1

//RedPacketModel 红包信息实体
type RedPacketModel struct {
	count          int64   //红包个数
	money          int64   //红包金额(单位:分)
	remainCount    int64   //剩余红包个数
	remainMoney    int64   //剩余红包金额(单位:分)
	bestLuckMoney  int64   //手气最佳金额(单位:分)
	bestLuckIndex  int64   //手气最佳索引位置
	historyRewards []int64 //历史红包记录
}

/*
CreateRedPacket
@Desc 	生成红包
@Param 	count int64	红包个数
@Param 	money int64	红包金额
*/
func CreateRedPacket(count, money int64) (res *RedPacketModel, err error) {
	if count <= 0 || money <= 0 || money < count*RED_PACKET_MIN_MONEY {
		err = errors.New("parameter error")
		return
	}

	res = &RedPacketModel{
		count:          count,
		money:          money,
		remainCount:    count,
		remainMoney:    money,
		bestLuckMoney:  0,
		bestLuckIndex:  0,
		historyRewards: nil,
	}

	return
}

/*
Open
@Desc 	拆红包(二倍均值法)
@Return money 	int64	本地拆红包获得金额
@Return res 	bool	拆红包失败,红包已经被抢光
*/
func (r *RedPacketModel) Open() (money int64, res bool) {
	//检测红包是否被抢光
	if r.Check() {
		return
	}

	//最后一个红包
	if r.remainCount == 1 {
		money = r.remainMoney
	} else {
		//最大可用金额 = 剩余红包金额 - 后续多少个没拆的包所需要的保底金额
		//目的是为了保证后续的包至少都能分到最低保底金额,避免后续未拆的红包出现金额0
		maxCanUseMoney := r.remainMoney - RED_PACKET_MIN_MONEY*r.remainCount

		//2倍均值基础金额
		maxAvg := maxCanUseMoney / r.remainCount

		//2倍均值范围数额
		maxMoney := maxAvg*2 + RED_PACKET_MIN_MONEY

		//随机红包数额
		money = rand.Int63n(maxMoney) + RED_PACKET_MIN_MONEY
	}

	//手气最佳
	if money > r.bestLuckMoney {
		r.bestLuckMoney = money
		r.bestLuckIndex = r.count - r.remainCount
	}

	res = true
	r.remainMoney -= money
	r.remainCount--
	r.historyRewards = append(r.historyRewards, money)
	return
}

/*
Check
@Desc 校验红包是否被抢完
*/
func (r *RedPacketModel) Check() bool {
	return r.remainCount == 0
}

上面的代码只是实现了如何创建红包。 因为涉及到Redis等基础组件的使用,下面对其他环节进行文本描述。

发红包:发红包的主要步骤是将拆分后的小红包保存到Redis的list结构中,并设置过期时间。

抢红包:是先验证用户是否已经抢过该红包,如果没有则允许其抢红包并记录到Redis中。然后从List中pop出来一个红包。

注意其中的原子性,可以使用lua脚本实现,效率更高。

总结

本文主要介绍了微信红包的拆分、发放和抢红包的流程,并重点讲解了二倍均值法在拆红包算法中的应用。通过随机生成红包金额的方式,实现了公平且随机的抢红包效果。整个过程中涉及到了Redis数据库的高效利用以及高并发环境下的数据处理问题。

关于我
loading