针对日志并发问题,在自动存点中增加Redis分布式冷却锁,杜绝进程重复触发导致经验暴涨,完善单元测试
This commit is contained in:
@@ -91,17 +91,27 @@ class AutoSaveExp extends Command
|
||||
|
||||
foreach ($roomMap as $roomId => $usernames) {
|
||||
foreach ($usernames as $username) {
|
||||
// 如果本轮已经处理过该用户,跳过发放经验(防止多房间/多开重复吃经验)
|
||||
// 1. 如果本轮已经处理过该用户,跳过发放经验(防止多房间/多开重复吃经验)
|
||||
if (isset($processedUsernames[$username])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. 利用 Redis 缓存锁,防止在短时间内(200秒内)重复进行自动存点奖励(防御多进程并发/重复调度)
|
||||
$cooldownKey = "user:auto_save_cooldown:{$username}";
|
||||
if (Redis::exists($cooldownKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = $usersByUsername->get($username);
|
||||
if (! $user) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->processUser($user, $roomId, $expGainRaw, $jjbGainRaw, $superLevel);
|
||||
|
||||
// 3. 设置冷却标记,200秒后过期(本任务5分钟运行一次,200秒冷却期既防重复,又不影响下一次存点)
|
||||
Redis::setex($cooldownKey, 200, 1);
|
||||
|
||||
$processedUsernames[$username] = true;
|
||||
$totalProcessed++;
|
||||
}
|
||||
|
||||
@@ -155,4 +155,36 @@ class AutoSaveExpCommandTest extends TestCase
|
||||
$user->refresh();
|
||||
$this->assertEquals(15, $user->exp_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试自动存点命令会通过 Redis 冷却锁,防止在短时间内并发重复调度执行。
|
||||
*/
|
||||
public function test_auto_save_cooldown_prevents_overlapping_process(): void
|
||||
{
|
||||
Sysparam::updateOrCreate(['alias' => 'exp_per_heartbeat'], ['body' => '5']);
|
||||
Cache::flush();
|
||||
|
||||
$room = Room::create(['room_name' => 'cd']);
|
||||
$user = User::factory()->create([
|
||||
'username' => 'cooldown-user',
|
||||
'exp_num' => 10,
|
||||
]);
|
||||
|
||||
Redis::hset("room:{$room->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
|
||||
Redis::setex("room:{$room->id}:alive:{$user->username}", 90, 1);
|
||||
|
||||
// 第一次执行自动存点
|
||||
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
|
||||
|
||||
// 验证第一次加了经验 (10 + 5 = 15)
|
||||
$user->refresh();
|
||||
$this->assertEquals(15, $user->exp_num);
|
||||
|
||||
// 紧接着在冷却时间内(200秒内)模拟第二次并发执行
|
||||
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
|
||||
|
||||
// 验证因为有 Redis 冷却锁,第二次执行被拦截,经验仍为 15,并未再次增加
|
||||
$user->refresh();
|
||||
$this->assertEquals(15, $user->exp_num);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user