我有一个问题,我目前处于一种情况,我有一个解决方案,但我不太确定它是否100%解决了手头的问题,因为我没有编写可以验证我的解决方案的测试。
我会喜欢你对这个问题的看法,也许是对更优雅的解决方案的建议,甚至可能是一种完全避免这个问题的方法。
这是:
我正在创建一个游戏,您可以创建或加入开放的房间/游戏。
用户界面中有一个游戏列表,当您点击游戏时,您尝试加入该游戏。
每个游戏都有一个赌注(您赢或输的信用额度),创作者设置的任何人都必须匹配。
在服务器端,在我让玩家真正加入房间之前,我必须确认他的信用余额足以匹配他加入的游戏的赌注。这将通过API调用。
现在,如果两个玩家一次加入游戏,我们可以说第一个玩家加入的验证需要3秒,但第二个玩家的验证只需要1秒。
由于房间是1比1,如果其他人已经做过,我不能让玩家加入。
我可以通过检查游戏中的玩家是否已经完成此操作:
// game already full
if (game.p2) {
return socket.emit("join_game_reply", {
err: "Someone else already joined."
})
}
但是,手头的问题是,在检查之后,我必须验证余额。
所以我们得到这样的东西:
socket.on("join_game", data => {
const game = openGames[data.gameId}
// game already full
if (game.p2) {
return socket.emit("join_game_reply", {
err: "Someone else already joined."
})
}
// check if users balance is sufficient to match bet of room creator
verifyUserBalance(socket.player, game.bet)
.then(sufficient => {
if(sufficient){
// join game
game.p2 = socket.player
}
})
})
这里的问题:
如果当玩家X点击join
游戏开启时,验证开始,但在验证playerY加入并在playerX之前完成验证并因此设置为game.p2
时。不久后完成了playerX的验证,然后服务器继续将game.p2设置为playerX,让playerY的UI状态为ingame,即使在服务器上他已经不在了。
我的解决方案是在验证后再次进行检查:
socket.on("join_game", data => {
const game = openGames[data.gameId}
// game already full
if (game.p2) {
return socket.emit("join_game_reply", {
err: "Someone else already joined."
})
}
// check if users balance is sufficient to match bet of room creator
verifyUserBalance(socket.player, game.bet)
.then(sufficient => {
if(sufficient){
// join game
if (game.p2) {
return socket.emit("join_game_reply", {
err: "Someone else already joined."
})
game.p2 = socket.player
}
}
})
})
我认为这是有效的原因是因为nodeJS是单线程的,因此我可以确保在验证后我只允许玩家加入,如果没有其他人同时加入。
写完之后我真的觉得它会起作用,所以如果你看到任何错误,请让我犯错误!非常感谢您抽出宝贵时间!
答案 0 :(得分:2)
您的代码可以运行,但我认为这是短期的自举,您将不得不在中期进行更改。
它会起作用
一个。如果你只有一台服务器。
B中。如果您的服务器没有崩溃
℃。如果您只有一个同步操作(此处为game.p2 = socket.player
)
为了扩大您的基础设施,我担心它不会起作用。
您不应该使用nodejs变量(如openGames
)来存储数据,而是从缓存数据库中检索它们(如redis)。这个redis数据库将是你唯一的事实来源。
如果您的服务器崩溃(出于任何原因,例如完整磁盘......),将会发生同样的问题。您将丢失存储在nodejs变量中的所有数据。
如果您想在工作流程中添加一个操作(例如将下注金额放入托管),则需要在此操作(以及房间加入失败)时捕获失败并确保存在全部 - 或者没有机制(托管+加入或没有)。
您可以在代码中管理它,但它会变得非常复杂。
在处理资金+行动时,我认为您应该使用数据库的交易功能。我会使用例如Redis Transactions。
答案 1 :(得分:0)
您需要在服务器上进行“验证并加入”原子操作,以便其他任何人都不会导致竞争条件。解决方案的方法有很多种。最佳解决方案只会影响特定游戏,而不会影响加入其他游戏的过程。
这是一个想法:
创建“临时加入游戏”的方法。这将基本上保留您在游戏中的位置,然后您检查用户是否验证游戏。这可以防止其他人加入您第一次参加游戏并正在进行验证。
当其他人进入临时加入游戏时,但游戏已经有临时用户,加入功能可以返回尚未解决的承诺。如果先前的临时联接验证并完成,则此承诺将拒绝,因为游戏已满。如果其他临时联接无法验证,那么请求临时联接的第一个将解决,然后可以继续进行验证过程。
如果第二个用户正确验证并转换为已完成的加入游戏,它将拒绝任何其他等待此游戏的临时加入的承诺。如果验证失败,则返回步骤2,下一次等待有机会。
通过这种方式,每个游戏基本上都有一群用户等待进入游戏。只要游戏中没有经过验证的用户,队列就会挂起,所以每当有人不验证时,队列中的下一个队员就会加入。
出于性能和用户体验的原因,您可能希望在队列中等待时实现超时,并且您可能希望限制队列中可以有多少用户(可能没有必要让100个用户进入队列因为它们不太可能都无法验证)。
重要的是要了解验证和连接需要在服务器上全部实现,因为这是确保流程完整性并控制流程以避免竞争条件的唯一方法。