抽奖系统架构实战:参与者ID乱序、公平算法与高并发抽奖完整解决方案

一、抽奖系统概述

1.1 抽奖系统需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
抽奖系统核心需求:
公平性:
- 每个参与者机会均等
- 不可预测获奖结果
- 防止作弊和刷奖

高性能:
- 支持高并发报名
- 毫秒级抽奖响应
- 处理百万级参与者

可靠性:
- 保证数据一致性
- 防止重复抽奖
- 支持秒杀场景

业务需求:
- 多种抽奖模式
- 灵活的奖品配置
- 实时结果统计

1.2 参与者ID乱序的重要性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ID乱序的原因:
公平性保证:
- 避免顺序偏袒
- 确保随机性
- 防止预测结果

公平竞争:
- 早报名不占优势
- 晚报名也有机会
- 体现抽奖公平性

技术需求:
- 洗牌算法
- 随机排序
- Fisher-Yates算法

二、乱序算法实现

2.1 Fisher-Yates洗牌算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// FisherYatesShuffle.java
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

public class FisherYatesShuffle {

/**
* Fisher-Yates洗牌算法
* 时间复杂度: O(n)
* 空间复杂度: O(n)
*/
public static <T> void shuffle(List<T> list) {
int n = list.size();

for (int i = n - 1; i > 0; i--) {
// 生成[0, i]范围内的随机数
int j = ThreadLocalRandom.current().nextInt(i + 1);

// 交换元素
Collections.swap(list, i, j);
}
}

/**
* 带种子的洗牌算法
*/
public static <T> void shuffle(List<T> list, Random random) {
int n = list.size();

for (int i = n - 1; i > 0; i--) {
int j = random.nextInt(i + 1);
Collections.swap(list, i, j);
}
}

/**
* 数组版本
*/
public static void shuffleArray(int[] arr) {
int n = arr.length;

for (int i = n - 1; i > 0; i--) {
int j = ThreadLocalRandom.current().nextInt(i + 1);

// 交换
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}

public static void main(String[] args) {
// 测试洗牌算法
List<Integer> participants = new ArrayList<>();
for (int i = 1; i <= 100; i++) {
participants.add(i);
}

System.out.println("原始顺序: " + participants);

shuffle(participants);

System.out.println("洗牌后: " + participants);
}
}

2.2 多种乱序方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// MultipleShuffleMethods.java
import java.util.*;

public class MultipleShuffleMethods {

/**
* 方法1: Collections.shuffle()
*/
public static List<Integer> shuffleMethod1(List<Integer> list) {
List<Integer> shuffled = new ArrayList<>(list);
Collections.shuffle(shuffled);
return shuffled;
}

/**
* 方法2: 使用自定义Comparator
*/
public static List<Integer> shuffleMethod2(List<Integer> list) {
List<Integer> shuffled = new ArrayList<>(list);
shuffled.sort((a, b) -> ThreadLocalRandom.current().nextInt(-1, 2));
return shuffled;
}

/**
* 方法3: Knuth洗牌(Fisher-Yates的变种)
*/
public static List<Integer> shuffleMethod3(List<Integer> list) {
List<Integer> shuffled = new ArrayList<>(list);
Random random = new Random();

for (int i = shuffled.size() - 1; i > 0; i--) {
int j = random.nextInt(i + 1);
Collections.swap(shuffled, i, j);
}

return shuffled;
}

/**
* 方法4: 随机排序(效率较低)
*/
public static List<Integer> shuffleMethod4(List<Integer> list) {
List<Integer> shuffled = new ArrayList<>(list);
shuffled.sort((a, b) -> new Random().nextInt(3) - 1);
return shuffled;
}
}

2.3 分布式环境下的乱序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// DistributedShuffle.java
import java.util.*;
import java.util.concurrent.*;

public class DistributedShuffle {

/**
* 分片洗牌
* 适用于大量参与者的情况
*/
public static List<Integer> shardedShuffle(List<Integer> participants, int shardCount) {
List<Integer> result = new ArrayList<>();

// 将参与者分成多个分片
List<List<Integer>> shards = splitIntoShards(participants, shardCount);

// 对每个分片进行洗牌
for (List<Integer> shard : shards) {
FisherYatesShuffle.shuffle(shard);
}

// 合并结果
for (List<Integer> shard : shards) {
result.addAll(shard);
}

return result;
}

/**
* 并行洗牌
*/
public static List<Integer> parallelShuffle(List<Integer> participants, int threadCount) {
List<Integer> result = new ArrayList<>(participants);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
List<Future<Void>> futures = new ArrayList<>();

// 分片并行洗牌
int chunkSize = participants.size() / threadCount;

for (int i = 0; i < threadCount; i++) {
final int start = i * chunkSize;
final int end = (i == threadCount - 1) ? participants.size() : (i + 1) * chunkSize;

Future<Void> future = executor.submit(() -> {
List<Integer> chunk = new ArrayList<>(result.subList(start, end));
FisherYatesShuffle.shuffle(chunk);

synchronized (result) {
for (int j = 0; j < chunk.size(); j++) {
result.set(start + j, chunk.get(j));
}
}
return null;
});

futures.add(future);
}

// 等待所有任务完成
for (Future<Void> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}

executor.shutdown();
return result;
}

private static List<List<Integer>> splitIntoShards(List<Integer> list, int shardCount) {
List<List<Integer>> shards = new ArrayList<>();
int size = list.size() / shardCount;

for (int i = 0; i < shardCount; i++) {
int start = i * size;
int end = (i == shardCount - 1) ? list.size() : (i + 1) * size;
shards.add(new ArrayList<>(list.subList(start, end)));
}

return shards;
}
}

三、Redis实现

3.1 Redis List实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// RedisLotteryService.java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import java.util.*;

@Service
public class RedisLotteryService {

@Autowired
private JedisPool jedisPool;

private static final String LOTTERY_KEY_PREFIX = "lottery:";
private static final String PARTICIPANTS_KEY_PREFIX = "participants:";

/**
* 添加参与者并乱序
*/
public void addParticipants(String lotteryId, List<String> userIds) {
try (Jedis jedis = jedisPool.getResource()) {
String key = PARTICIPANTS_KEY_PREFIX + lotteryId;

// 先洗牌
List<String> shuffled = new ArrayList<>(userIds);
Collections.shuffle(shuffled);

// 批量添加
Pipeline pipeline = jedis.pipelined();
for (String userId : shuffled) {
pipeline.rpush(key, userId);
}
pipeline.sync();
}
}

/**
* 抽奖核心逻辑
*/
public List<String> draw(String lotteryId, int count) {
try (Jedis jedis = jedisPool.getResource()) {
String key = PARTICIPANTS_KEY_PREFIX + lotteryId;

// 使用Lua脚本保证原子性
String script =
"local results = {} " +
"for i = 1, ARGV[1] do " +
" local userId = redis.call('lpop', KEYS[1]) " +
" if userId then " +
" table.insert(results, userId) " +
" end " +
"end " +
"return results";

@SuppressWarnings("unchecked")
List<String> winners = (List<String>) jedis.eval(script,
Collections.singletonList(key),
Collections.singletonList(String.valueOf(count))
);

return winners;
}
}

/**
* 检查参与者数量
*/
public long getParticipantCount(String lotteryId) {
try (Jedis jedis = jedisPool.getResource()) {
String key = PARTICIPANTS_KEY_PREFIX + lotteryId;
return jedis.llen(key);
}
}

/**
* 初始化抽奖池(带乱序)
*/
public void initLotteryPool(String lotteryId, List<String> userIds) {
try (Jedis jedis = jedisPool.getResource()) {
// 1. 先洗牌
List<String> shuffled = new ArrayList<>(userIds);
Collections.shuffle(shuffled);

// 2. 清空旧数据
String key = PARTICIPANTS_KEY_PREFIX + lotteryId;
jedis.del(key);

// 3. 批量添加到Redis List
Pipeline pipeline = jedis.pipelined();
for (String userId : shuffled) {
pipeline.rpush(key, userId);
}
pipeline.sync();

// 4. 设置过期时间
jedis.expire(key, 3600);
}
}
}

3.2 Redis Set实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// RedisSetLotteryService.java
@Service
public class RedisSetLotteryService {

@Autowired
private JedisPool jedisPool;

/**
* 使用Set实现抽奖
*/
public List<String> drawFromSet(String lotteryId, int count) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:set:" + lotteryId;

// 随机取出指定数量
Set<String> randomMembers = jedis.srandmember(key, count);
return new ArrayList<>(randomMembers);
}
}

