问题描述
在我的Meteor应用程序中实现一个基于turnbased的多人游戏服务器,客户端通过发布/订阅接收游戏状态,并可以调用Meteor方法 sendTurn 发送回合数据(他们不能直接更新游戏状态集合)。var endRound = function(gameRound){
//检查gameRound是否已经结束/
//如果循环结果已经确定
// - > yes:
do nothing
// - > no:
//确定轮次结果
//更新集合
//创建下一个gameRound
};
Meteor.methods({
sendTurn:function(turnParams){
//查找gameRound数据
//验证turnParams对gameRound
//存储(更新gameRound集合对象)
//让所有客户轮流发送本轮?
//是 - >调用endRound
// no - > ;等待其他客户端发送转弯
}
});
要实施时间限制,我想等待一段时间调用 sendTurn ),然后确定循环结果 - 但只有当循环结果尚未在 sendTurn 。
我应该如何在服务器上实现此时间限制?
方法来实现这将是调用 Meteor.setTimeout(endRound,< roundTimeLimit>)。
问题:
-
我假设我应该在 sendTurn 和 endRound (?)中同步更新集合(无回调)消除种族条件? (阅读对关于同步数据库操作也会产生,我怀疑)
-
在这方面,每个请求在我的上下文中的(函数 endRound 通过客户端方法调用和/或在服务器 setTimeout )中调用?
-
在多服务器/集群环境中,
伟大的问题,它比它看起来更棘手。首先,我想指出,我已经在下面的repos中实现了这个问题的解决方案:
In a multi-server / clustered environment, (how) would this work?
Great question, and it's trickier than it looks. First off I'd like to point out that I've implemented a solution to this exact problem in the following repos:
To summarize, the problem basically has the following properties:
- Each client sends in some action on each round (you call this sendTurn)
- When all clients have sent in their actions, run endRound
- Each round has a timer that, if it expires, automatically runs endRound anyway
- endRound must execute exactly once per round regardless of what clients do
Now, consider the properties of Meteor that we have to deal with:
- Each client can have exactly one outstanding method to the server at a time (unless this.unblock() is called inside a method). Following methods wait for the first.
- All timeout and database operations on the server can yield to other fibers
This means that whenever a method call goes through a yielding operation, values in Node or the database can change. This can lead to the following potential race conditions (these are just the ones I've fixed, but there may be others):
- In a 2-player game, for example, two clients call sendTurn at exactly same time. Both call a yielding operation to store the turn data. Both methods then check whether 2 players have sent in their turns, finding the affirmative, and then endRound gets run twice.
- A player calls sendTurn right as the round times out. In that case, endRound is called by both the timeout and the player's method, resulting running twice again.
- Incorrect fixes to the above problems can result in starvation where endRound never gets called.
You can approach this problem in several ways, either synchronizing in Node or in the database.
- Since only one Fiber can actually change values in Node at a time, if you don't call a yielding operation you are guaranteed to avoid possible race conditions. So you can cache things like the turn states in memory instead of in the database. However, this requires that the caching is done correctly and doesn't carry over to clustered environments.
- Move the endRound code outside of the method call itself, using something else to trigger it. This is the approach I've taken which ensures that only the timer or the final player triggers the end of the round, not both (see here for an implementation using observeChanges).
In a clustered environment you will have to synchronize using only the database, probably with conditional update operations and atomic operators. Something like the following:
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 } }
The above approach is primitive and probably results in bad database performance under high loads; it also doesn't handle timers, but I'm sure with some thinking you can figure out how to extend it to work better.
You may also want to see this timers code for some other ideas. I'm going to extend it to the full setting that you described once I have some time.
这篇关于Meteor.setTimeout和Meteor.methods之间的并发性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!