Как разработать многопользовательское веб-приложение на Ajax, которое будет одновременно безопасно

95

У меня есть веб-страница, на которой отображается большой объем данных с сервера. Связь осуществляется через ajax.

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

Если пользователь B одновременно обращается к странице и создает новый объект данных, он снова сообщит серверу через ajax, и сервер вернется с новым объектом для пользователя.

На странице A у нас есть данные с переименованным объектом. А на странице B у нас есть данные с новым объектом. На сервере у данных есть как переименованный объект, так и новый объект.

Какие у меня есть варианты для синхронизации страницы с сервером, когда несколько пользователей используют ее одновременно?

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

Если это помогает, в этом конкретном примере веб-страница вызывает статический веб-метод, который запускает хранимую процедуру в базе данных. Хранимая процедура вернет все данные, которые она изменила, и не более того. Затем статический веб-метод пересылает возвращенную хранимую процедуру клиенту.

Редактирование награды:

Как разработать многопользовательское веб-приложение, которое использует Ajax для связи с сервером, но избегает проблем с параллелизмом?

Т.е. одновременный доступ к функциям и данным в базе данных без риска повреждения данных или состояния.

Райнос
источник
не так уверен, но у вас может быть страница, такая как facebook, где браузер отправляет запрос ajax, постоянно ищет изменения в базе данных сервера и обновляет их в браузере
Сантош Линкха
Сериализация состояния клиента, а затем сообщение серверу через ajax, вот мое состояние, что мне нужно обновить, - это вариант. Но требует, чтобы клиент знал, как обновить любую информацию в одном месте.
Raynos
1
Лучшее решение для параллелизма на стороне пользователя - это не просто один из вариантов push? Веб-сокеты, кометы и т. Д.
Дэвин
@davin вполне может быть. Но я не знаком с кометой, и веб-сокеты не предназначены для кросс-браузерной поддержки.
Raynos
2
есть хорошие пакеты для кроссбраузерной поддержки, особенно рекомендую socket.io, хотя есть также jWebSocket и многие другие. Если вы пойдете по пути socket.io, вы можете включить всевозможные полезности node.js, такие как фреймворки и (клиентские) механизмы шаблонов и т. Д.
Дэвин

Ответы:

157

Обзор:

  • вступление
  • Архитектура сервера
  • Клиентская архитектура
  • Обновить дело
  • Фиксировать случай
  • Случай конфликта
  • Производительность и масштабируемость

Привет, Райнос,

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

С архитектурной точки зрения, у вас, похоже, та же проблема, что и в программном обеспечении для контроля версий. Один пользователь регистрирует изменение объекта, другой пользователь хочет изменить тот же объект другим способом => конфликт. Вы должны интегрировать изменения пользователей в объекты, в то же время имея возможность своевременно и эффективно доставлять обновления, обнаруживая и разрешая конфликты, подобные описанному выше.

Если бы я был на вашем месте, я бы разработал что-то вроде этого:

1. На стороне сервера:

  • Определите разумный уровень, на котором вы бы определили то, что я бы назвал «атомарными артефактами» (страница? Объекты на странице? Значения внутри объектов?). Это будет зависеть от ваших веб-серверов, базы данных и оборудования для кэширования, количества пользователей, объектов и т. Д. Решение непростое.

  • Для каждого атомарного артефакта есть:

    • уникальный идентификатор для всего приложения
    • увеличивающийся идентификатор версии
    • механизм блокировки для доступа на запись (возможно, мьютекс)
    • небольшая история или «журнал изменений» внутри кольцевого буфера (для них хорошо подходит разделяемая память). Одна пара "ключ-значение" тоже может быть приемлемой, хотя и менее расширяемой. см. http://en.wikipedia.org/wiki/Circular_buffer
  • Серверный или псевдосерверный компонент, который может эффективно доставлять соответствующие журналы изменений подключенному пользователю. Observer-Pattern - ваш друг в этом.

