4.6 KiB
Executable File
4.6 KiB
Executable File
直播间抽奖系统 - 随机离散方案
概述
此方案将原来的"连续区间权重"改为"随机离散位置",解决大奖连续出现或长时间不出的问题。
核心变化
原方案(连续区间)
总权重: 99000
冰箱贴 (65800): [0 - 65799] ← 占据大片连续区间
高达徽章 (28000): [65800 - 93799]
兰博基尼 (3000): [93800 - 96799]
...
大奖 (100): [98900 - 98999] ← 仅占100个连续数字
问题:如果随机数生成有偏,整个区间都会受影响
新方案(随机离散)
1. 创建位置池 [0 - 98999]
2. 使用Fisher-Yates算法打乱位置顺序
3. 按权重分配位置给每个奖品
冰箱贴获得65800个**分散**的位置
大奖获得100个**分散**的位置
优势:随机数生成即使有偏,也不会导致某个奖品连续出现
实现原理
1. 随机位置生成
// Fisher-Yates洗牌算法
positions := [0, 1, 2, 3, ..., 98999]
for i := len(positions)-1; i > 0; i-- {
j = random(0, i)
swap(positions[i], positions[j])
}
2. 位置分配
prizePosMap = {
冰箱贴ID: [打乱后的位置...], // 65800个
徽章ID: [打乱后的位置...], // 28000个
...
}
3. 抽奖查找
randValue = crypto/rand(0, totalWeight)
// 二分查找
for each prize in prizePosMap:
if randValue in prize.positions:
return prize
代码结构
internal/service/livestream/
├── livestream.go # 主服务文件(已集成随机离散)
├── discrete_random.go # 随机离散核心逻辑
├── discrete_random_test.go # 单元测试
└── draw_integration_test.go # 集成测试
使用方法
1. 配置活动奖品
// 原有接口,无需改动
s.CreatePrizes(ctx, activityID, prizes)
// 系统会自动生成随机离散位置
2. 执行抽奖
// Draw() 方法已自动使用随机离散
result, err := s.Draw(ctx, input)
3. 调试日志
随机离散位置已生成
activity_id: 1
total_weight: 99000
prize_count: 11
测试验证
运行测试
cd /Users/win/aicode/bindbox/bindbox_game
# 单元测试
go test -v ./internal/service/livestream/ -run TestDiscreteRandomDistribution
# 集成测试
go test -v ./internal/service/livestream/ -run TestDrawWithDiscreteIntegration
# 性能测试
go test -bench=BenchmarkDiscreteGeneration ./internal/service/livestream/
预期输出
========== 随机离散抽奖统计结果 ==========
模拟次数: 1000
Nu高达 权重: 100 理论: 0.10% 实际: 0.10% 偏差: +0.00% 次数: 1
NT高达 权重: 100 理论: 0.10% 实际: 0.10% 偏差: +0.00% 次数: 1
魔礼青 权重: 100 理论: 0.10% 实际: 0.10% 偏差: +0.00% 次数: 1
维达尔 权重: 100 理论: 0.10% 实际: 0.20% 偏差: +0.10% 次数: 2
玉衡星6号 权重: 400 理论: 0.40% 实际: 0.40% 偏差: +0.00% 次数: 4
马克兔 权重: 600 理论: 0.61% 实际: 0.70% 偏差: +0.09% 次数: 7
00高达 权重: 700 理论: 0.71% 实际: 0.60% 偏差: -0.11% 次数: 6
SD随机款 权重: 1100 理论: 1.11% 实际: 1.10% 偏差: -0.01% 次数: 11
兰博基尼 权重: 3000 理论: 3.03% 实际: 2.90% 偏差: -0.13% 次数: 29
高达徽章 权重: 28000 理论: 28.28% 实际: 28.20% 偏差: -0.08% 次数: 282
冰箱贴 权重: 65800 理论: 66.47% 实际: 66.50% 偏差: +0.03% 次数: 665
性能分析
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 生成位置 | O(n) | 一次生成,缓存复用 |
| 查找奖品 | O(m × log(k)) | m=奖品数量, k=平均位置数 |
| 内存占用 | O(totalWeight × 4字节) | 99000个位置≈390KB |
注意事项
- 缓存失效:奖品配置变更后,缓存会自动重新生成
- 并发安全:使用RWMutex保护缓存读写
- 位置唯一:Fisher-Yates算法保证每个位置只属于一个奖品
- 密码学安全:使用crypto/rand保证随机性
对比分析
| 特性 | 连续区间 | 随机离散 |
|---|---|---|
| 实现复杂度 | ⭐ 简单 | ⭐⭐⭐ 复杂 |
| 概率精度 | ✅ 准确 | ✅ 准确 |
| 分布均匀性 | ⚠️ 可能偏斜 | ✅ 均匀 |
| 防连续中奖 | ❌ 无 | ✅ 天然防聚集 |
| 缓存需求 | 无 | 需要 |
| 内存占用 | 低 | 中等(~400KB) |
结论
随机离散方案更适合需要分布均匀、防连续中奖的场景,虽然实现稍复杂,但能显著提升用户体验。