微信红包设计流程讲解与实战分析
前言
微信红包作为大家耳熟能详的一种互动方式,其背后的技术支持包含多个方面。从用户发出红包到红包被抢完,涉及到的流程包括发红包、红包存储、红包拆分以及抢红包等。本文将详细介绍这一系列流程,并通过代码案例来实践讲解,特别重点分析红包的拆分算法。
微信红包设计流程
红包主要有三点限制
- 抢到的总额 = 红包的总额,不能多也不能少了
- 最小值是
0.01
元,即每个人都有份 - 每个人抢到的红包金额,尽量平均
整个红包流程按照发红包、红包拆分、抢红包的顺序来设计。在数据结构的选择上,考虑到抢红包的高并发特性和即时响应要求,采用Redis非关系数据库进行设计是优于MySQL的。Redis的每个命令都是单线程执行,保证原子性操作,无需额外的锁机制。
1. 发红包
一个红包通常会被拆分成多个小红包,例如100元可以拆分为20元、20元、20元、30元和10元。这种情况下,可以使用Redis的list结构来存储这些拆分后的小红包。
2. 抢红包
在高并发环境下,如何保证多线程抢红包时不加锁且保持原子性是关键。Redis的特性使得每个命令都是单线程且原子性的,因此使用LPOP
命令即可实现。
3. 记红包
为了确保同一个用户不能抢夺同一个红包两次,需要记录哪些红包被哪些用户抢过。这里可以使用Redis的hash结构来进行存储。
拆红包算法
在拆红包算法中,较为合理的是采用“二倍均值算法”。
该算法的核心思想是每次拆分红包时,取一个随机区间,其最大值为剩余红包金额的两倍与未被抢的剩余红包个数的乘积。 这样可以保证拆分的随机性和公平性。
代码实现
实现逻辑
-
假设总金额是
M
元,N
个人,每次抢的金额=(0, (M/N) *2)
-
比如,金额
100
,人数10
,第一个人抢的金额是(0,20)
,抢到的数值,根据正态分布,应该是10
左右,远低于10
的概率很小,同样远大于10
的概率和很小,这里假设第一个人抢到的数值是10
; -
第二个人抢的金额是
(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数据库的高效利用以及高并发环境下的数据处理问题。