2. На стороне клиента:

  • Клиент javascript, который может иметь длительное HTTP-соединение с указанным выше сервером или использует упрощенный опрос.

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

  • Компонент фиксации артефакта javascript, который может запрашивать изменение атомарного артефакта, пытаясь получить блокировку мьютекса. Он определит, было ли состояние артефакта изменено другим пользователем всего за несколько секунд до этого (латантность клиента javascript и факторы процесса фиксации), путем сравнения известного идентификатора версии артефакта на стороне клиента и идентификатора версии текущего артефакта на стороне сервера.

  • Средство разрешения конфликтов javascript, позволяющее человеку принимать правильное решение. Возможно, вы не захотите просто сказать пользователю: «Кто-то был быстрее вас. Я удалил ваше изменение. Иди плачь.». Многие варианты из довольно технических различий или более удобных решений кажутся возможными.

Так как бы он катился ...

Случай 1: вид диаграммы последовательности для обновления:

  • Браузер отображает страницу
  • javascript "видит" артефакты, каждый из которых имеет хотя бы одно поле значения, уникальное значение и идентификатор версии.
  • запускается клиент javascript, запрашивая "посмотреть" историю найденных артефактов, начиная с их найденных версий (более старые изменения не интересны)
  • Серверный процесс отмечает запрос и постоянно проверяет и / или отправляет историю
  • Записи истории могут содержать простые уведомления «артефакт x был изменен, данные запроса PLS клиента», позволяющие клиенту опрашивать независимо или полные наборы данных «артефакт x изменился на значение foo»
  • javascript artifact-updater делает все возможное для получения новых значений, как только становится известно, что они обновились. Он выполняет новые запросы ajax или получает поток от клиента javascript.
  • DOM-контент страниц обновляется, пользователь опционально уведомляется. Наблюдение за историей продолжается.

Случай 2: Теперь о фиксации:

  • Артефакт-коммиттер знает желаемое новое значение из пользовательского ввода и отправляет запрос на изменение на сервер
  • серверный мьютекс приобретен
  • Сервер получает «Эй, я знаю состояние артефакта x из версии 123, позвольте мне установить для него значение foo pls».
  • Если серверная версия артефакта x равна (не может быть меньше) 123, новое значение принимается, создается новый идентификатор версии 124.
  • Новая информация о состоянии «обновлена ​​до версии 124» и, возможно, новое значение foo помещаются в начало кольцевого буфера артефакта x (журнал изменений / история).
  • серверный мьютекс выпущен
  • коммиттер запрашивающего артефакта будет рад получить подтверждение фиксации вместе с новым идентификатором.
  • тем временем серверный компонент сервера продолжает опрашивать / отправлять кольцевые буферы подключенным клиентам. Все клиенты, наблюдающие за буфером артефакта x, получат новую информацию о состоянии и значение в пределах своей обычной задержки (см. Случай 1.)

Случай 3: для конфликтов:

  • Коммиттер артефакта знает желаемое новое значение из пользовательского ввода и отправляет запрос на изменение на сервер
  • тем временем другой пользователь успешно обновил тот же артефакт (см. случай 2.), но из-за различных задержек об этом еще не известно другому нашему пользователю.
  • Таким образом, серверный мьютекс приобретается (или ждет, пока «более быстрый» пользователь не подтвердит свое изменение)
  • Сервер получает сообщение «Эй, я знаю состояние артефакта x из версии 123, позвольте мне установить для него значение foo».
  • На Серверной стороне версия артефакта x сейчас уже 124. Запрашивающий клиент не может знать значение, которое он будет перезаписывать.
  • Очевидно, что Сервер должен отклонить запрос на изменение (не считая божественных приоритетов перезаписи), освобождает мьютекс и достаточно любезен, чтобы отправить обратно новый идентификатор версии и новое значение непосредственно клиенту.
  • столкнувшись с отклоненным запросом на фиксацию и значением, которое пользователь, запрашивающий изменение, еще не знал, коммиттер артефакта javascript ссылается на средство разрешения конфликтов, которое отображает и объясняет проблему пользователю.
  • Пользователю, которому передаются некоторые параметры со стороны интеллектуального средства разрешения конфликтов JS, разрешается еще одна попытка изменить значение.
  • Как только пользователь выбрал значение, которое он считает правильным, процесс начинается заново со случая 2 (или случая 3, если кто-то другой был быстрее, снова)

