起因
待办
随机算法原理
待办
应用场景
随机需要保持一致
基于随机算法的原理分析,我们只需要保证在某个维度下随机种子相等即可
例如:
- 针对用户的随机数,可以用用户id作为随机种子
- 针对订单的随机数,可以用订单id作为随机种子
- 针对某个区域+时间的随机数,可以用经纬度+时间作为随机种子
随机约乱越好
在有些场景下,我们期望随机数越乱越好,例如发红包 很少会出现均分的红包,哪有应该怎么保证红包随机数不一致呢?
让我们来实现红包算法吧
1.声明一个红包
type RedPacket struct {
Mu sync.Mutex //互斥锁
TotalMoney int //红包总金额,单位分
TotalNumber int //红包总个数
Money int //红包金额,单位分
Number int //红包个数
}
2.红包的金额随机算法
func (redPacket *RedPacket) GetRandMoney() int {
r := rand.New(rand.NewSource(time.Now().Unix()))
money := r.Intn(int(float64(redPacket.TotalMoney) * 0.9)) //红包上限系数
if money == 0 {
return 1 //兜底1分红包
} else {
return money
}
}
3.实现红包的接收算法
func (redPacket *RedPacket) ReceiveRedPacket() int {
redPacket.Mu.Lock()
defer redPacket.Mu.Unlock()
if redPacket.Number <= 0 || redPacket.Money <= 0 {
return 0
} else if redPacket.Number == 1 {
//最后一个人领取红包所有金额
money := redPacket.Money
redPacket.Number = 0
redPacket.Money = 0
return money
} else {
money := 0
//重试5次
for i := 0; i < 5; i++ {
money = redPacket.GetRandMoney()
if money <= redPacket.Money {
break
}
}
if money == 0 || money > redPacket.Money {
money = 1 //兜底1分红包
}
redPacket.Number -= 1
redPacket.Money -= money
return money
}
}
4.实现类
func main() {
redPacket := RedPacket{
Mu: sync.Mutex{},
TotalMoney: 10000,
TotalNumber: 4,
Money: 10000, //100元
Number: 4,
}
var wg sync.WaitGroup
wg.Add(redPacket.Number)
for i := 0; i < redPacket.TotalNumber; i++ {
go func(i int) {
defer wg.Done()
fmt.Printf("第%d个人获取了:%.2f元\n", i+1, float64(redPacket.ReceiveRedPacket())/100)
}(i)
}
wg.Wait()
}
看看运行结果
可以看出n-1个人的红包金额永远是一样的,为什么会出现这种情况呢?
看看随机生成红包金额的函数
money := r.Intn(int(float64(redPacket.TotalMoney) * 0.9)) //红包上限系数
这里通过协程并发请求模拟了用户同时点击红包行为,导致抢红包的时间是同一时刻,生成的随机金额也是一样的
怎么解决呢?
首先,可以尝试用进度更高的时间精度
r := rand.New(rand.NewSource(time.Now().UnixNano()))
其次,为了解决时间相同导致的随机数生成的相同的情况,我们可以尝试维护一个随机生成的序号,每次生成随机数后+1
改造后的随机算法是这样的
var RandomID int64 = 0
func (redPacket *RedPacket) GetRandMoney() int {
r := rand.New(rand.NewSource(time.Now().Unix() | atomic.LoadInt64(&RandomID)))
money := r.Intn(int(float64(redPacket.TotalMoney) * 0.9)) //红包上限系数
atomic.AddInt64(&RandomID, 1)//RandomID+1
if money == 0 {
return 1 //兜底1分红包
} else {
return money
}
}
运行后结果
如果单机服务到这里已经没啥问题了
但是分布式环境下,需要将sync.Mutex转化成分布式锁
同时,由于每个服务器各自维护一套从0开始的RandomID,如果这4个人同时获取红包时正好请求到不同的重启后机器上(RandomID=0)时,也可能造成等额红包
因此可以在随机种子中加上机器号,确保同一时刻不同的重启后机器生成的随机数不同
最后,即使我们为随机数做了这么多,但是由于红包金额大小受限,还是可能会出现等额红包。 我们可以获取上一次发放的红包金额,保证这次和上次不同即可
生产环境中红包生成不会像上述那样,只是希望通过红包这个例子说明,当我们需要在分布式环境制造出差异较大的随机数时,可以通过增加多个维度来保证随机种子尽可能的不一致
总结
- 随机算法是一种变种的hash算法,当随机种子相等时,总能得到相同的随机结果
- 利用hash算法的特性
- 当需要某种维度下(用户,订单,时间....)前后随机一致性时,保证随机种子相等接客(用户id,订单id,时间...)
- 当需要在某种维度下随机不同时,可以通过多种维度保证随机种子不相等即可