/**
* 添加参与者(自动去重)
*/
public void addParticipant(String lotteryId, String userId) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:set:" + lotteryId;
jedis.sadd(key, userId);
}
}

/**
* 批量添加
*/
public void addParticipants(String lotteryId, String[] userIds) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:set:" + lotteryId;
jedis.sadd(key, userIds);
}
}
}

3.3 Redis ZSet实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// RedisZSetLotteryService.java
@Service
public class RedisZSetLotteryService {

@Autowired
private JedisPool jedisPool;

/**
* 初始化抽奖池(带权重)
*/
public void initLotteryPool(String lotteryId, Map<String, Double> participants) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:zset:" + lotteryId;

// 清空旧数据
jedis.del(key);

// 添加参与者(权重用于优先级或复抽机会)
Pipeline pipeline = jedis.pipelined();
for (Map.Entry<String, Double> entry : participants.entrySet()) {
pipeline.zadd(key, entry.getValue(), entry.getKey());
}
pipeline.sync();
}
}

/**
* 按权重抽奖
*/
public List<String> drawByWeight(String lotteryId, int count) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:zset:" + lotteryId;

// 使用REVRANGE获取权重最高的
Set<String> winners = jedis.zrevrange(key, 0, count - 1);
return new ArrayList<>(winners);
}
}
}

