蓄水池算法
代码实现
1 |
|
应用场景 - 等概率抽奖
参与抽奖时进行中奖序号计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 已参与人数
$participantCount = 2;
// 第N人参与时
$participantCount += 1;
// 计算排名
$rank = mt_rand(1, $participantCount);
if ($rank !== $participantCount) {
// 与指定用户的排名互换,若更新失败,则不互换
$result = 'UPDATE test_table SET rank = $participantCount WHERE rank = $rank;';
if (!$result) {
$rank = $participantCount;
}
}
// 保存参与记录
$result = 'INSERT INTO test_table(rank) VALUES($rank)';防并发处理
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
77class RedisService
{
/**
* @var Redis
*/
private $redis;
/**
* Bootstrap.
*
* @throws RedisException|Exception
*/
public function __construct()
{
parent::__construct(config('redis.'));
$this->redis = new Redis();
$this->redis->connect($this->config->get('host'), $this->config->get('port'));
if (!$this->redis->auth($this->config->get('auth'))) {
throw new RedisException("redis password verification failed, auth={$this->config->get('auth')}");
}
if ($this->redis->ping() !== '+PONG') {
throw new RedisException("redis connection is not available, ping={$this->redis->ping()}");
}
}
/**
* Incr Atomic.
*
* @param string $key
* @param int $defaultValue
* @param int $value
*
* @return int
*/
public function incrAtomic(string $key, int $defaultValue = 1, int $value = 1)
{
// 使用 redis 的 eval (lua 语法), 把对比和自增变成原子操作
$script = "if redis.call('get', KEYS[1]) == false then return redis.call('incrBy', KEYS[1], ARGV[1]) else return redis.call('incrBy', KEYS[1], ARGV[2]) end";
return $this->redis->eval($script, [$key, $defaultValue, $value], 1);
}
}
$lottery = [
'lotteryId' => 1,
'participantCount' => 10,
];
$redisService = new RedisService();
// 获取当前活动参与人数
$participantCount = $redisService->incrAtomic("lottery:incr_{$lottery['lotteryId']}", $lottery['participantCount'] + 1);
// 计算中奖序号
$winningNumber = mt_rand(1, $participantCount);
// 开启事务
$participationRecordModel->startTrans();
// 与已有的中奖序号互换
if ($winningNumber !== $participantCount) {
$updateResult = $participationRecordModel::where([
'lottery_id' => $lottery['lotteryId'],
'winning_number' => $winningNumber,
])->update([
'winning_number' => $participantCount
]);
if (!$updateResult) {
$winningNumber = $participantCount;
}
}
// 保存参与记录
$participationRecordModel->insertGetId([
'lottery_id' => $lottery['lotteryId'],
'user_id' => $user->userId,
'winning_number' => $winningNumber,
'generate_time' => date('Y-m-d H:i:s'),
]);
// 提交事务
$participationRecordModel->commit();
转盘抽奖
1 |
|
砍价
1 |
|