Що робити, коли оптимістичне блокування не працює?


11

У мене такий сценарій:

  1. Користувач робить GET запит до /projects/1та отримує ETag .
  2. Користувач робить запит PUT до /projects/1ETag з кроку №1.
  3. Користувач робить ще один запит PUT до /projects/1ETag з кроку №1.

Зазвичай другий запит PUT отримав би відповідь 412, оскільки ETag тепер застарілий - перший запит PUT змінив ресурс, тому ETag більше не відповідає.

Але що робити, якщо два запити PUT надсилаються одночасно (або рівно один за одним)? Перший запит PUT не встигає обробити та оновити ресурс до того, як прийде PUT №2, через що PUT №2 замінить PUT №1. Вся суть оптимістичного блокування полягає в тому, щоб цього не сталося ...


3
Створіть свої операції в операціях на рівні бізнесу, як пояснює Есбен нижче.
Роберт Харві

Що буде, якщо я розпорошив свої операції за допомогою транзакцій? PUT №2 не буде оброблено, поки PUT №1 не буде повністю оброблений?
maximedupre

7
Стати песимістом?
jpmc26

добре, це те, що блокування.
Fattie

Правильно, звичайно, Put №2 не слід обробляти - вони повинні бути унікальними.
Fattie

Відповіді:


21

Механізм ETag задає лише протокол зв'язку для оптимістичного блокування. Служба прикладних програм відповідає за виконання механізму виявлення одночасних оновлень для забезпечення оптимістичного блокування.

У типовому додатку, який використовує базу даних, зазвичай це робиться, відкриваючи транзакцію під час обробки запиту PUT. Ви, як правило, читаєте існуючий стан бази даних всередині цієї транзакції (щоб отримати блокування читання), перевіряєте свою дійсність Etag та перезаписуєте дані (таким чином, що це призведе до конфлікту запису при будь-якій несумісній сумісній транзакції), потім здійснити. Якщо ви правильно налаштували транзакцію, один із комітетів повинен вийти з ладу, оскільки вони обидва намагаються одночасно оновлювати ті самі дані. Тоді ви зможете використовувати цю помилку транзакції або для повернення 412, або повторного запиту, якщо це має сенс для програми.


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

1
@maximedupre: якщо ви використовуєте транзакцію, у вас є якісь блокування, хоча це можуть бути неявні блокування (блокування отримуються автоматично під час читання / оновлення полів, а не явно вимагається). Механізм, який я описав вище, можна реалізувати, використовуючи лише ті неявні блокування. Як і ваше інше запитання, це залежить від бази даних, яку ви використовуєте, але багато сучасних баз даних використовують MVCC (управління кількома версіями одночасності), щоб дозволити декільком читачам і авторам працювати на одних і тих же полях без зайвого блокування один одного.
Lie Lie Ryan

1
Попередження: у великій DBMSes (PostgreSQL, Oracle, SQL Server і т.д.), рівень ізоляції транзакції по замовчуванням «читати досконалий», де ваш підхід НЕ досить , щоб запобігти стан гонки OP ще. У таких DMBSes ви можете виправити це, включивши AND ETag = ...в додаток UPDATEзаяву WHEREта перевіряючи кількість оновлених рядків після цього. (Або за допомогою більш жорсткого рівня ізоляції транзакцій, але я не дуже рекомендую цього.)
ruakh

1
@ruakh: це залежить від того, як ви пишете свій запит, так, рівень ізоляції за замовчуванням не забезпечує таку поведінку автоматично для всіх запитів, але часто можна структурувати транзакцію таким чином, який буде достатнім для здійснення оптимістичного блокування. У більшості випадків, якщо в програмі важлива послідовність транзакцій, я б рекомендував повторно читати як рівень ізоляції за замовчуванням; у базах даних, що використовують MVCC, накладні витрати на повторне читання досить мінімальні, що значно спрощує додаток.
Лі Лі Райан

