我已经为一个简单的游戏编写了REST API,包括创建,加入和提交游戏的比赛。我在测试服务器时遇到问题。通过发送单个玩家(一次很多)的加入请求,服务器会将玩家加入大厅的倍增次数。
我的设计如下:
我知道问题出在检查玩家当前是否在玩。每个请求都由Go中单独的goroutine运行,因此有可能每个goroutine从数据库中获取玩家的“ playing”字段等于false的信息。然后,每个鱼肉酱都会在大厅中添加同一位玩家,这是我要避免的倍数。
是否可以避免此问题,或者问题出在我的设计中?
答案 0 :(得分:0)
有几种选择-值得阅读Postgres上的docs进行并发处理。
简短的版本是:确保插入/更新仅在他们以期望的状态找到数据并使用事务时才触发。
所以,像这样:
begin;
insert into lobby
select player_id, ...
from players
where player_id = $current_player
and player_id not in (select player_id from lobby);
update player
set playing = 1
where playing = 0
and player_id = $player_id;
commit;
如果其他一些进程已经将播放器标记为活动状态,则这两个SQL语句将不执行任何操作,但它们将同时触发。 这应该意味着,如果您多次尝试同时更新玩家的状态,那么只有其中之一会做任何事情。
答案 1 :(得分:0)
您的问题与Go无关,而是与数据库结构有关。
为简化解决方案,
要确保您只有一个玩家同时玩,可以为此创建一个专用表,并带有唯一的索引/约束。
如何在2列上添加唯一性,请参见以下问题:In Postgresql, Force unique on combination of two columns
在您的情况下,列将为player_id
和playing
在API级别,如果玩家已经在玩游戏,则需要处理从数据库中获取的错误。
答案 2 :(得分:0)
如果您想确保同一大厅中永远不会有相同的用户两次,则应该对玩家表中这两个字段的组合添加唯一约束(假设players
是链接{{ 1}}至users
)。
一旦有了,就可以使用upsert:
lobbies
这将创建记录-但是,如果由于INSERT INTO players (user_id, lobby_id, playing) VALUES (12, 41, 1) ON CONFLICT name_of_your_constraint UPDATE players.playing = 1;
和user
的组合已经存在而无法实现,则会将用户标记为正在播放。