Як запобігти умовам перегонів у веб-додатку?


31

Розглянемо веб-сайт електронної комерції, де Аліса та Боб редагують списки товарів. Аліса покращує описи, а Боб оновлює ціни. Вони починають редагувати віджет Acme Wonder одночасно. Боб закінчує перше місце і економить продукт новою ціною. Алісі потрібно трохи більше часу, щоб оновити опис, і коли вона закінчить, вона зберігає продукт своїм новим описом. На жаль, вона також переписує ціну зі старою ціною, яка не була призначена.

На мій досвід, ці питання надзвичайно поширені у веб-додатках. Деяке програмне забезпечення (наприклад, програмне забезпечення wiki) має захист від цього - зазвичай другий збереження не вдається "сторінка була оновлена ​​під час редагування". Але більшість веб-сайтів не мають такого захисту.

Варто зазначити, що методи контролера самі по собі є потоковими. Зазвичай вони використовують транзакції бази даних, що робить їх безпечними в тому сенсі, що якщо Аліса і Боб спробують зберегти в той самий момент, це не спричинить корупції. Умова перегонів виникає у зв'язку з тим, що Аліса або Боб мають несвіжі дані у своєму браузері.

Як ми можемо запобігти таким умовам гонки? Зокрема, я хотів би знати:

  • Які прийоми можна використовувати? наприклад, відстеження часу останньої зміни. Які плюси і мінуси кожного.
  • Що корисне для користувачів?
  • У які рамки вбудований цей захист?

Ви вже дали відповідь: відстежуючи дату зміни об’єктів і порівнюючи її з віком даних, які інші зміни намагаються оновити. Ви хочете знати щось інше, наприклад, як це зробити ефективно?
Кіліан Фот

@KilianFoth - Я додав інформацію про те, що я особливо хотів би знати
paj28

1
Ваше запитання жодним чином не стосується веб-додатків, настільні програми можуть мати точно таку ж проблему. Тут описані типові стратегії рішення: stackoverflow.com/questions/129329/…
Doc Brown

2
FYI, форма блокування, яку ви згадуєте у своєму питанні, відома як " оптимістичний контроль
сумісності

Деякі дискусії, пов'язані з Джанго тут
paj28

Відповіді:


17

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


Це звучить як найбільш практичний підхід. Чи знаєте ви якісь рамки, які реалізують це? Я думаю, що найбільша проблема цієї схеми полягає в тому, що просте повідомлення "редагувати конфлікт" розчарує користувачів, але спроба об'єднати набори змін (вручну або автоматично) є складною.
paj28

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

Я підтримував продукт із розподіленою базою даних для ПК, який використовував тонкозернистий підхід (проти його локальної копії бази даних): якщо один користувач змінив ціну, а інший змінив опис - немає проблем! Як і в реальному житті. Якщо двоє користувачів змінили ціну - другий користувач отримує вибачення і намагається змінити їх ще раз. Без проблем! Для цього не потрібні блокування, за винятком моменту, коли дані записуються в базу даних. Не має значення, чи йде один користувач на обід, поки його зміна з’являється на екрані, і подає його пізніше. Для віддалених змін бази даних вона базувалася на часових позначках запису.

1
Dataflex мав функцію під назвою "перечитати ()", яка робить те, що ви описуєте. У пізніших версіях це було безпечно у багатокористувацькому середовищі. І дійсно, це був єдиний спосіб отримати такі переплетені оновлення для роботи.

ви можете навести приклад того, як це зробити з сервером sql? \
l --''''''--------- '' '' '' '' '' '23

10

Я бачив два основні способи:

  1. Додайте часову позначку останнього оновлення сторінки, яку редагує використання, у прихованому введенні. Під час здійснення позначки часу перевіряється на поточну, і якщо вони не відповідають, її оновив хтось інший і поверне помилку.

    • pro: кілька користувачів можуть редагувати різні частини сторінки. Сторінка помилок може призвести до різної сторінки, де другий користувач може об'єднати свої зміни в новій сторінці.

    • con: колись великі зусилля витрачаються витрачені під час великих одночасних редагувань.

  2. Коли користувач починає редагувати сторінку, заблокуйте її протягом розумного часу, коли інший користувач потім намагається редагувати, він отримує сторінку помилки і повинен чекати, поки блокування закінчиться, або перший користувач здійснить.

    • Pro: Намагання редагування не витрачаються даремно.

    • con: недобросовісний користувач може заблокувати сторінку нескінченно. Сторінка з пропущеним блокуванням може закінчитись, якщо не буде вирішено інше (використовуючи техніку 1)


7

Використовуйте оптимістичний контроль за сумісністю .

Додайте стовпчик versionNumber або versionTimestamp у відповідну таблицю (ціле число є найбезпечнішим).

Користувач 1 читає запис:

{id:1, status:unimportant, version:5}

Користувач 2 читає запис:

{id:1, status:unimportant, version:5}

Користувач 1 зберігає запис, збільшуючи версію:

save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}

Користувач 2 намагається зберегти записаний ними запис:

save {id:1, status:unimportant, version:5}
ERROR

Hibernate / JPA може це зробити автоматично з @Versionприміткою

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


Дякую ... особливо корисно знати про @Version. Одне запитання: чому сейфи зберігати стан на сесії? У такому випадку я б хвилювався, що використання кнопки "назад" може сплутати речі.
paj28

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

Ця техніка називається Оптимістичний відсутня замок і знаходиться в SQLAlchemy , а також
paj28

@ paj28 - це посилання для SQLAlchemyнічого не вказує на оптимістичні офлайн-блокування, і я не можу його знайти в документах. Чи було у вас корисніше посилання або взагалі просто вказували людей на SQLAlchemy?
dwanderson

@dwanderson Я мав на увазі частину лічильника версій цього посилання.
paj28

1

Деякі системи реляційного відображення об'єктів (ORM) визначатимуть, які поля об’єкта змінилися після завантаження з бази даних, і побудують оператор оновлення SQL, щоб встановити лише ці значення. ActiveRecord для Ruby on Rails - одна з таких ОРМ.

Чистий вплив полягає в тому, що поля, які користувач не змінив, не включаються до команди UPDATE, що надсилається до бази даних. Люди, які одночасно оновлюють різні поля, не перезаписують зміни.

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


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

1
@ paj28 Ключовим моментом у відповіді Грега є " поля, які користувач не змінив ". Аліса не змінила ціну, тому ORM не намагатиметься зберегти значення "ціни" в базі даних.
Росс Паттерсон

@RossPatterson - як ORM знає різницю полів, які користувач змінив, та застарілих даних у веб-переглядачі? Не обійдеться, принаймні, без додаткового відстеження. Якщо ви хочете відредагувати відповідь Грега, щоб включити таке відстеження, або надіслати іншу відповідь, це було б корисно.
paj28

@ paj28 деяка частина системи повинна знати, що робив користувач, і зберігати лише ті зміни, які вніс користувач. Якщо користувач змінив ціну, потім змінив її назад, потім подав, це не повинно вважатися "чимось, що змінив користувач", оскільки цього не зробили. Якщо у вас є система, яка вимагає такого рівня контролю за одночасністю, ви повинні побудувати її таким чином. Якщо ні, ні.

@nocomprende - Якась частина, звичайно, але не ORM, як сказано у цій відповіді
paj28
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.