Задать вопрос
vechnokrainii
@vechnokrainii
ну почти всегда(

Как обработать 2 одновременных запроса со стороннего сервера?

Здравствуйте! Столкнулся с такой проблемой!
Пользователь кидает некий запрос на мой сервер (на handler.php например)
В теле запроса есть какие-то параметры какие мне нужны например
$_REQUEST = [
    'auth' => '123412341234',
    'member_id' => 'asdfasdfasdf'
];

И мне нужно на этот запрос ответить,

Проблема в том что в моменте мне приходят два полностью идентичных запроса в одно и тоже время.
Под капотом я обращаюсь по ресту к стороннему сервису и дублирование мною запросов приводит там к ошибке.

Что пробовал.
1. Вставлял рандомную задержку в hadler.php и потом проверял есть уже такой запущенный процесс (реализовывал что блокировку потока к файлу fopen flock)
2 Складировал запросы в редис, потом из handler.php делал exec(php worker.php) и разбирал то что есть в очереди, но проблема в том что все равно запускается 2 процесса worker.php
3. Пытался на уровне сервера настроить (Centos стоит) но там только кол-во запросов в секунду, а мне приходят прям одновременно.

Подскажите, пожалуйста, как можно решить эту проблему.
  • Вопрос задан
  • 181 просмотр
Подписаться 1 Средний 17 комментариев
Пригласить эксперта
Ответы на вопрос 1
Охх... Никогда не думал, что столкнусь с race condition в PHP )))
В других языках такое решают общим хранилищем данных для всех параллельных потоков и использованием мьютекса.

Мы же можем воспользоваться инструментами Redis, которые сделали специально для решения таких вещей. У Redis есть специальные флаги, позволяющие использовать атомарные операции. Команда SET с параметрами NX и EX.
  • SET key value NX — установить ключ, только если он Not eXists (не существует).

  • SET key value EX seconds — установить ключ с временем жизни (eXpire) в секундах.

Комбинация этих двух флагов позволяет создать атомарную операцию "захватить блокировку на N секунд".

handler.php
<?php

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 1. Создаем уникальный ключ для этого конкретного запроса
$lockKey = 'request_lock:' . md5(json_encode($_REQUEST));

// 2. Пытаемся захватить ключ на 10 секунд
// Эта команда атомарная: только один процесс из двух победит.
$isLockAcquired = $redis->set($lockKey, '1', ['nx', 'ex' => 10]);

if ($isLockAcquired) {
    // КЛЮЧ НАШ! Делаем свою работу
    try {
        // ... обращаемся к стороннему сервису, запускаем worker.php или ваще что угодно ...
        
        // отвечаем клиенту об успехе операции
        http_response_code(200);
        echo json_encode(['status' => 'success']);

    } catch(\Throwable $e) {
        // Освобождаем ключ для будущих запросов в случае ошибки, чтобы не ждать 10с для переотправки.
        $redis->del($lockKey);
        // отвечаем клиенту, что произошла ошибка
        http_response_code(500);
        echo json_encode(['status' => 'error']);
    }

} else {
    // КЛЮЧ УЖЕ КЕМ-ТО ЗАНЯТ. Ничего не делаем.
    // Просто отвечаем клиенту, что все ок или что запрос дублируется.
    http_response_code(429); // Too Many Requests
    echo json_encode(['status' => 'error', 'message' => 'Request already in progress']);
    exit;
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы