Як розробити багатокористувацьку веб-програму ajax, щоб вона була одночасно безпечною


95

У мене є веб-сторінка, яка показує велику кількість даних із сервера. Зв'язок здійснюється через ajax.

Кожного разу, коли користувач взаємодіє та змінює ці дані (Скажімо, користувач А перейменовує щось), він повідомляє серверу виконати дію, а сервер повертає нові змінені дані.

Якщо користувач B одночасно звертається до сторінки та створює новий об'єкт даних, він знову скаже серверу через ajax, і сервер повернеться з новим об'єктом для користувача.

На сторінці A ми маємо дані з перейменованим об’єктом. А на сторінці B ми маємо дані з новим об’єктом. На сервері дані мають як перейменований об’єкт, так і новий об’єкт.

Які мої варіанти підтримують синхронізацію сторінки із сервером, коли кілька користувачів одночасно використовують її?

Такі варіанти, як блокування всієї сторінки або передача користувачеві цілого стану при кожній зміні, навряд чи уникаються.

Якщо це допомагає, у цьому конкретному прикладі веб-сторінка викликає статичний веб-метод, який запускає збережену процедуру в базі даних. Збережена процедура поверне будь-які дані, які вона змінила, і не більше. Потім статичний веб-метод пересилає повернення збереженої процедури клієнту.

Редагування Bounty:

Як ви розробляєте багатокористувацьку веб-програму, яка використовує Ajax для зв’язку з сервером, але уникає проблем із паралельністю?

Тобто одночасний доступ до функціональних можливостей та даних у базі даних без будь-якого ризику даних чи корупції


не настільки впевнений, але у вас може бути така сторінка, як facebook, де браузер надсилає запит ajax, постійно шукаючи зміни в базі даних сервера та оновлюючи їх у браузері
Santosh Linkha

Серіалізація стану клієнта, а потім повідомлення сервера через ajax ось мій стан, що мені потрібно оновити - це варіант. Але вимагає, щоб клієнт знав, як оновити будь-яку інформацію в одному місці.
Raynos

1
Чи найкраще рішення для одночасності з кінцем користувача - не просто один із варіантів натискання? Веб-розетки, комети тощо
Девін

@davin це цілком може бути. Але я не знайомий з кометою, а веб-сокети не підтримуються між браузерами.
Raynos

2
є хороші пакети для підтримки крос-браузера, зокрема я рекомендую socket.io, хоча існує також jWebSocket та багато інших. Якщо ви йдете шляхом socket.io, ви можете включити всілякі смаколики node.js, такі як фреймворки та механізми
шаблонування

Відповіді:


157

Огляд:

  • Вступ
  • Архітектура сервера
  • Клієнтська архітектура
  • Справа оновлення
  • Справа коміту
  • Випадок конфлікту
  • Продуктивність та масштабованість

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

Я не буду тут обговорювати якийсь конкретний продукт. Те, що згадали інші, є хорошим набором інструментів, на який можна вже поглянути (можливо, додати node.js до цього списку).

З архітектурної точки зору у вас, схоже, є та сама проблема, яку можна побачити в програмному забезпеченні контролю версій. Один користувач перевіряє зміну об’єкта, інший користувач хоче змінити той самий об’єкт іншим способом => конфлікт. Вам потрібно інтегрувати зміни користувачів до об’єктів, одночасно маючи змогу доставляти оновлення вчасно та ефективно, виявляючи та вирішуючи конфлікти, подібні наведеному вище.

