4.6 KiB
Executable File
Raw Permalink Blame History

直播间抽奖系统 - 随机离散方案

概述

此方案将原来的"连续区间权重"改为"随机离散位置",解决大奖连续出现或长时间不出的问题。

核心变化

原方案(连续区间)

总权重: 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

注意事项

  1. 缓存失效:奖品配置变更后,缓存会自动重新生成
  2. 并发安全使用RWMutex保护缓存读写
  3. 位置唯一Fisher-Yates算法保证每个位置只属于一个奖品
  4. 密码学安全使用crypto/rand保证随机性

对比分析

特性 连续区间 随机离散
实现复杂度 简单 复杂
概率精度 准确 准确
分布均匀性 ⚠️ 可能偏斜 均匀
防连续中奖 天然防聚集
缓存需求 需要
内存占用 中等(~400KB

结论

随机离散方案更适合需要分布均匀、防连续中奖的场景,虽然实现稍复杂,但能显著提升用户体验。