Обработка медленных запросов

Иногда нам необходимо обрабатывать медленные запросы, и чтобы избежать влияния этих медленных процессов на другие запросы, для них можно использовать различные подходы в зависимости от ситуации.

Подход 1 Использование очереди сообщений

Обратитесь к очередям Redis и очередям Stomp

Преимущества

Позволяет справляться с мгновенными массовыми запросами на обработку.

Недостатки

Невозможно напрямую вернуть результат клиенту. Если необходимо отправить результат, потребуется сочетание с другими сервисами, например, используя webman/push для отправки результата обработки.

Подход 2 Добавление HTTP порта

Добавление нового HTTP порта для обработки медленных запросов; эти медленные запросы обрабатываются конкретной группой процессов при доступе к этому порту, после чего результат возвращается клиенту.

Преимущества

Можно напрямую вернуть данные клиенту.

Недостатки

Невозможно справляться с внезапной массовой нагрузкой запросов.

Шаги по реализации

В config/process.php добавьте следующую конфигурацию.

return [
    // ... здесь опущены другие настройки ...

    'task' => [
        'handler' => \Webman\App::class,
        'listen' => 'http://0.0.0.0:8686',
        'count' => 8, // число процессов
        'user' => '',
        'group' => '',
        'reusePort' => true,
        'constructor' => [
            'requestClass' => \support\Request::class, // настройка класса request
            'logger' => \support\Log::channel('default'), // экземпляр лога
            'appPath' => app_path(), // путь к каталогу app
            'publicPath' => public_path() // путь к каталогу public
        ]
    ]
];

Таким образом, медленные интерфейсы могут проходить через процессы по адресу http://127.0.0.1:8686/, не влияя на обработку других процессов.

Чтобы обеспечить, чтобы фронтенд не ощущал различий между портами, можно добавить в nginx прокси на порт 8686. Предположим, что все пути запросов медленного интерфейса начинаются с /tast, общая конфигурация nginx будет выглядеть примерно так:

upstream webman {
    server 127.0.0.1:8787;
    keepalive 10240;
}

# Добавляем новый 8686 upstream
upstream task {
   server 127.0.0.1:8686;
   keepalive 10240;
}

server {
  server_name webman.com;
  listen 80;
  access_log off;
  root /path/webman/public;

  # Запросы, начинающиеся с /tast, идут на порт 8686; измените /tast на нужный вам префикс
  location /tast {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      proxy_pass http://task;
  }

  # Другие запросы идут через оригинальный порт 8787
  location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      if (!-f $request_filename){
          proxy_pass http://webman;
      }
  }
}

Таким образом, когда клиент обращается к domain.com/tast/xxx, запрос будет обрабатываться на отдельном порту 8686, не влияя на обработку запросов на порту 8787.

Подход 3 Использование HTTP chunked для асинхронной отправки данных по частям

Преимущества

Можно напрямую вернуть данные клиенту.

Установка workerman/http-client

composer require workerman/http-client

app/controller/IndexController.php

<?php
namespace app\controller;

use support\Request;
use support\Response;
use Workerman\Protocols\Http\Chunk;

class IndexController
{
    public function index(Request $request)
    {
        $connection = $request->connection;
        $http = new \Workerman\Http\Client();
        $http->get('https://example.com/', function ($response) use ($connection) {
            $connection->send(new Chunk($response->getBody()));
            $connection->send(new Chunk('')); // отправка пустого chunk для завершения response
        });
        // сначала отправляем http заголовок, а последующие данные отправляются асинхронно
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }
}

Совет
В этом примере используется workerman/http-client для асинхронного получения HTTP результата и возврата данных; также можно использовать другие асинхронные клиенты, такие как AsyncTcpConnection.