四、高并发优化

4.1 预洗牌策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// PreShuffleLotteryService.java
@Service
public class PreShuffleLotteryService {

@Autowired
private JedisPool jedisPool;

/**
* 预洗牌策略
* 抽奖前预先完成洗牌,提高并发性能
*/
public void prepareLottery(String lotteryId, List<String> participants) {
try (Jedis jedis = jedisPool.getResource()) {
// 1. 洗牌
List<String> shuffled = new ArrayList<>(participants);
Collections.shuffle(shuffled);

// 2. 存储到Redis(使用Hash提高性能)
String key = "lottery:prepared:" + lotteryId;
Pipeline pipeline = jedis.pipelined();

for (int i = 0; i < shuffled.size(); i++) {
pipeline.hset(key, String.valueOf(i), shuffled.get(i));
}
pipeline.sync();

// 3. 存储总数量
jedis.hset("lottery:info:" + lotteryId, "total",
String.valueOf(shuffled.size()));
jedis.hset("lottery:info:" + lotteryId, "drawed", "0");

// 4. 设置过期时间
jedis.expire(key, 3600);
}
}

/**
* 高速抽奖(无需实时洗牌)
*/
public List<String> fastDraw(String lotteryId, int count) {
try (Jedis jedis = jedisPool.getResource()) {
String infoKey = "lottery:info:" + lotteryId;
String preparedKey = "lottery:prepared:" + lotteryId;

// 原子性获取并更新已抽取数量
String script =
"local drawed = redis.call('hget', KEYS[1], 'drawed') " +
"local total = redis.call('hget', KEYS[1], 'total') " +
"local drawCount = tonumber(ARGV[1]) " +
"local endIndex = tonumber(drawed) + drawCount - 1 " +
"local results = {} " +
"for i = tonumber(drawed), endIndex do " +
" local userId = redis.call('hget', KEYS[2], tostring(i)) " +
" if userId then " +
" table.insert(results, userId) " +
" end " +
"end " +
"redis.call('hset', KEYS[1], 'drawed', endIndex + 1) " +
"return results";

@SuppressWarnings("unchecked")
List<String> winners = (List<String>) jedis.eval(script,
Arrays.asList(infoKey, preparedKey),
Collections.singletonList(String.valueOf(count))
);

return winners;
}
}
}

4.2 分布式锁控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// DistributedLotteryService.java
@Service
public class DistributedLotteryService {

@Autowired
private RedissonClient redissonClient;

@Autowired
private JedisPool jedisPool;

/**
* 使用分布式锁保证并发安全
*/
public List<String> safeDraw(String lotteryId, int count) {
RLock lock = redissonClient.getLock("lottery:lock:" + lotteryId);

try {
// 尝试获取锁
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
try {
// 执行抽奖逻辑
return performDraw(lotteryId, count);
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("获取锁失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("抽奖被中断");
}
}

private List<String> performDraw(String lotteryId, int count) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:participants:" + lotteryId;

// 抽奖逻辑
List<String> winners = new ArrayList<>();
for (int i = 0; i < count; i++) {
String userId = jedis.lpop(key);
if (userId != null) {
winners.add(userId);
}
}

return winners;
}
}
}

