December 17, 2024

Обработка десятков тысяч заказов и смена статуса с ограничением по запросам API 


🚀 Задача: Обработка десятков тысяч заказов и смена статуса с ограничением по запросам API 📦

Представьте, что перед вами стоит задача обновить статус для нескольких десятков тысяч заказов в внешней системе, но API ограничивает количество запросов до 50 в секунду. Как эффективно решить такую задачу? 🤔

Давайте разберемся, как с помощью генераторов, пакетных запросов и параллельных запросов через Guzzle можно справиться с этой задачей без перегрузки системы! 😎


🔧 Решение проблемы с обработкой заказов:

1️⃣ Используем генераторы:
Вместо того, чтобы сразу загружать все заказы в память, мы применяем генератор, который будет извлекать данные "по частям". Это помогает эффективно работать с большими объемами данных без переполнения памяти.

2️⃣ Пакетная обработка данных:
Каждую порцию данных (например, 50 заказов) извлекаем и отправляем на API. Это позволяет избежать перегрузки БД и контролировать количество запросов, отправляемых на сервер.

3️⃣ Параллельные запросы с Guzzle:
API ограничивает нас 50 запросами в секунду, но это не значит, что мы не можем отправлять их параллельно! Мы используем асинхронные запросы через Guzzle, отправляя сразу 50 запросов за раз. Это позволяет значительно ускорить процесс обработки, не нарушая лимит.

use GuzzleHttp\Client; use GuzzleHttp\Promise; use GuzzleHttp\Exception\RequestException;

// Генератор для выборки заказов из базы данных function getOrdersFromDatabase($batchSize = 50) {

$offset = 0;

while (true) { // Выполнение SQL-запроса для выборки заказов $stmt = $db->prepare("SELECT * FROM orders WHERE status = 'pending' LIMIT :batchSize OFFSET :offset"); $stmt->bindParam(':batchSize', $batchSize, PDO::PARAM_INT); $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); $stmt->execute();

$data = $stmt->fetchAll();

// Если данных больше нет, выходим из цикла if (empty($data)) { break; }

// Возвращаем порцию заказов yield $data;

// Переходим к следующей порции $offset += $batchSize; } }

// Функция для отправки статуса заказов на API function updateOrderStatus(Client $client, $orders) { $requests = []; foreach ($orders as $order) { // Создаем асинхронный запрос для обновления статуса $requests[] = $client->postAsync('https://api.example.com/update-status', [ 'json' => [ 'order_id' => $order['id'], 'status' => 'completed', // Пример статуса ] ]); }

// Возвращаем массив промисов return Promise\settle($requests); }

// Основная логика обработки заказов function processOrders() { $client = new Client(); // Инициализация Guzzle клиента $batchSize = 50; // Размер пачки заказов $maxRequestsPerSecond = 50; // Лимит API запросов в секунду $delayBetweenBatches = 1; // Задержка между батчами (сек)

// Процесс обработки заказов по батчам foreach (getOrdersFromDatabase($batchSize) as $batch) { try { // Отправляем 50 параллельных запросов на API $results = updateOrderStatus($client, $batch)->wait();

// Логируем успешные обновления (по желанию) foreach ($results as $result) { if ($result['state'] === 'fulfilled') { echo "Order {$batch[0]['id']} successfully updated\n"; // Пример логирования } else { echo "Failed to update order {$batch[0]['id']}: {$result['reason']->getMessage()}\n"; } }

} catch (RequestException $e) { // Обработка ошибок API или подключения echo "Request failed: " . $e->getMessage() . "\n"; } // Задержка между батчами для соблюдения лимита запросов в секунду sleep($delayBetweenBatches); } }

// Вызов основной функции processOrders();