Meteor.setTimeout和Meteor.methods之间的并发性

在我的Meteor应用程序中实现一个基于turnbased的多人游戏服务器,客户端通过发布/订阅接收游戏状态,并且可以调用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还没有确定sendTurn结果的sendTurn

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

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

问题:

  • 什么并发? 我假设我应该在sendTurnendRound (?)中同步更新集合(没有callback),但这足以消除竞争条件吗? (关于这个关于同步数据库操作的SO问题的接受答案,读第四个评论也让步,我怀疑)

  • 在这方面,在我的上下文的meteor文档中(“客户端方法调用和/或服务器setTimeout调用函数endRound ”),“每个请求”是什么意思?

    在Meteor中,您的服务器代码在每个请求中运行在单个线程中,而不是以Node的典型asynchronouscallback方式运行。

  • 在多服务器/集群环境中,(这将如何)工作?

伟大的问题,它比看起来更棘手。 首先,我想指出,我已经在以下回购协议中实施了一个解决scheme:

https://github.com/ldworkin/meteor-prisoners-dilemma https://github.com/HarvardEconCS/turkserver-meteor

总之,这个问题基本上有以下属性:

  • 每个客户端在每一轮发送一些行动(你称之为sendTurn
  • 当所有的客户都发送了他们的动作,运行endRound
  • 每一轮都有一个计时器,如果到期,自动运行endRound
  • 无论客户做什么, endRound执行一次

现在,考虑我们必须处理的meteor的属性:

  • 每个客户端一次只能向服务器提供一个优秀的方法(除非在方法中调用this.unblock() )。 以下方法等待第一个。
  • 服务器上的所有超时和数据库操作都可以产生其他光纤

这意味着只要方法调用通过一个yielding操作,Node或数据库中的值就可以改变。 这可能会导致以下潜在的竞争条件(这些只是我已经修复的,但可能有其他的):

  • 例如,在双人游戏中,两个客户端在同一时间调用sendTurn 。 两者都调用yielding操作来存储转弯数据。 然后两种方法检查是否有2名玩家轮stream发送,find肯定的,然后endRound运行两次。
  • 玩家在回合超时时请求sendTurn 。 在这种情况下, endRound被超时和玩家的方法调用,从而再次运行两次。
  • 对上述问题的错误修复可能会导致饥饿, endRound永远不会被调用。

您可以通过几种方式来解决这个问题,或者在Node中或者在数据库中进行同步。

  • 由于一次只有一个光纤实际上可以更改Node中的值,所以如果不调用yielding操作,则可以保证避免可能的竞争条件。 所以你可以在内存中而不是在数据库中caching诸如转向状态之类的东西。 但是,这要求caching正确完成,不会转移到集群环境。
  • endRound代码移动到方法调用本身之外,使用其他方法来触发它。 这是我采取的方法,确保只有计时器或最终玩家触发轮的结束,而不是两个( 这里查看使用observeChanges的实现)。
  • 在集群环境中,只能使用数据库进行同步,可能需要使用条件更新操作和primefaces操作符。 像下面这样:

     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, eg endRound) break; } else { // Race condition detected, try again } } 

上述方法是原始的,可能会导致高负载下的数据库性能不佳。 它也不处理定时器,但我确信有一些想法可以弄清楚如何扩展它以更好地工作。

你也可能想看看这个计时器代码的一些其他的想法。 一旦我有一些时间,我将把它扩展到你描述的全部设置。