【问题标题】:Prevent simultaneous transactions in a web application防止 Web 应用程序中的同时事务
【发布时间】:2010-09-23 16:13:17
【问题描述】:

我们有一个 Web 应用程序(它是一个游戏),其中包含许多各种形式和元素,它们充当按钮并在服务器上触发一些操作。问题是,如果用户点击按钮太快或在两个选项卡中打开网站然后同时发出一些操作,有时会混淆我们的应用程序。我们有一些基本的保护——MySQL 事务,一些双击防止 Javascripts,但无论如何有时有些东西会跳过。当然,最好的方法是重新设计所有的 SQL 事务和支持功能,使其不会混淆系统。这种混淆的一个例子是同时发布两个更新 - 一个 Web 请求更改了数据库中的某些内容,但第二个请求仍然使用旧数据运行,因此 SQL 更新返回“受影响的行数为零”,因为第一个事务已经更改数据库中的数据。 显而易见的解决方案是在 UPDATE 之前再次读取数据以查看它是否仍需要更新,但这意味着在各处放置更多的双重 SELECT 查询,这不是一个好的解决方案 - 为什么要从 db 读取相同的数据两次? 我们还考虑使用一些隐藏的令牌在服务器上比较每个更新请求并拒绝具有相同令牌 id 的操作,但这也意味着要触及很多代码位置,并可能将新错误引入系统,除了工作正常之外这个问题。

动作流的逻辑如下:如果用户同时发出两个请求,第二个请求应该等到第一个请求完成。但我们还必须考虑到我们的应用程序中存在许多重定向(例如在 POST 之后以避免用户刷新页面时重复 POST),因此该解决方案不应为用户造成死锁。

所以问题是: 什么是最简单的全局修复,它可以以某种方式使所有用户操作按顺序进行?有什么好的通用解决方案吗?

我们正在使用 MySQL 和 PHP。

【问题讨论】:

    标签: php mysql locking simultaneous


    【解决方案1】:

    很高兴您意识到这只是一个糟糕的临时解决方案,您应该优化您的代码。 忘记你的代币和东西。 最简单且仍然有效的方法可能是每次操作都对共享文件进行排他文件锁定。你可以把它想象成一块木头,只有一个人可以持有 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 秒(大约)看到一次回显“我现在睡觉了...”。

    这会在全球范围内序列化此代码的所有执行。 这可能不是您想要的(您希望基于每个用户,不是吗?) 我相信你有类似用户 ID 的东西,否则使用外国 IP 地址,并且每个用户都有一个唯一的文件名:

    &lt;?php $fp = fopen("/tmp/lock-".$_SERVER["REMOTE_ADDR"].".txt", "w+"); ?&gt;

    您还可以为一组操作定义锁定文件名。也许用户可以并行做一些事情,但只有这个操作会产生问题?给它一个标签并将其包含在他的锁定文件名中!

    这个做法真的没那么糟糕,可以用!只有很少的方法可以更快地做到这一点(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

    【讨论】:

    • P.S: $_SERVER["REMOTE_ADDR"] 可能不是唯一的,因为很多 ip 都在路由器后面。我宁愿调用 uniqid() 并将值存储在用户 cookie 中。
    • 大声笑真正的阿尔弗雷德,但有很多路由器后面的用户不接受 cookie,共享相同的用户代理,禁用 javascript 并具有相同的浏览器接受设置:D 他们呢? :D ...但你是对的。一个组合可能会很好。
    【解决方案2】:

    两个选项卡和同时操作?您的用户有多快?

    无论如何:只允许一个选项卡已经是一个很好的解决方案。

    只有一个选项卡,您可以在 javascript 中堆叠操作,并在获得上次调用的返回值后提交一个新的操作。

    【讨论】:

    • 也许用户不是那么快,但我们的服务器有时可能有点滞后 :D 因此,如果用户在两个选项卡或同一选项卡中两次点击出售相同的游戏项目,他可能会得到他的游戏钱回来了两次。
    • 为什么不在服务器端检查用户是否可以执行操作?检查它,然后执行它。在 javascript 中,防止双击:一旦用户单击,执行 javascript 操作(移动对象,删除它)然后发送请求。如果您在 javascript 中进行验证,您可以确定该操作是有效的。但仅在 javascript 中,无法解决“两个选项卡”问题。
    • 问题在于一些冗长的操作。如果用户在第一个请求在 SELECT 之后但在更新之前发出第二个请求,那么我有以下内容: SELECT(1) SELECT(2) UPDATE(1), UPDATE(2) - and update 2 is wrong因为它是在旧数据上执行的,忽略了 UPADTE(1) 之后的更改。
    • 这就是为什么要进行事务和锁定的原因:如果您要修改内容添加锁定,请执行选择/更新然后释放锁定。
    【解决方案3】:

    使用mysql的get_lock函数怎么样? 另外也许你应该看看mysql的transactions

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-27
      • 1970-01-01
      • 1970-01-01
      • 2012-08-07
      • 2011-01-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多