Якби я був на вашому місці, я б розвинув щось подібне:

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

  • Визначте розумний рівень, на якому ви могли б визначити те, що я б назвав "атомними артефактами" (сторінка? Об'єкти на сторінці? Значення всередині об'єктів?). Це буде залежати від ваших веб-серверів, апаратного забезпечення баз даних та кешування, # користувача, # об'єктів тощо. Непросте рішення.

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

    • унікальний ідентифікатор для всього додатка
    • ідентифікатор версії, що збільшується
    • механізм блокування для доступу до запису (можливо, mutex)
    • невелика історія або "журнал змін" всередині буфера (спільна пам'ять добре працює для тих). Одна пара ключ-значення може бути нормальною, хоча і менш розширювана. див. http://en.wikipedia.org/wiki/Circular_buffer
  • Серверний або псевдосерверний компонент, який здатний ефективно доставляти відповідні журнали змін підключеному користувачеві. Для цього ваш друг - спостерігач.

2. Сторона клієнта:

  • Клієнт javascript, який може мати тривале HTTP-з'єднання із зазначеним вище сервером або використовує полегшене опитування.

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

  • Компонент артефакту-комітера javascript, який може вимагати зміни атомного артефакту, намагаючись отримати блокування mutex. Він виявить, чи змінив стан артефакту інший користувач за кілька секунд до цього (затримка клієнта javascript та фактори процесу введення), порівнюючи відомі ідентифікатори сторони клієнта-артефакт-ідентифікатора та поточний ідентифікатор артефакту-версії сервера.

  • Розв'язувач конфліктів у JavaScript, що дозволяє прийняти рішення про зміну людини, яке є правильним. Можливо, ви не захочете просто сказати користувачеві "Хтось був швидшим за вас. Я видалив ваші зміни. Поплачте". Багато варіантів з досить технічних відмінностей або більш зручних для користувача рішень здаються можливими.

То як би він котився ...

Випадок 1: діаграма виду послідовності для оновлення:

  • Сторінка візуалізації браузера
  • javascript "бачить" артефакти, кожен з яких має принаймні одне поле значення, унікальний і ідентифікатор версії
  • клієнт javascript починає роботу, вимагаючи "перегляду" знайденої історії артефактів, починаючи з їх знайдених версій (старі зміни не цікаві)
  • Процес сервера відзначає запит і постійно перевіряє та / або надсилає історію
  • Записи історії можуть містити прості сповіщення "артефакт x змінився, клієнт pls запитує дані", що дозволяє клієнтові самостійно опитувати або повні набори даних "артефакт x змінився на значення foo"
  • javascript артефакт-оновлення робить все можливе для отримання нових значень, як тільки вони стають відомими для оновлення. Він виконує нові запити ajax або подається клієнтом javascript.
  • Сторінки DOM-вмісту оновлюються, користувач отримує додаткове повідомлення. Перегляд історії триває.

Випадок 2: Тепер для вчинення:

  • artifact-committer знає бажане нове значення від вводу користувача і надсилає запит на зміну серверу
  • набуто серверний мьютекс
  • Сервер отримує "Гей, я знаю стан артефакту х з версії 123, дозвольте мені встановити значення foo pls."
  • Якщо серверна версія артефакту x дорівнює (не може бути менше), ніж 123, приймається нове значення, генерується новий ідентифікатор версії 124.
  • Нова інформація про стан "оновлена ​​до версії 124" і, за бажанням, нове значення foo розміщуються на початку буфера обміну артефакту x (журнал змін / історія)
  • випущено серверний мьютекс
  • запитуючий артефакт комітер із задоволенням отримує підтвердження коміту разом із новим ідентифікатором.
  • тим часом серверний серверний компонент продовжує опитувати / передавати буфери дзвінка підключеним клієнтам. Усі клієнти, які спостерігають за буфером артефакту х, отримуватимуть нову інформацію про стан і значення в межах своєї звичайної затримки (див. Випадок 1.)

Випадок 3: для конфліктів:

  • Артефакт-фіксатор знає бажане нове значення з вводу користувача та надсилає запит на зміну серверу
  • тим часом інший користувач успішно оновив той самий артефакт (див. випадок 2.), але через різні затримки це ще невідомо для нашого іншого користувача.
  • Отже, серверний мьютекс отримується (або чекає, поки "швидший" користувач не здійснить свою зміну)
  • Сервер отримує "Гей, я знаю стан артефакту х з версії 123, дозвольте мені встановити значення foo."
  • На сервері версія артефакту x зараз уже 124. Клієнт, що подає запит, не може знати, яке значення він би перезаписав.
  • Очевидно, що Сервер повинен відхилити запит на зміну (не враховуючи пріоритети перезапису, що втручаються в Бог), звільняє мьютекс і достатньо люб'язно надсилає новий ідентифікатор версії та нове значення безпосередньо клієнту.
  • зіткнувшись із відхиленим запитом на фіксацію та значенням, яке користувач, що вимагає змін, ще не знав, комітет артефакту javascript посилається на вирішувач конфліктів, який відображає та пояснює користувачеві проблему.
  • Користувачеві, який представляє деякі опції за допомогою інтелектуального вирішувача конфліктів JS, дозволяється ще одна спроба змінити значення.
  • Як тільки користувач вибрав значення, яке він вважає правильним, процес починається спочатку із випадку 2 (або випадку 3, якщо хтось інший був швидшим, знову)

Кілька слів щодо продуктивності та масштабованості

Опитування HTTP проти HTTP "натискання"

  • Опитування створює запити, один на секунду, 5 на секунду, що б ви не вважали прийнятною затримкою. Це може бути досить жорстоким щодо вашої інфраструктури, якщо ви не налаштуєте свої (Apache?) Та (php?) Досить добре, щоб бути "полегшеними" початківцями. Бажано оптимізувати запит опитування на стороні сервера, щоб він працював набагато менше часу, ніж довжина інтервалу опитування. Розподіл цього часу роботи навпіл може означати зниження навантаження всієї вашої системи до 50%,
  • Натискати через HTTP (які передбачають webworkers занадто далеко , щоб їх підтримати) зажадає від вас , щоб мати один апач / lighthttpd обробки для кожного користувача весь час . Резидентна пам’ять, зарезервована для кожного з цих процесів, і загальна пам’ять ваших систем буде одним дуже певним обмеженням масштабування, з яким ви зіткнетеся. Потрібно зменшити обсяг пам'яті підключення, а також обмежити обсяг безперервної роботи процесора та вводу-виводу, виконаної в кожному з них (ви хочете багато часу сну / простою)

серверне масштабування

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

масштабування інтерфейсу

  • незалежно від того, чи проводите ви опитування чи отримуєте "натискання", спробуйте отримати інформацію про всі спостережувані артефакти одним кроком.

"креативні" налаштування

  • Якщо клієнти проводять опитування, і багато користувачів схильні спостерігати за тими ж артефактами, ви можете спробувати опублікувати історію цих артефактів як статичний файл, дозволяючи apache кешувати його, тим не менше, оновлюючи його на стороні сервера, коли артефакти змінюються. Це виводить PHP / memcache з гри деякі для запитів. Lighthttpd дуже ефективно обслуговує статичні файли.
  • використовуйте мережу доставки вмісту, як cotendo.com, щоб перенести туди історію артефактів. Затримка натискання буде більшою, але масштабованість - це мрія
  • написати справжній сервер (не використовуючи HTTP), до якого користувачі підключаються за допомогою Java або flash (?). Вам доведеться мати справу з обслуговуванням багатьох користувачів в одному потоці сервера. Їзда на велосипеді через відкриті розетки, виконання (або делегування) необхідної роботи. Може масштабуватися за допомогою процесів розгалуження або запуску більшої кількості серверів. Мьютекси повинні залишатися глобально унікальними.
  • Залежно від сценаріїв завантаження групуйте свої інтерфейсні та серверні сервери за діапазонами ідентифікаторів артефактів. Це дозволить краще використовувати постійну пам’ять (жодна база даних не має всіх даних) і дає змогу масштабувати змішування. Ваш javascript повинен підтримувати з'єднання з декількома серверами одночасно.

Ну, я сподіваюся, це може бути початком для ваших власних ідей. Я впевнений, що можливостей набагато більше. Я вітаю будь-яку критику чи вдосконалення цієї публікації, wiki увімкнено.

Крістоф Штрасен


1
@ChristophStrasen Подивіться на парні сервери, такі як node.js, які не покладаються на один потік на кожного користувача. Це дозволяє обробляти техніку натискання з меншим споживанням пам'яті. Я думаю, що сервер node.js і покладання на TCP WebSockets дійсно допомагають у масштабуванні. Це повністю руйнує перехресну відповідність браузеру.
Raynos

Я цілком погоджуюсь і сподіваюся, що моє написання не заохочує винаходити колесо! Хоча деякі колеса є начебто новими, лише починають ставати відомими і недостатньо добре пояснені, щоб архітектори програмного забезпечення середнього рівня могли судити про його застосування щодо конкретної ідеї. ІМХО. Node.js якось заслуговує на книгу "для манекенів";). Я б точно купив.
Крістоф Штрасен

