【问题标题】:Consistent database with concurrent reads and writes具有并发读写的一致性数据库
【发布时间】:2019-08-19 01:49:06
【问题描述】:

我为一个简单的游戏编写了一个 REST API,包括创建、加入和提交游戏的比赛。我在测试服务器时遇到了问题。通过单个玩家发送加入请求(一次有很多),服务器会多次将玩家加入大厅。

我的设计是这样的:

  • 在玩家表中检查玩家的“正在玩”字段是否设置为 true,如果是则丢弃请求
  • 如果玩家当前未在玩,则为大厅创建新玩家条目并将玩家的游戏状态设置为 true

我知道问题在于检查玩家是否正在玩。每个请求都由 Go 中的单独 goroutine 运行,因此每个 goroutine 从数据库中获取玩家“正在玩”字段等于 false 的信息可能会发生。然后每个 gouroutine 都会将相同的玩家添加到大厅乘以我想避免的次数。

有没有办法避免这个问题,或者问题出在我的设计中?

【问题讨论】:

  • 这是一个标准的竞争条件。有很多方法可以解决。

标签: database postgresql


【解决方案1】:

有几个选项 - 值得阅读 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 语句将不会执行任何操作,但它们会同时触发。 这应该意味着,如果您有多个并发尝试更新玩家的状态,则只有其中一个会执行任何操作。

【讨论】:

  • 这个条件插入可能是我想要的,因为 ORM 会为我做交易。
【解决方案2】:

您的问题与 Go 无关,而是与数据库结构有关。

为了简化解决方案:

为确保您只有一个玩家同时玩,您可以为此创建一个具有唯一索引/约束的专用表。

如何在 2 列上添加唯一性请参阅此问题:In Postgresql, Force unique on combination of two columns 在您的情况下,这些列将是 player_idplaying

在 API 级别,您需要处理从数据库中获取的错误,以防玩家已经在玩游戏。

【讨论】:

    【解决方案3】:

    如果您想确保同一个用户永远不会在同一个大厅中出现两次,您应该在玩家表中的这两个字段的组合上添加一个唯一约束(假设 players 是将 users 链接到lobbies)。

    一旦你有了它,你就可以使用upsert

    INSERT INTO players (user_id, lobby_id, playing)  VALUES (12, 41, 1) ON CONFLICT name_of_your_constraint UPDATE players.playing = 1;
    

    这将创建记录 - 但是,如果因为 userlobby 的组合已经存在而无法实现,它只会将用户标记为正在播放。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-10
      • 1970-01-01
      • 2012-05-08
      • 2016-11-19
      • 1970-01-01
      • 1970-01-01
      • 2015-06-04
      • 2020-06-19
      相关资源
      最近更新 更多