我们有一个Web应用程序(它是一个游戏),有许多不同的表单和元素,它们充当按钮并在服务器上触发一些操作。问题是如果用户太快点击按钮或在两个标签中打开网站然后同时发出一些操作,用户有时会混淆我们的应用程序。我们有一些基本的保护 - MySQL事务,一些双击防止Javascripts,但无论如何有时候只是跳过了。当然,最好的方法是以不会混淆系统的方式重新设计所有SQL事务和支持功能。这种混淆的一个例子是同时发出两个更新 - 一个Web请求更改了db中的某些内容,但第二个请求仍然使用旧数据运行,因此SQL更新返回“受影响的行数为零”,因为第一个事务已经更改数据库中的数据。 显而易见的解决方案是在UPDATE之前再次读取数据以查看它是否仍然需要更新,但这意味着在任何地方放置更多的双SELECT查询,这不是一个好的解决方案 - 为什么要从db读取相同的数据两次? 此外,我们已经考虑使用一些隐藏令牌在服务器上对每个更新请求进行比较并拒绝具有相同令牌ID的操作,但这也意味着触及很多代码位置并可能将新错误引入系统,除了工作正常外这个问题。
操作流程的逻辑如下:如果用户同时发出两个请求,则第二个请求应该等到第一个请求完成。但是我们还必须考虑在我们的应用程序中有许多重定向(例如在POST之后以避免在用户刷新页面时进行双重POST),因此解决方案不应该为用户创建死锁。
所以问题是: 什么是最容易的全局修复,它可以以某种方式使所有用户操作顺序?有没有好的通用解决方案?
我们正在使用MySQL和PHP。
答案 0 :(得分:2)
我很高兴你意识到它只是一个糟糕的临时解决方案,你应该优化你的代码。 忘记你的代币和东西。 最简单且最有效的方法可能是对每个操作的共享文件进行独占文件锁定。你可以想象这就像一块木头,只有一个可以容纳id,只有拥有它的人才可以说话或做某事。
<?php
$fp = fopen("/tmp/only-one-bed-available.txt", "w+");
if (flock($fp, LOCK_EX)) { // do an exclusive lock
// do some very important critical stuff here which must not be interrupted:
// sleeping.
sleep(60);
echo "I now slept 60 seconds";
flock($fp, LOCK_UN); // release the lock
} else {
echo "Couldn't get the lock!";
}
fclose($fp);
?>
如果您执行此脚本10次并行,则需要10分钟才能完成(因为只有一张床可用!)并且您将每隔60秒(大约)看到回声“我现在睡觉了......”。
这将序列化此代码的所有执行GLOBALLY。 这可能不是你想要的(你想要每个用户,不是吗?) 我确信你有类似用户ID的东西,否则使用外国IP地址,并为每个用户提供一个唯一的文件名:
<?php $fp = fopen("/tmp/lock-".$_SERVER["REMOTE_ADDR"].".txt", "w+"); ?>
您还可以为一组操作定义锁定文件名。 Maby用户可以做一些并行的东西,但只有这个操作会产生问题吗?给它一个标签并将其包含在锁定文件名中!
这种做法确实不错,可以使用!只有很少的方法可以更快地完成它(mysql内部,共享内存段,更高层的序列化,如负载均衡器...)
通过上面发布的解决方案,您可以在您的应用程序中执行此操作,这可能很好。 您也可以在Mysql中使用相同的方案: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock
但是对于您的用例,PHP实现可能更容易,更好。
如果你真的想踢屁股那里有一个更优雅的解决方案, 只需查看此功能http://www.php.net/manual/en/function.sem-get.php
答案 1 :(得分:1)
两个标签并同时执行操作?你的用户有多快?
无论如何:只允许一个选项卡已经是一个很好的解决方案。
只有一个选项卡,您可以在javascript中堆叠操作,并在最后一次调用后获得返回值时提交一个新操作。
答案 2 :(得分:1)
如何使用mysql的get_lock功能? 也许你应该看一下mysql的transactions。