2
+500 Ви зухвало виставили цей. Це чудова відповідь.
Raynos

1
@luqmaan ця відповідь від лютого 2011 року. Веб-розетки все ще були новинкою і вийшли лише без префіксу в Chrome близько серпня. Comet і socket.io були добре, хоча, я думаю, це була просто пропозиція щодо більш ефективного підходу.
Рікардо Томасі,

1
І якщо Node.js занадто далеко від вашої зони комфорту (або зони комфорту команди операцій, але впевнений у бізнес-контексті питання), ви також можете використовувати Socket.io з сервером на основі Java. І Tomcat, і Jetty підтримують безпоточні з'єднання для налаштування типу push-сервера (див. Наприклад: wiki.eclipse.org/Jetty/Feature/Continuations )
Томас

13

Я знаю, що це давнє запитання, але я думав, що я просто блукаю.

OT (операційні перетворення), здається, добре підходить для ваших вимог щодо одночасного та послідовного багатокористувацького редагування. Це техніка, яка використовується в Документах Google (а також використовувалася в 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

Точно так. Цей сайт використовує "серцебиття", тобто кожні х разів він надсилає на сервер запит AJAX, і він передає ідентифікатор даних для перевірки, а також модифіковану мітку часу, яку він має для цих даних. Тож, скажімо, ми опитуємось на запитання №1029. Кожен запит AJAX сервер переглядає лише модифіковану мітку часу для запитання №1029. Якщо він коли-небудь виявить, що у клієнта є старіша версія даних, він відповідає на звуковий сигнал із новою копією. Потім клієнт може або перезавантажити сторінку (оновити), або відобразити користувачеві якесь повідомлення з попередженням про нові дані.
Chris Baker

модифіковані штампи набагато приємніші, ніж хешування наших поточних "даних" і порівняння їх із хешем на іншій стороні.
Raynos

1
Майте на увазі, що клієнт і сервер повинні мати доступ точно в один і той же час, щоб уникнути невідповідностей.
молитва вбивця

3

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

У майбутньому буде простіший спосіб зробити це, який називається WebSockets, але поки невідомо, коли цей стандарт буде готовий до прайм-тайму, оскільки існують проблеми з безпекою щодо поточного стану стандарту.

У базі даних із новими об’єктами не повинно виникати проблем з одночасністю. Але коли користувач редагує об’єкт, сервер повинен мати певну логіку, яка перевіряє, чи об’єкт тим часом редагувався чи видалявся. Якщо об’єкт було видалено, рішення, знову ж таки, просто: просто відкиньте редагування.

Але найскладніша проблема з’являється, коли кілька користувачів одночасно редагують один і той же об’єкт. Якщо користувач 1 і 2 почнуть редагувати об’єкт одночасно, вони обидва внесуть свої зміни в однакові дані. Скажімо, зміни, внесені користувачем 1, спочатку надсилаються на сервер, поки користувач 2 все ще редагує дані. Потім у вас є два варіанти: Ви можете спробувати об’єднати зміни Користувача 1 з даними Користувача 2 або Ви можете сказати Користувачеві 2, що його дані застаріли, і відобразити йому повідомлення про помилку, як тільки його дані надійдуть на сервер. Останній тут не дуже зручний для користувача варіант, але перший дуже важко реалізувати.

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

Насправді нелегко зробити це одночасно редагуючи матеріали, оскільки неможливо робити атомні операції в Інтернеті через затримку. Можливо, ця стаття допоможе вам дізнатися більше про тему.


2

Спроба написати все це самостійно - велика робота, і дуже складно це правильно зрозуміти. Один із варіантів - використовувати фреймворк, побудований для синхронізації клієнтів з базою даних та між собою в режимі реального часу.

Я виявив, що фреймворк Meteor робить це добре ( http://docs.meteor.com/#reactivity ).

"Meteor охоплює концепцію реактивного програмування. Це означає, що ви можете писати свій код у простому імперативному стилі, і результат буде автоматично перерахований щоразу, коли змінюються дані, від яких залежить ваш код".

"Цей простий шаблон (реактивне обчислення + реактивне джерело даних) має широку застосовність. Програміст позбавлений від написання дзвінків для відписки / повторної підписки та переконання, що вони викликані в потрібний час, усуваючи цілі класи коду розповсюдження даних, які в іншому випадку забивали б ваш додаток із логікою, схильною до помилок. "


1

Я не можу повірити, що ніхто не згадував Метеор . Це точно новий і незрілий фреймворк (і офіційно підтримує лише одну БД), але він вимагає всієї бурчання та роздумів із багатокористувацької програми, як це описує плакат. Насправді ви не можете НЕ створити багатокористувацьку програму для оновлення в режимі реального часу. Ось короткий підсумок:

  • Все знаходиться в node.js (JavaScript або CoffeeScript), тому ви можете ділитися такими речами, як перевірки, між клієнтом та сервером.
  • Він використовує веб-сокети, але може повернутися для старих браузерів
  • Він фокусується на негайних оновленнях локального об’єкта (тобто користувальницький інтерфейс відчуває себе швидко), а зміни надсилаються на сервер у фоновому режимі. Тільки атомні оновлення дозволяють спростити змішування оновлень. Оновлення, відхилені на сервері, відкочуються.
  • Як бонус, він обробляє для вас перезавантаження коду в реальному часі і зберігає стан користувача, навіть коли програма радикально змінюється.

Метеор досить простий, і я б запропонував вам хоча б поглянути на нього, щоб знайти ідеї для крадіжки.


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

1

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

Існує безліч форм одночасної роботи в Інтернеті програмного забезпечення для спільної роботи в Інтернеті .

Існує декілька клієнтських бібліотек HTTP API для etherpad-lite , спільного редактора реального часу .

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

Як AppEngine, так і AppScale реалізують API AppEngine Channel ; що відрізняється від API Google Realtime , що демонструється googlegleve / realtime-playground .


0

Тут слід пройти прийоми натискання на стороні сервера . Комета - це (чи було?) Модне слово.

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

Зокрема, див. Цю демонстрацію автора бібліотеки, яка демонструє майже точно ситуацію, яку ви описуєте.


Це чудова бібліотека для зменшення проблем з комінізацією, але я більше шукав інформації на високому рівні про те, як розробляти додаток
Райнос,

1
Зазначимо, що socket.io (і SignalR) - це фреймворки, які використовують веб-сокети як першокласний вибір, але мають сумісні резервні копії для використання інших методів, таких як комета, тривале опитування, флеш-сокети та назавжди.
Tracker1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.