У розподіленій системі мережа ненадійна: запити губляться, сервіси перезапускаються, тимчасово перевантажуються. Код, що викликає інший сервіс, має бути готовий до цього.
Тайм-аут - обов'язковий для кожного мережевого виклику. Без нього запит до сервісу, що «завис», може чекати хвилини. Процес PHP зайнятий, наступні запити теж чекають - і за кілька хвилин вичерпано всі воркери застосунку через проблему іншого сервісу.
Http::connectTimeout(3) // встановлення з'єднання
->timeout(10) // уся відповідь
->get('https://inventory.internal/api/stock/42');
Значення - з урахуванням очікуваного часу відповіді, а не «з запасом на всяк випадок»: 10 секунд для виклику, що зазвичай займає 100 мс, означає, що користувач чекатиме 10 секунд при кожній проблемі.
Повторні спроби - для тимчасових помилок:
Http::timeout(5)
->retry([200, 500, 1000], when: fn (Throwable $e) =>
$e instanceof ConnectionException
|| ($e instanceof RequestException && $e->response->serverError()))
->get($url);
Що повторювати, а що ні:
- так: тайм-аути з'єднання, помилки мережі,
502,503,504,429(з урахуваннямRetry-After); - ні:
400,401,403,404,422- повтор дасть той самий результат; - обережно: неідемпотентні операції (
POSTстворення, списання коштів) - лише з ключем ідемпотентності, інакше повтор створить дублікат.
Правила повторів:
- експоненційна затримка (200 мс, 400 мс, 800 мс...) замість миттєвих повторів;
- випадковий розкид (jitter) - щоб тисячі клієнтів після збою не повторювали синхронно й не «добили» сервіс, що відновлюється;
- обмеження кількості спроб і загальний бюджет часу на операцію;
- повтори не на кожному рівні: якщо клієнт повторює 3 рази, сервіс A повторює виклик B 3 рази, а B - виклик C 3 рази, один збій C перетворюється на 27 запитів (шторм повторів).
Для фонових операцій - повтори через чергу: tries і backoff у джобі, а не цикл повторів усередині HTTP-запиту.
Коли повтори не допомагають (сервіс лежить надовго) - потрібен circuit breaker, що перестає надсилати запити до несправного сервісу на певний час.