Лучший способ справиться с проблемами concurrency

У меня есть среда LAPP (linux, apache, postgresql и php), но вопрос такой же как у Postgres, так и для Mysql.

У меня разработано приложение cms i, которое обрабатывает клиенты, документы (оценки, счета и т.д.) и другие данные, структурированные в 1 постгерской БД со многими схемами (по одному для каждого нашего клиента, использующего приложение); допустим около 200 схем, каждый из которых используется одновременно на 15 человек (avg).

EDIT: у меня есть поле timestamp с именем last_update для каждой таблицы и триггер, который обновляет временную метку каждый раз, когда строка обновляется.

Ситуация такова:

  • Люди Foo и Bar редактируют документ 0001, используя форму с каждой информацией о документе.
  • Foo измените данные отправки, например.
  • Сменить номера телефонов и некоторые элементы в документе.
  • Foo нажмите кнопку "Сохранить" , приложение обновит db.
  • Нажмите кнопку "Сохранить" после строки, повторно отправив форму со старыми деталями отправки.
  • В базе данных изменения Foo были потеряны.

Ситуация, в которой я хочу:

  • Люди Foo, Bar, John, Mary, Paoul редактируют документ 0001, используя форму с каждой информацией о документе.
  • Foo измените данные отправки, например.
  • Бар и другие меняют что-то еще.
  • Foo нажмите кнопку "Сохранить" , приложение обновит db.
  • Бар и другие получают предупреждение "Предупреждение! этот документ был изменен кем-то другим. Нажмите здесь, чтобы загрузить фактические данные".

Я хотел бы использовать ajax для этого; просто используя скрытое поле с идентификатором документа и последней обновленной меткой времени, каждые 5 секунд проверяют, совпадает ли последнее обновленное время и ничего не делают, иначе покажите диалоговое окно предупреждения.

Итак, страница check-last-update.php должна выглядеть примерно так:

<?php
//[connect to db, postgres or mysql]
$documentId = isset($_POST['document-id']) ? $_POST['document-id'] : 0;
$lastUpdateTime = isset($_POST['last-update-time']) ? $_POST['last-update-time'] : 0;
//in the real life i sanitize the data and use prepared statements;
$qr = pg_query("
    SELECT
        last_update_time
    FROM
        documents
    WHERE
        id = '$documentId'
");
$ray = pg_fetch_assoc($qr);
if($ray['last_update_time'] > $lastUpdateTime){
    //someone else updated the document since i opened it!
    echo 'reload';
}else{
    echo 'ok';
}
?>

Но я не хочу подчеркивать db каждые 5 секунд для каждого пользователя, у которого есть один (или более...) документов.

Итак, что может быть другим эффективным решением без использования db?

Я думал использовать файлы, создавая, например, пустой файл txt для каждого документа, и каждый раз, когда документ обновляется, я также "касаюсь" файла, обновляющего "последнее измененное время"... но я предполагаю, что это будет медленнее, чем db и даст проблемы, когда у меня будет много пользователей, редактирующих один и тот же документ.

Если у кого-то есть лучшая идея или какое-либо предложение, опишите это подробно!

* - - - - - UPDATE - - - - - *

Я определенно выбрал NOT, чтобы не ударить db для проверки "последней отметки времени обновления", не возражаете, если запрос будет довольно быстрым, у (основного) сервера базы данных есть другие задачи для полного заполнения, не нравится идея увеличить его перегрузку для этой вещи.

Итак, im так:

  • Каждый раз, когда документ обновляется кем-то, я должен сделать что-то, чтобы подписать новую временную метку вне среды db, например. не спрашивая db. Мои идеи:
    1. Файловая система: для каждого документа я создаю файлы empry txt, названные как идентификатор документа, каждый раз, когда документ обновляется, я "касаюсь" файла. Я ожидаю, что у вас будут тысячи этих пустых файлов.
    2. APC, php cache: это, вероятно, будет более гибким, чем первый, но им интересно, сохраняют ли тысячи и тысячи данных навсегда в apc, чтобы замедлить выполнение самого php или использовать память сервера. Я немного боюсь выбирать этот путь.
    3. Другие db, sqlite или mysql (которые быстрее и легче с простыми структурами db) используются для хранения только идентификаторов документов и временных меток.
Независимо от того, как я выбираю (файлы, apc, sub-db), я серьезно думаю использовать другой веб-сервер (lighttp?) в поддомене, чтобы обрабатывать все эти запросы долгого опроса.

ДАЙТЕ ДРУГОЕ ИЗМЕНЕНИЕ:

Файл не работает.

APC может быть решением.

Удар по БД также может быть решением, создающим таблицу для обработки временных меток (только с двумя столбцами, document_id и last_update_timestamp), которые должны быть как можно более быстрыми и легкими.

Длительный опрос: этот способ я выберу, используя lighttpd под apache для загрузки статических файлов (изображения, css, js и т.д.), и только для этого типа долгого опроса; Это облегчит загрузку apache2, специально для опроса.

Apache будет проксировать все эти запросы на lighttpd.

Теперь мне нужно решить только решение db и решение APC.

p.s: спасибо всем, кто уже ответил мне, вы действительно были полезны!

Ответ 1

Я согласен, что я, вероятно, не попал бы в базу данных для этого. Я полагаю, что для хранения этой информации я бы использовал кеш APC (или какой-то другой кеш в памяти). То, что вы описываете, явно оптимистично блокируется на подробном уровне записи. Чем выше уровень в структуре базы данных, тем меньше вам нужно иметь дело. Похоже, вы хотите проверить несколько таблиц внутри структуры.

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

С этой архитектурой вам нужно будет сделать следующее (в дополнение к настройке APC):

  • при любом обновлении любой (применимой) таблицы обновите запись кэша APC новой меткой времени.

  • внутри ajax просто заходит так же "назад", как php (чтобы получить кеш APC для проверки записи), а не весь путь "назад" к базе данных.

Ответ 2

Я думаю, вы можете использовать условие в инструкции UPDATE, например WHERE ID =? И LAST_UPDATE =?.

Идея состоит в том, что вам удастся только обновить, когда вы последний читаете эту строку. Если кто-то еще что-то совершил, вы потерпите неудачу, и как только вы узнаете, что не удалось, вы можете запросить изменения.

Ответ 3

Для каждой записи вам понадобится поле типа штампа. Что это не имеет значения, если вы можете гарантировать, что внесение каких-либо изменений в запись приведет к тому, что штамп этой версии будет отличаться. Лучшая практика заключается в том, чтобы затем проверить и убедиться, что печать загруженной записи совпадает с печатью версии в БД, когда пользователь нажимает кнопку "Сохранить", и если она отличается от нее.

Как вы справляетесь с этим, зависит от вас. По крайней мере, вы хотите предложить перезагрузить из БД, чтобы пользователь мог проверить, что они все еще хотят сохранить. Один из них - попытаться объединить свои изменения в новую запись БД, а затем попросить их проверить правильность работы слияния.

Если вы хотите периодически опросить любой БД, способный обрабатывать вашу систему, вы должны иметь возможность загружать опрос. 10 опросов пользователей каждые 5 секунд - 2 транзакции в секунду. Это тривиальная нагрузка, и это не должно быть проблемой. Чтобы средняя загрузка была близка к фактической нагрузке, просто слегка измените время опроса (вместо того, чтобы делать это ровно через каждые 5 секунд, например, каждые 4-6 секунд).

Ответ 4

Донни ответ (опрос), вероятно, ваш лучший вариант - простой и работает. Он будет охватывать почти каждый случай (маловероятный простой поиск PK может повредить производительность даже на очень популярном сайте).

Для полноты, и если вы хотите избежать опроса, вы можете использовать push-model. Там различные способы описаны в статье Википедии. Если вы можете поддерживать кеш-запись (каждый раз, когда вы обновляете запись, вы обновляете кеш), то вы почти полностью можете исключить загрузку базы данных.

Не используйте столбец "last_updated" с меткой времени. Изменения в пределах одной секунды не являются неслыханными. Если вы добавите дополнительную информацию (сервер, который сделал обновление, удаленный адрес, порт и т.д.), Вы могли бы сбежать, чтобы убедиться, что, если два запроса пришли в тот же самый момент, на тот же сервер, вы можете обнаружить разницу. Однако, если вам нужна эта точность, вы можете использовать уникальное поле редактирования (это необязательно должно быть инкрементирующее целое, просто уникальное в течение этой продолжительности записи).

Кто-то упомянул о постоянных соединениях - это снизит стоимость установки запросов на опрос (каждое соединение, естественно, потребляет ресурсы в базе данных и хост-машине). Вы должны поддерживать единственное соединение (или как можно меньше) все время (или как можно дольше) и использовать его (в сочетании с кешированием и записью, если это необходимо).

Наконец, существуют инструкции SQL, которые позволяют вам добавить условие в UPDATE или INSERT. Мой SQl действительно ржавеет, но я думаю, что это что-то вроде UPDATE ... WHERE .... Чтобы соответствовать этому уровню защиты, вам нужно будет сделать свою собственную блокировку строк перед отправкой обновления (и вся обработка ошибок и очистка, которые могут повлечь за собой). Вряд ли вам это понадобится; Я просто упоминаю об этом для полноты.

Edit:

Ваше решение отлично звучит (отметки времени кеша, запросы опроса прокси на другой сервер). Единственное изменение, которое я сделал бы, это обновить кешированные метки времени при каждом сохранении. Это позволит сохранить кеш более свежим. Я также проверял бы временную метку непосредственно из db при сохранении, чтобы предотвратить кражу данных из-за устаревших данных кеша.

Если вы используете APC для кеширования, то второй HTTP-сервер не имеет смысла - вам придется запускать его на одном компьютере (APC использует общую память). Такая же физическая машина будет выполнять эту работу, но с дополнительными служебными данными второго HTTP-сервера. Если вы хотите отключить запросы опроса на второй сервер (lighttpd, в вашем случае), тогда было бы лучше настроить lightttpd перед Apache на второй физической машине и использовать общий кэш-сервер (memcache), чтобы Lighttpd-сервер может читать кэшированные метки времени, и Apache может обновлять временные метки кэширования. Обоснованием для установки lighttpd перед Apache является, если большинство запросов являются запросами на опрос, чтобы избежать использования процесса Apache более тяжелого веса.

Вам, вероятно, вообще не нужен второй сервер. Apache должен иметь возможность обрабатывать дополнительные запросы. Если он не может, я бы пересмотрел вашу конфигурацию (в частности, директивы, определяющие, сколько рабочих процессов вы выполняете, и сколько запросов, с которыми им разрешено обрабатывать до их уничтожения).

Ответ 5

Ваш подход к поиску базы данных является лучшим. Если вы делаете это каждые 5 секунд, и у вас есть 15 одновременных пользователей, то вы смотрите ~ 3 вопроса в секунду. Это тоже очень маленький запрос, возвращающий только одну строку данных. Если ваша база данных не может обрабатывать 3 транзакции секунду, вам, возможно, придется искать лучшую базу данных, потому что 3 запроса/секунда - ничто.

Отметьте отметки в таблице, чтобы вы могли быстро увидеть, изменилось ли что-либо, не изменяя каждое поле.

Ответ 6

Hibernate использует для этого поле версии. Дайте каждой таблице такое поле и используйте триггер, чтобы увеличить его при каждом обновлении. При сохранении обновления сравните текущую версию с версией, когда данные были прочитаны ранее. Если они не совпадают, выведите исключение. Используйте транзакции для создания атома check-and-update.

Ответ 7

Это немного не соответствует теме, но вы можете использовать пакет PEAR (или пакет PECL, я забыл, какой) xdiff, чтобы отправить хорошее руководство пользователя, когда вы столкнулись с конфликтом.

Ответ 8

Прежде всего, обновите поля, которые были изменены при записи в базу данных, это уменьшит нагрузку на базу данных.

Во-вторых, запросите отметку времени последнего обновления, если у вас есть старая временная метка, а затем текущая версия в базе данных, а затем выпустите предупреждение клиенту.

В-третьих, нужно каким-то образом передать эту информацию клиенту, хотя какое-то постоянное соединение с сервером, позволяя одновременное двухстороннее соединение.

Ответ 9

Опрос редко является хорошим решением.
Вы можете выполнить проверку метки времени только тогда, когда пользователь (с открытым документом) делает что-то активное с документом, например, прокруткой, перемещением мыши над ним или началом редактирования. Затем пользователь получает предупреждение, если документ был изменен.

.....
Я знаю, что это не то, о чем вы просили, но... почему не редактирование-синглтон?
Синглтон может быть столбцом userID в таблице документов.
Если пользователь хочет отредактировать документ, документ заблокирован для редактирования другими пользователями.

Или иметь правки-одиночные списки в отдельных полях/группах информации.

Только один пользователь может редактировать документ за раз. Если у другого пользователя открыт документ и вы хотите отредактировать одну метку времени, убедитесь, что документ был изменен и перезагружен.

С помощью singleton нет опроса и проверяется только одна метка времени, когда пользователь "прикасается" и/или хочет отредактировать документ.

Но, возможно, одноэлементный механизм не подходит вашей системе.

С уважением
    Sigersted

Ответ 10

Ahhh, хотя это было проще.

Итак, давайте сделаем следующее: у меня есть общая база данных (pgsql или mysql не имеет значения), которая содержит много общих объектов.

У меня есть $x (фактически $x = 200, но растет, надеюсь, скоро достигнет 1000) точной копии этой базы данных, а для каждого из них до 20 (avg 10) пользователей в течение 9 часов в день.

Если один из этих пользователей просматривает запись, любая запись, я должен посоветовать ему, если кто-то отредактирует одну и ту же запись.

Скажем, Foo смотрит документ 0001, садится за кофе, открывает бар и редактирует тот же документ, когда Foo возвращается, он должен увидеть "Предупреждение, кто-то другой отредактировал этот документ! нажмите здесь, чтобы обновить страницу. '.

Это все, что мне нужно, возможно, я продолжу эту ситуацию, добавив способ увидеть изменения и откаты, но это не главное.

Некоторые из вас предложили проверить временную метку последнего обновления только тогда, когда foo пытается сохранить документ; Может быть и решением, но мне нужно что-то в режиме реального времени (10 секунд deelay).

Длинный опрос, плохой способ, но, похоже, единственный.

Итак, что я сделал:

  • Установленный Lighttp на моей машине (и php5 как fastcgi);
  • Загруженный модуль прокси-сервера apache2 (все или ошибка 403 поразит вас);
  • Изменен порт lighttpd с 80 (который используется apache2) до 81;
  • Конфигурированный apache2 для проксирования запроса от mydomain.com/polling/* к polling.mydomain.com(подается с Lighttp)
  • Теперь у меня есть другая вспомогательная HTTP-служба, которую я буду использовать как для опроса, так и для загрузки статического контента (изображений и т.д.), чтобы уменьшить загрузку apache2.
  • Станьте я не хотите, чтобы nuke базы данных проверяли метку времени, я попробовал некоторую систему кешей (которую можно вызвать из php).
    • APC: довольно простая установка и управление, очень легкая и быстрая, это был бы мой первый выбор. Если бы только кеш был бы разделен между двумя процессами cgi (мне нужно хранить в кеше значение из процесса apache2 php, и прочитайте его из процесса lighttpd php)
    • Memcached: примерно в 4-5 раз медленнее, чем APC, но запускается как один процесс, который можно затронуть везде в моей среде. Я пойду с этим, atm. (даже если это медленнее, использование, которое я сделаю, будет относительно простым).

Теперь мне просто нужно попробовать эту систему, загружая некоторые тестовые данные, чтобы увидеть, что ho будет двигаться "под давлением" и оптимизировать его.

Я уверен, что эта среда будет работать для других ситуаций с большим количеством опросов (chat?)

Спасибо всем, кто меня услышал!

Ответ 11

Я предлагаю: при первом запросе записи, которая может быть изменена, вставьте ее в локальную копию. Когда "обновление", сравните копию в заблокированной таблице/строке с вашей копией, и если она изменится, отбросьте ее обратно пользователю.