1
@ruakh: головний недолік повторюваного читання полягає в тому, що вам доведеться бути готовими до повторної спроби або відмови, якщо відбувається одночасна транзакція. Зазвичай це проблема, але програми, що забезпечують оптимістичне блокування як стратегія одночасності, так чи інакше вимагатимуть цього керування, тому повторювані помилки читання, природно, прирівнюються до оптимістичних помилок блокування, і це фактично не додасть нових недоліків.
Лі Лі Раян

13

Ви повинні виконати таку пару атомно:

  • перевірка дійсності тегу (тобто оновлена)
  • оновлення ресурсу (що включає оновлення його тегу)

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

Це все ще вважається оптимістичним блокуванням, якщо подивитися на велику картину: що сам ресурс не блокується початковим зчитуванням (GET) будь-яким Користувачем або будь-якими Користувачами, які переглядають дані, з наміром оновити чи ні.

Потрібна деяка атомна поведінка, але це відбувається в межах одного запиту (PUT), а не намагання утримувати блокування через кілька мережевих взаємодій; це оптимістичне блокування: об'єкт не заблокований GET, але все ще може бути безпечно оновлений PUT.

Існує також багато способів досягти атомного виконання цих двох операцій - блокування ресурсу - не єдиний варіант; Наприклад, легкої нитки або блокування об'єкта може бути достатньо і залежить від архітектури та контексту виконання програми.


4
+1 за те, що зазначив, що це атомне значення. Залежно від базового ресурсу, який оновлюється, це може бути здійснено без транзакцій або блокування. Наприклад, атомне порівняння та обмін ресурсу в пам'яті або джерело збереження даних про події.
Аарон М. Ешбах

@ AaronM.Eshbach, погодився і дякую за те, що зателефонував.
Ерік Ейдт

1

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

GET /projects/1

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

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

Оскільки клієнт тепер має значення E-Tag, він може включати це у PUTзапит:

PUT /projects/1
If-Match: "412"
Content-Type: application/json
{modified: true}

На даний момент у вашій заяві потрібно зробити наступне:

  • Переконайтеся, що електронний тег все-таки правильний: "412" == "412"?
  • Якщо так, зробіть оновлення та обчисліть новий E-тег

Надішліть відповідь про успіх.

204 No Content
E-Tag: "543"

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

  • Переконайтесь, що електронний тег все-таки правильний: "412"! = "543"

У разі відмови надішліть відповідь про помилку.

412 Precondition Failed

Це код, який ви насправді повинні писати. Фактично E-тегом може бути будь-який текст (у межах, визначених у специфікації HTTP). Це не повинно бути числом. Це може бути і хеш-значення.


Це не стандартні позначення HTTP, які ви використовуєте тут. У стандартному сумісному HTTP ви використовуєте лише ETag у заголовку відповіді. Ви ніколи не надсилаєте ETag у заголовок запиту, а замість цього використовуєте раніше придбане значення ETag у заголовку If-Match або If-None-Match у заголовках запитів.
Lie Lie Ryan

-2

Як доповнення до інших відповідей я опублікую одну з найкращих цитат у документації ZeroMQ, яка правдиво описує основну проблему:

Щоб зробити абсолютно ідеальними програми MT (я маю на увазі це буквально), нам не потрібні мутекси, блокування чи будь-яка інша форма міжпотокового зв'язку, крім повідомлень, що надсилаються через розетки ZeroMQ.

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

Якщо ви витратили роки на вивчення хитрощів, щоб змусити код МТ взагалі працювати, не кажучи вже про швидкі, із замками та семафорами та критичними розділами, вам буде огида, коли ви зрозумієте, що це все дарма. Якщо є один урок, який ми засвоїли за 30+ років одночасного програмування, це: просто не поділяй стан. Це як два п’яниці, які намагаються поділитися пивом. Не має значення, чи добрі вони. Рано чи пізно вони вступають у бій. І чим більше п'яниць ви додасте до столу, тим більше вони б’ються між собою за пиво. Трагічна більшість програм МТ виглядає як п'яні бари.

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