Processamento de Negócios Lentos

Às vezes, precisamos lidar com negócios lentos. Para evitar que esses negócios afetem o processamento de outras solicitações do webman, diferentes soluções podem ser usadas dependendo da situação.

Solução Um: Usar Filas de Mensagens

Referência fila redis fila stomp

Vantagens

Pode lidar com solicitações de processamento de negócios em massa inesperadas.

Desvantagens

Não é possível retornar diretamente o resultado ao cliente. Se for necessário enviar resultados, é preciso usar outros serviços, como por exemplo, webman/push para enviar o resultado do processamento.

Solução Dois: Adicionar uma Porta HTTP

Adicionar uma porta HTTP para lidar com solicitações lentas. Essas solicitações lentas entram em um conjunto específico de processos através de acesso a esta porta, e após o processamento, o resultado é retornado diretamente ao cliente.

Vantagens

Os dados podem ser retornados diretamente ao cliente.

Desvantagens

Não é capaz de lidar com solicitações em massa inesperadas.

Passos de Implementação

Adicione a seguinte configuração em config/process.php.

return [
    // ... outras configurações omitidas ...

    'task' => [
        'handler' => \Webman\App::class,
        'listen' => 'http://0.0.0.0:8686',
        'count' => 8, // número de processos
        'user' => '',
        'group' => '',
        'reusePort' => true,
        'constructor' => [
            'requestClass' => \support\Request::class, // configuração da classe request
            'logger' => \support\Log::channel('default'), // instância de log
            'appPath' => app_path(), // localização do diretório app
            'publicPath' => public_path() // localização do diretório public
        ]
    ]
];

Dessa forma, a interface lenta pode ser acessada através de http://127.0.0.1:8686/, sem afetar o processamento de negócios dos outros processos.

Para que o front-end não perceba a diferença da porta, você pode adicionar um proxy para a porta 8686 no nginx. Supondo que os caminhos da solicitação da interface lenta comecem todos com /tast, a configuração completa do nginx seria semelhante a:

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

# Adicione um upstream 8686
upstream task {
   server 127.0.0.1:8686;
   keepalive 10240;
}

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

  # Solicitações que começam com /tast vão para a porta 8686, ajuste /tast conforme necessário
  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;
  }

  # Outras solicitações vão para a porta original 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;
      }
  }
}

Assim, quando o cliente acessa dominio.com/tast/xxx, a solicitação será processada na porta separada 8686, sem afetar o processamento da porta 8787.

Solução Três: Utilizar HTTP Chunked para Envio Assíncrono de Dados

Vantagens

Os dados podem ser retornados diretamente ao cliente.

Instalar 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('')); // Enviar um chunk vazio representa o fim da resposta
        });
        // Primeiro enviar um cabeçalho http, dados subsequentes serão enviados de forma assíncrona
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }
}

Dica
Neste exemplo, estamos usando o cliente workerman/http-client para obter resultados http de forma assíncrona e retornar os dados. Também é possível usar outros clientes assíncronos, como AsyncTcpConnection.