Несколько слов о производительности и масштабируемости

HTTP-опрос против HTTP "проталкивания"

  • Опрос создает запросы, один в секунду, 5 в секунду, независимо от того, что вы считаете приемлемой задержкой. Это может быть довольно жестоким по отношению к вашей инфраструктуре, если вы не настроите свои (Apache?) И (php?) Достаточно хорошо, чтобы быть «легковесными» стартерами. Желательно оптимизировать запрос на опрос на стороне сервера, чтобы он выполнялся в течение гораздо меньшего времени, чем продолжительность интервала опроса. Разделение этого времени выполнения пополам вполне может означать снижение нагрузки на всю систему до 50%,
  • Нажимать через HTTP (предполагающие webworkers слишком далеко , чтобы их поддержать) потребует от вас , чтобы иметь один апач / lighthttpd обработки для каждого пользователя все время . Резидентная память, зарезервированная для каждого из этих процессов, и общая память вашей системы будут одним очень определенным пределом масштабирования, с которым вы столкнетесь. Потребуется уменьшение объема памяти, используемого для подключения, а также ограничение объема непрерывной работы ЦП и ввода-вывода, выполняемой в каждом из них (вам нужно много времени сна / простоя)

внутреннее масштабирование

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

масштабирование внешнего интерфейса

  • Независимо от того, опрашиваете ли вы или получаете «толчки», постарайтесь получить информацию обо всех наблюдаемых артефактах за один шаг.

"творческие" настройки

  • Если клиенты проводят опрос и многие пользователи склонны наблюдать одни и те же артефакты, вы можете попытаться опубликовать историю этих артефактов в виде статического файла, позволяя apache кэшировать его, тем не менее, обновляя его на стороне сервера при изменении артефактов. Это убирает PHP / memcache из игры для запросов. Lighthttpd очень эффективен при обслуживании статических файлов.
  • используйте сеть доставки контента, такую ​​как cotendo.com, для отправки туда истории артефактов. Задержка push будет больше, но масштабируемость - мечта
  • напишите реальный сервер (не использующий HTTP), к которому пользователи подключаются с помощью java или flash (?). Вы должны иметь дело с обслуживанием многих пользователей в одном серверном потоке. Переключение между открытыми сокетами, выполнение (или делегирование) необходимой работы. Может масштабироваться за счет разветвления процессов или запуска дополнительных серверов. Однако мьютексы должны оставаться уникальными в глобальном масштабе.
  • В зависимости от сценариев нагрузки сгруппируйте интерфейсные и внутренние серверы по диапазонам идентификаторов артефактов. Это позволит лучше использовать постоянную память (ни одна база данных не содержит всех данных) и дает возможность масштабировать мьютекс. Однако ваш javascript должен поддерживать соединения с несколькими серверами одновременно.

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

Кристоф Стразен

Christoph Strasen
источник
1
@ChristophStrasen Посмотрите на серверы с четными данными, такие как node.js, которые не полагаются на один поток для каждого пользователя. Это позволяет обрабатывать технику проталкивания с меньшим потреблением памяти. Я думаю, что сервер node.js и использование TCP WebSockets действительно помогает в масштабировании. Однако это полностью разрушает кроссбраузерность.
Raynos
Я полностью согласен и надеюсь, что моя рецензия не побудит изобретать велосипед! Хотя некоторые колеса являются своего рода новыми, они только начинают становиться известными и недостаточно хорошо объяснены, чтобы архитекторы программного обеспечения среднего уровня могли судить о его применении для конкретной идеи. ПО МОЕМУ МНЕНИЮ. Node.js заслуживает книги "для чайников";). Я бы обязательно купил.
Christoph Strasen
2
+500 Вы вызывающе один этот. Отличный ответ.
Raynos
1
@luqmaan, это ответ от февраля 2011 года. Веб-сокеты все еще были новинкой и были выпущены без префикса в Chrome примерно в августе. Тем не менее, Comet и socket.io были в порядке, я думаю, что это просто предложение более производительного подхода.
Ricardo Tomasi
1
И если Node.js находится слишком далеко от вашей зоны комфорта (или зоны комфорта команды эксплуатации, но уверены в бизнес-контексте вопроса), вы также можете использовать Socket.io с сервером на основе Java. И Tomcat, и Jetty поддерживают беспоточные соединения для настроек типа server-push (см., Например: wiki.eclipse.org/Jetty/Feature/Continuations )
Томас
13

Я знаю, что это старый вопрос, но я подумал, что просто вмешаюсь.

OT (операционные преобразования) кажутся подходящими для ваших требований одновременного и последовательного многопользовательского редактирования. Это метод, используемый в Google Docs (а также в Google Wave):

Существует библиотека на основе JS для использования операционных преобразований - ShareJS ( http://sharejs.org/ ), написанная членом команды Google Wave.

И, если хотите, есть полноценная веб-инфраструктура MVC - DerbyJS ( http://derbyjs.com/ ), построенная на ShareJS, которая все сделает за вас.

Он использует BrowserChannel для связи между сервером и клиентами (и я считаю, что поддержка WebSockets должна быть в разработке - она ​​была там раньше через Socket.IO, но была удалена из-за проблем разработчика с Socket.io) Документы для начинающих - это однако на данный момент немного скудно.

Викторхуи
источник
5

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

Крис Бейкер
источник
Это полезный момент. Это также помогает мне лучше понять поля «LastEdited» в нашей базе данных с точки зрения разработки.
Raynos
Именно. Этот сайт использует «сердцебиение», то есть каждые x периодов времени он отправляет запрос AJAX на сервер и передает идентификатор данных для проверки, а также измененную временную метку для этих данных. Итак, допустим, мы ответили на вопрос № 1029. При каждом запросе AJAX сервер просматривает только измененную метку времени для вопроса № 1029. Если он когда-либо обнаруживает, что у клиента есть более старая версия данных, он отвечает на тактовый сигнал новой копией. Затем клиент может либо перезагрузить страницу (обновить), либо отобразить пользователю какое-то сообщение, предупреждающее его о новых данных.
Крис Бейкер
модифицированные штампы намного лучше, чем хеширование наших текущих «данных» и сравнение их с хешем на другой стороне.
Raynos
1
Имейте в виду, что клиент и сервер должны иметь доступ к одному и тому же времени, чтобы избежать несоответствий.
молитвенный убийца 08
3

Вам необходимо использовать методы push (также известные как Comet или обратный Ajax), чтобы распространять изменения на пользователя, как только они вносятся в базу данных. Наилучшим доступным в настоящее время методом для этого является длинный опрос Ajax, но он поддерживается не всеми браузерами, поэтому вам нужны резервные варианты. К счастью, уже есть решения, которые справятся с этим за вас. Среди них: orbited.org и уже упомянутый socket.io.

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

В базе данных не должно быть проблем с параллелизмом с новыми объектами. Но когда пользователь редактирует объект, на сервере должна быть логика, которая проверяет, был ли объект отредактирован или удален тем временем. Если объект был удален, решение опять же простое: просто отмените правку.

Но самая сложная проблема возникает, когда несколько пользователей редактируют один и тот же объект одновременно. Если Пользователь 1 и 2 начнут редактировать объект одновременно, они оба будут редактировать одни и те же данные. Допустим, изменения, внесенные пользователем 1, сначала отправляются на сервер, в то время как пользователь 2 все еще редактирует данные. Затем у вас есть два варианта: вы можете попытаться объединить изменения пользователя 1 с данными пользователя 2 или вы можете сообщить пользователю 2, что его данные устарели, и вывести ему сообщение об ошибке, как только его данные будут отправлены на сервер. Последний вариант здесь не очень удобный, но первый очень сложно реализовать.

Одной из немногих реализаций, которые впервые действительно получили это право, был EtherPad , который был приобретен Google. Я полагаю, что они тогда использовали некоторые технологии EtherPad в Google Docs и Google Wave, но я не могу сказать это наверняка. Google также открывает источник EtherPad, так что, возможно, стоит взглянуть на него, в зависимости от того, что вы пытаетесь сделать.

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

Jannes
источник
2

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

Я обнаружил, что фреймворк Meteor делает это хорошо ( http://docs.meteor.com/#reactivity ).

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

«Этот простой шаблон (реактивные вычисления + реактивный источник данных) имеет широкое применение. Программисту не нужно писать вызовы отписки / повторной подписки и следить за тем, чтобы они вызывались в нужное время, устраняя целые классы кода распространения данных, которые в противном случае засоряли бы ваш приложение с логикой, подверженной ошибкам ".

мб.
источник
1

Не могу поверить, что никто не упомянул Метеор . Это новый и незрелый фреймворк (и официально поддерживает только одну БД), но он берет на себя всю тяжелую работу и продумывание многопользовательского приложения, как описано на плакате. Фактически, вы НЕ можете создать многопользовательское приложение для обновления в реальном времени. Вот краткое изложение:

  • Все находится в node.js (JavaScript или CoffeeScript), поэтому вы можете обмениваться такими вещами, как проверки, между клиентом и сервером.
  • Он использует веб-сокеты, но может использовать более старые браузеры.
  • Он фокусируется на немедленных обновлениях локального объекта (т.е. пользовательский интерфейс кажется мгновенным), а изменения отправляются на сервер в фоновом режиме. Для упрощения смешивания обновлений разрешены только атомарные обновления. Обновления, отклоненные на сервере, откатываются.
  • В качестве бонуса он выполняет перезагрузку живого кода за вас и сохраняет пользовательское состояние даже при радикальных изменениях приложения.

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

СмелыйНовыйВалюта
источник
1
Мне очень нравится идея Derby и Meteor для определенных типов приложений. Право собственности на документы / записи и разрешения - это всего лишь пара реальных проблем, которые, по моему мнению, не решены должным образом. Кроме того, из-за давнего мира MS, который делает эти 80% действительно легкими и тратит слишком много времени на остальные 20%, я не решаюсь использовать такие решения PFM (чистая гребаная магия).
Tracker1 08
1

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

Существует множество форм параллельного программного обеспечения для совместной работы в Интернете .

Существует ряд клиентских библиотек HTTP API для etherpad-lite , редактора для совместной работы в реальном времени .

django-realtime-playside реализует приложение чата в реальном времени в Django с различными технологиями реального времени, такими как Socket.io .

И AppEngine, и AppScale реализуют API канала AppEngine ; который отличается от Google Realtime API , который демонстрируется googledrive / realtime-Playground .

Уэс Тернер
источник
0

Здесь можно использовать методы push- уведомлений на стороне сервера . Комета - это модное слово (или было?).

Конкретное направление, которое вы выберете, сильно зависит от вашего серверного стека и от того, насколько вы / он гибкий. Если вы можете, я бы взглянул на socket.io , который обеспечивает кроссбраузерную реализацию веб-сокетов, которые обеспечивают очень простой способ двунаправленной связи с сервером, позволяя серверу отправлять обновления клиентам.

В частности, посмотрите эту демонстрацию автора библиотеки, которая почти точно демонстрирует описанную вами ситуацию.

Дэвин
источник
Это отличная библиотека для уменьшения проблем с комминированием, но я больше искал высокоуровневую информацию о том, как проектировать приложение
Raynos
1
Просто отметим, что socket.io (и SignalR) - это фреймворки, которые используют веб-сокеты в качестве первоклассного выбора, но имеют совместимые резервные варианты для использования других методов, таких как комета, длинный опрос, флеш-сокеты и вечные фреймы.
Tracker1