4.3 限流和降级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// RateLimitedLotteryService.java
@Service
public class RateLimitedLotteryService {

@Autowired
private JedisPool jedisPool;

/**
* 限流抽奖
*/
@RateLimiter(value = "lottery", qps = 1000)
public List<String> rateLimitedDraw(String lotteryId, int count) {
try (Jedis jedis = jedisPool.getResource()) {
// 检查是否达到限流阈值
if (!checkRateLimit(lotteryId)) {
throw new RuntimeException("请求过于频繁,请稍后重试");
}

return performDraw(lotteryId, count);
}
}

private boolean checkRateLimit(String lotteryId) {
// 使用Redis实现限流逻辑
// 例如:令牌桶、滑动窗口等
return true;
}
}

五、公平性保证

5.1 不可预测性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SecureRandomLotteryService.java
import java.security.SecureRandom;

@Service
public class SecureRandomLotteryService {

/**
* 使用SecureRandom保证安全性
*/
public List<String> secureShuffle(List<String> participants) {
// 使用安全随机数生成器
SecureRandom secureRandom = new SecureRandom();
List<String> shuffled = new ArrayList<>(participants);

// Fisher-Yates洗牌
for (int i = shuffled.size() - 1; i > 0; i--) {
int j = secureRandom.nextInt(i + 1);
Collections.swap(shuffled, i, j);
}

return shuffled;
}
}

5.2 反作弊机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// AntiCheatLotteryService.java
@Service
public class AntiCheatLotteryService {

/**
* 防刷机制
*/
public void addParticipant(String lotteryId, String userId) {
// 1. 检查是否重复报名
if (isAlreadyRegistered(lotteryId, userId)) {
throw new RuntimeException("用户已报名");
}

// 2. 检查IP限制
String ip = getClientIp();
if (isIpLimited(ip)) {
throw new RuntimeException("IP限制");
}

// 3. 检查设备指纹
String deviceId = getDeviceId();
if (isDeviceLimited(lotteryId, deviceId)) {
throw new RuntimeException("设备限制");
}

// 4. 添加参与者
addToLottery(lotteryId, userId);
}

/**
* 黑名单检查
*/
public boolean isBlacklisted(String userId) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.sismember("lottery:blacklist", userId);
}
}
}

六、完整抽奖系统

6.1 抽奖模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// LotteryModel.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LotteryModel {

/**
* 抽奖ID
*/
private String lotteryId;

/**
* 抽奖名称
*/
private String name;

/**
* 奖品配置
*/
private List<Prize> prizes;

/**
* 参与者数量
*/
private long participantCount;

/**
* 已抽取数量
*/
private long drawedCount;

/**
* 抽奖状态
*/
private LotteryStatus status;

/**
* 开始时间
*/
private Date startTime;

/**
* 结束时间
*/
private Date endTime;

@Data
public static class Prize {
private String prizeId;
private String name;
private int count;
private int probability;
}

public enum LotteryStatus {
NOT_STARTED,
ONGOING,
FINISHED,
CANCELLED
}
}

6.2 完整服务实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// CompleteLotteryService.java
@Service
public class CompleteLotteryService {

@Autowired
private JedisPool jedisPool;

@Autowired
private RedissonClient redissonClient;

/**
* 创建抽奖活动
*/
public String createLottery(LotteryModel lottery) {
String lotteryId = UUID.randomUUID().toString();

// 存储抽奖配置
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:config:" + lotteryId;

jedis.hset(key, "name", lottery.getName());
jedis.hset(key, "status", "NOT_STARTED");
jedis.hset(key, "startTime", String.valueOf(lottery.getStartTime().getTime()));
jedis.hset(key, "endTime", String.valueOf(lottery.getEndTime().getTime()));

// 存储奖品配置
String prizesJson = JSON.toJSONString(lottery.getPrizes());
jedis.hset(key, "prizes", prizesJson);
}

return lotteryId;
}

/**
* 参与抽奖
*/
public void participate(String lotteryId, String userId) {
String key = "lottery:participants:" + lotteryId;

try (Jedis jedis = jedisPool.getResource()) {
// 检查是否已参与
if (jedis.sismember("lottery:users:" + lotteryId, userId)) {
throw new RuntimeException("用户已参与");
}

// 添加参与者ID
jedis.sadd("lottery:users:" + lotteryId, userId);

// 添加到抽奖池
jedis.rpush(key, userId);
}
}

/**
* 开始抽奖前进行洗牌
*/
public void startLottery(String lotteryId) {
RLock lock = redissonClient.getLock("lottery:lock:" + lotteryId);

try {
if (lock.tryLock(30, 10, TimeUnit.SECONDS)) {
try {
// 1. 获取所有参与者
List<String> participants = getAllParticipants(lotteryId);

// 2. 洗牌
Collections.shuffle(participants);

// 3. 更新到Redis
updateShuffledParticipants(lotteryId, participants);

// 4. 更新状态
updateLotteryStatus(lotteryId, "ONGOING");
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

/**
* 执行抽奖
*/
public List<String> draw(String lotteryId, int count) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:participants:" + lotteryId;

// Lua脚本保证原子性
String script =
"local results = {} " +
"for i = 1, ARGV[1] do " +
" local userId = redis.call('lpop', KEYS[1]) " +
" if userId then " +
" table.insert(results, userId) " +
" end " +
"end " +
"return results";

@SuppressWarnings("unchecked")
List<String> winners = (List<String>) jedis.eval(script,
Collections.singletonList(key),
Collections.singletonList(String.valueOf(count))
);

return winners;
}
}

private List<String> getAllParticipants(String lotteryId) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:participants:" + lotteryId;
return jedis.lrange(key, 0, -1);
}
}

private void updateShuffledParticipants(String lotteryId, List<String> participants) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:participants:" + lotteryId;

// 清空旧数据
jedis.del(key);

// 添加洗牌后的数据
Pipeline pipeline = jedis.pipelined();
for (String userId : participants) {
pipeline.rpush(key, userId);
}
pipeline.sync();
}
}

private void updateLotteryStatus(String lotteryId, String status) {
try (Jedis jedis = jedisPool.getResource()) {
String key = "lottery:config:" + lotteryId;
jedis.hset(key, "status", status);
}
}
}

七、性能测试

7.1 性能对比测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// LotteryPerformanceTest.java
public class LotteryPerformanceTest {

@Test
public void testShufflePerformance() {
List<Integer> participants = new ArrayList<>();
for (int i = 1; i <= 100000; i++) {
participants.add(i);
}

// 测试Collections.shuffle()
long start1 = System.currentTimeMillis();
List<Integer> shuffled1 = new ArrayList<>(participants);
Collections.shuffle(shuffled1);
long end1 = System.currentTimeMillis();
System.out.println("Collections.shuffle: " + (end1 - start1) + "ms");

// 测试Fisher-Yates
long start2 = System.currentTimeMillis();
FisherYatesShuffle.shuffle(participants);
long end2 = System.currentTimeMillis();
System.out.println("Fisher-Yates: " + (end2 - start2) + "ms");
}

@Test
public void testDrawPerformance() {
// 模拟10000个参与者,抽取100个中奖者
int participantCount = 10000;
int drawCount = 100;

List<String> participants = new ArrayList<>();
for (int i = 0; i < participantCount; i++) {
participants.add("user_" + i);
}

// 洗牌
Collections.shuffle(participants);

// 抽奖
long start = System.currentTimeMillis();
List<String> winners = participants.subList(0, drawCount);
long end = System.currentTimeMillis();

System.out.println("抽奖耗时: " + (end - start) + "ms");
System.out.println("中奖者数量: " + winners.size());
}
}

八、总结

本文介绍抽奖系统设计核心要点:

核心要点

  1. 参与者ID乱序:通过 Fisher-Yates 洗牌保证公平
  2. 公平算法:随机排序避免顺序偏袒
  3. 高并发实现:预洗牌、分布式锁、限流
  4. Redis部署:利用 List/Set/ZSet 多种实现
  5. 防作弊机制:黑名单、IP 限制、设备检查

技术要点

  • Fisher-Yates 洗牌:O(n) 时间复杂度、真随机
  • Redis 实现:List/Set/ZSet
  • 分布式锁:Redisson 保证一致性
  • 原子性保证:Lua 脚本避免并发问题
  • 性能优化:预洗牌、流水线、缓存

实践建议

  1. 按业务体量选择数据结构(List/Set/ZSet)
  2. 引入队列/限流降低瞬时压力
  3. 使用分布式锁与 Lua 保障一致性
  4. 落地反作弊方案与监控告警
  5. 定期压测优化并发与响应

合理运用乱序算法与高并发设计可构建稳定且公平的抽奖系统。