Ответ
Оптимистичная блокировка — это стратегия управления параллельным доступом, которая позволяет избежать проблемы "потерянного обновления" (lost update) без использования реальных блокировок ресурсов на сервере. Основная идея в том, чтобы перед обновлением ресурса проверить, не был ли он изменен кем-то другим с момента его последнего чтения.
В HTTP это реализуется с помощью условных запросов (Conditional Requests), которые используют специальные заголовки.
Основные механизмы:
-
ETag и
If-Match/If-None-MatchETag(Entity Tag) — это заголовок ответа, содержащий уникальный идентификатор версии ресурса (например, хэш контента).If-Match— это заголовок запроса. Клиент, желающий обновить ресурс, отправляетPUTилиPATCHзапрос с этим заголовком, указываяETag, который он получил при последнем чтении. Сервер выполнит обновление только если текущийETagресурса совпадает с присланным. В противном случае сервер вернет ошибку412 Precondition Failed.If-None-Matchчаще используется для кэширования (GET-запросы), чтобы избежать повторной загрузки ресурса, если он не изменился (сервер вернет304 Not Modified).
-
Last-Modified и
If-Unmodified-Since/If-Modified-SinceLast-Modified— заголовок ответа, содержащий дату и время последнего изменения ресурса.If-Unmodified-Since— заголовок запроса, аналогичныйIf-Match, но использующий временную метку. Запрос будет выполнен, только если ресурс не изменялся с указанной даты.
Пример рабочего процесса с ETag:
- Клиент делает
GET /api/items/1и получает в ответеETag: "v1-abcde12345". - Клиент изменяет данные и отправляет
PUT /api/items/1с заголовкомIf-Match: "v1-abcde12345". - Сервер:
- Если текущий
ETagресурса все еще"v1-abcde12345", сервер обновляет ресурс и возвращает200 OK. - Если другой клиент уже обновил ресурс и его
ETagтеперь"v2-fghij67890", сервер отклоняет запрос и возвращает412 Precondition Failed.
- Если текущий
Пример в Go:
// Клиентский код
req, _ := http.NewRequest("PUT", url, requestBody)
// Устанавливаем заголовок для оптимистичной блокировки
req.Header.Set("If-Match", "полученный-ранее-etag")
client := &http.Client{}
resp, err := client.Do(req)
if resp.StatusCode == http.StatusPreconditionFailed {
// Обработать конфликт: сообщить пользователю, что данные устарели
} Ответ 18+ 🔞
А, слушай, вот это тема! Оптимистичная блокировка, блядь! Это ж как в жизни — ты думаешь, что всё под контролем, а потом раз — и кто-то уже твою булку с маслом сожрал. Но без драк, на цивильном уровне, ёпта.
Представь: два чувака в базе данных лезут к одной и той же строчке. Один думает: «Сейчас обновлю, и всё будет пиздато». Другой — такой же. Если просто дать им обоим писать, получится каша, потерянное обновление, пиздец. Пессимисты сразу навесят замок, как будто в сейф заперли, и всё тормозит. А оптимисты — они верят в лучшее, блядь! Они идут по жизни с улыбкой, но с подстраховкой.
Вот как эта подстраховка в HTTP работает, на примере ETag. Это как татуировка на ресурсе, версионка. Сделал GET запрос — сервер тебе в ответе, кроме данных, подсунул ETag: "v1-abcde12345". Типа, «запомни эту метку, браток».
Теперь ты, такой весь из себя обновлённый, собираешься отправить PUT или PATCH. Но не просто так, а с читерским заголовком If-Match: "v1-abcde12345". Ты как бы говоришь серверу: «Эй, я меняю ту версию, которую видел. Если она до сих пор такая же — пропусти. Если уже кто-то успел её подшаманить — дай мне пизды».
Сервер смотрит. Ага, ETag у ресурса всё ещё "v1-abcde12345". Ну всё, красава, обновляй. Возвращает 200 OK. Но если за это время какая-то мартышлюшка уже всё поменяла и ETag стал "v2-fghij67890" — сервер тебе в ответ: 412 Precondition Failed. Всё, приехали. Конфликт, блядь. Тебе теперь надо пользователю сказать: «Слушай, там пока ты кнопку жал, данные уже поменялись. Обнови страницу, посмотри, что там новенького, и решай, что делать».
Есть ещё вариант с датами — Last-Modified и If-Unmodified-Since. Принцип тот же, только вместо хэша — штамп времени. Но ETag надёжнее, потому что время на серверах может плавать, а хэш — он точный.
Вот, смотри, как это в коде на Go выглядит. Блок кода не трогаю, он святой.
// Клиентский код
req, _ := http.NewRequest("PUT", url, requestBody)
// Устанавливаем заголовок для оптимистичной блокировки
req.Header.Set("If-Match", "полученный-ранее-etag")
client := &http.Client{}
resp, err := client.Do(req)
if resp.StatusCode == http.StatusPreconditionFailed {
// Обработать конфликт: сообщить пользователю, что данные устарели
}
Видишь? Всё просто. Отправил запрос, получил 412 — и волнение ебать, терпения ноль. Надо конфликт решать. Но зато никаких реальных блокировок на сервере, всё летает. Красота, да? Прям как в той истории про Герасима — думал, что он один с Муму, а там, оказывается, барыня уже приказ отдала. Вот и приходится метаться. Только здесь, слава богу, собачку топить не надо, а просто данные перечитать.