Запретить одновременные транзакции в веб-приложении - PullRequest
4 голосов
/ 23 сентября 2010

У нас есть веб-приложение (это игра) с множеством различных форм и элементов, которые действуют как кнопки и запускают некоторые действия на сервере.Проблема в том, что пользователи иногда могут запутать наше приложение, если он слишком быстро нажимает на кнопки или открывает веб-сайт на двух вкладках, а затем одновременно выполняет некоторые действия.У нас есть некоторая базовая защита - транзакции MySQL, некоторые двойные щелчки, предотвращающие Javascripts, но в любом случае иногда что-то просто пропускается.Конечно, наилучшим способом было бы перепроектировать все транзакции SQL и вспомогательные функции таким образом, чтобы не допустить путаницы в системе.Одним из примеров такой путаницы является одновременная выдача двух обновлений - один веб-запрос что-то меняет в БД, но второй запрос по-прежнему работает со старыми данными, поэтому обновление SQL возвращает «количество затронутых строк было равно нулю», поскольку первая транзакция уже измениласьДанные в БД.Очевидное решение состоит в том, чтобы снова прочитать данные прямо перед UPDATE, чтобы увидеть, нужно ли их обновлять, но это означает, что нужно помещать гораздо больше двойных запросов SELECT везде, а не хорошее решение - зачем читать одни и те же данные из db дважды?Кроме того, мы рассмотрели возможность использования некоторого скрытого токена для сравнения на сервере при каждом запросе на обновление и запрета операций, которые имеют одинаковый идентификатор токена, но это также означает, что нужно затрагивать действительно много мест кода и, возможно, вводить новые ошибки в систему, которая работает нормально, кромеэто одна проблема.

Логика потока действий будет следующей: если пользователь выполнил два запроса одновременно, второй запрос должен дождаться завершения первого.Но мы также должны учитывать, что в нашем приложении много перенаправлений (например, после POST, чтобы избежать двойного POST при обновлении страницы пользователем), поэтому решение не должно создавать тупиков для пользователя.

Так что вопрос: что было бы самым простым из возможных глобальных исправлений, которые могли бы просто как-то сделать все пользовательские операции последовательными?Есть ли хорошее универсальное решение?

Мы используем MySQL и PHP.

Ответы [ 3 ]

2 голосов
/ 23 сентября 2010

Я рад, что вы понимаете, что это только плохое и временное решение, и вы должны оптимизировать свой код. Забудьте свои жетоны и прочее. Самый простой и все же эффективный способ - это, вероятно, иметь эксклюзивную блокировку файла для общего файла на одну операцию. Вы можете представить это как кусок дерева, и только один может иметь удостоверение личности, и только тому, кто его держит, разрешено говорить или что-то делать.

<?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 секунд (приблизительно).

Это сериализует ВСЕ выполнения этого кода ГЛОБАЛЬНО. Это, вероятно, не то, что вы хотите (вы хотите это для каждого пользователя, не так ли?) Я уверен, что у вас есть что-то вроде User-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 голос
/ 23 сентября 2010

Как насчет использования функции mysql get_lock ? Также, возможно, вам стоит взглянуть на транзакции в mysql.

1 голос
/ 23 сентября 2010

Две вкладки и одновременное действие?Как быстро ваши пользователи?

В любом случае: использование только одной вкладки уже было бы хорошим решением.

Имея только одну вкладку, вы можете складывать действия в javascript и отправлять новую, как только вымы получили возвращаемое значение из последнего вызова.

...