在用于实现基于回合的多人游戏服务器的Meteor应用程序中,客户端通过发布/订阅接收游戏状态,并且可以调用Meteor方法sendTurn将回合数据发送到服务器(他们无法直接更新游戏状态集合)。

var endRound = function(gameRound) {
    // check if gameRound has already ended /
    // if round results have already been determined
    //     --> yes:
                  do nothing
    //     --> no:
    //            determine round results
    //            update collection
    //            create next gameRound
};

Meteor.methods({
    sendTurn: function(turnParams) {
        // find gameRound data
        // validate turnParams against gameRound
        // store turn (update "gameRound" collection object)
        // have all clients sent in turns for this round?
        //      yes --> call "endRound"
        //      no  --> wait for other clients to send turns
    }
});

要实现时间限制,我想等待特定时间段(让客户有时间调用sendTurn),然后确定舍入结果-但前提是尚未在sendTurn中确定舍入结果。

我应如何在服务器上实现此时间限制?

我实现这一目标的幼稚方法是调用Meteor.setTimeout(endRound, <roundTimeLimit>)

问题:
  • 并发呢?我假设我应该在sendTurnendRound(?)中同步更新集合(没有回调),但这足以消除竞争条件吗? (阅读关于对同步数据库操作的this SO question的可接受答案的第四条评论也会产生效果,我对此表示怀疑)
  • 在这方面,“按请求”在我的上下文中的Meteor docs中(由客户端方法调用和/或在服务器endRound中调用的setTimeout函数)是什么意思?

  • 在多服务器/群集环境中,这将如何工作?
  • 最佳答案

    很好的问题,而且比看起来要棘手。首先,我想指出的是,我在以下 repo 中实现了针对此确切问题的解决方案:



    概括而言,该问题基本上具有以下属性:

  • 每个客户端在每个回合中都会发送一些操作(您将其称为sendTurn)
  • 所有客户端均已发送其 Action 后,运行endRound
  • 每个回合都有一个计时器,如果该计时器到期,将自动运行endRound(无论如何)
  • endRound必须每轮准确执行一次,而不管客户端做什么

  • 现在,考虑一下我们必须处理的Meteor的属性:
  • 每个客户端一次只能对服务器使用一个出色的方法(除非在方法内部调用this.unblock())。以下方法等待第一个。
  • 服务器上的所有超时和数据库操作都可以屈服于其他光纤

  • 这意味着,每当方法调用执行一次屈服操作时,Node或数据库中的值都可以更改。这可能会导致以下潜在的比赛情况(这些只是我已经解决的情况,但可能还有其他情况):
  • 例如,在2人游戏中,两个客户端完全同时调用sendTurn。两者都调用屈服操作来存储转弯数据。然后,这两种方法都会检查是否有2位玩家轮到他们发送回合,找到肯定的答案,然后endRound运行两次。
  • 玩家在回合超时时立即调用sendTurn。在这种情况下,超时和播放器的方法都会调用endRound,从而再次运行两次。
  • 对上述问题的错误修复可能导致饥饿,而endRound从未被调用。

  • 您可以通过几种方式解决此问题,或者在Node或数据库中进行同步。
  • 由于一次只能有一个光纤实际可以更改Node中的值,因此,如果您不调用让步操作,则可以避免可能出现的竞争情况。因此,您可以将转弯状态之类的内容缓存在内存中而不是数据库中。但是,这需要正确完成缓存,并且不能继承到集群环境。
  • endRound代码移到方法调用本身之外,使用其他方式触发它。这是我采用的方法,可确保只有计时器或最终玩家触发回合的结束,而不是两者都触发(有关observeChanges的实现,请参见here)。
  • 在集群环境中,您将必须仅使用数据库(可能与条件更新操作和原子运算符)进行同步。类似于以下内容:
    var currentVal;
    
    while(true) {
      currentVal = Foo.findOne(id).val; // yields
    
      if( Foo.update({_id: id, val: currentVal}, {$inc: {val: 1}}) > 0 ) {
        // Operation went as expected
        // (your code here, e.g. endRound)
        break;
      }
      else {
        // Race condition detected, try again
      }
    }
    

  • 上面的方法很原始,可能会导致高负载下数据库性能下降。它也不能处理定时器,但是我可以肯定地认为您可以弄清楚如何扩展它以使其更好地工作。

    您可能还希望看到此timers code,以了解其他一些想法。一旦有时间,我将把它扩展到您描述的完整设置。

    关于node.js - Meteor.setTimeout和Meteor.methods之间的并发,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32987510/

    10-09 23:38