
PHP criado para morrer. E tudo ficaria bem, mas ele não teve a chance de fazer isso recentemente. Há um ano, no hub, ocorreu o anúncio da ferramenta RoadRunner, forçando o processo PHP a deixar o círculo interminável de morte e ressurreição.
O princípio do trabalho do RoadRunner é manter o processo em execução e enviar solicitações recebidas, o que permite, de acordo com os desenvolvedores, aumentar o desempenho do aplicativo (às vezes até 40 vezes).
Desde que eu trabalho com o Magento há muito tempo, pareceu uma ótima idéia testar a ferramenta não em uma estrutura mítica, mas em um aplicativo real, para o qual o Magento Open Source funcionou muito bem.
Custo de inicialização do aplicativo Magento
A maneira de acelerar o aplicativo RoadRunner envolve a redução do tempo de resposta (após o início do aquecimento), reduzindo a sobrecarga de inicialização do aplicativo.

Captura de tela da apresentação de Anton Tsitou "Projetando aplicativos Go / PHP híbridos usando RoadRunner"
No caso do Magento, o principal tempo gasto na inicialização recai sobre:
- carregamento automático do compositor
- bootstrapping
O carregamento automático do compositor não chama a atenção, pois é padrão para um aplicativo PHP.

Criação de perfil de resultados relacionados ao Composer.
A inicialização do aplicativo Magento inclui a inicialização de um manipulador de erros, a verificação do status do aplicativo etc.
A parte mais difícil é inicializar o contêiner IoC (“ObjectManager” em termos de Magento) e criar recursivamente instâncias de dependência por meio dele para obter o objeto do aplicativo.

Criação de perfil de resultados relacionados ao bootstraping.
Implementação do RoadRunner
Para iniciar o RoadRunner, você precisa criar um trabalhador que contenha um ciclo para aceitar solicitações recebidas e enviar respostas. Além disso, a ferramenta trabalha com solicitações e respostas implementando o PSR-7. A partir da documentação oficial, é algo parecido com isto:
while ($req = $psr7->acceptRequest()) { $resp = new \Zend\Diactoros\Response(); $resp->getBody()->write("hello world"); $psr7->respond($resp); }
Magento e PSR-7
O Magento ainda não implementou o PSR-7 e, fora da caixa, usa suas implementações de solicitação e resposta, as abordagens nas quais eles são extraídos principalmente da versão anterior.
Para implementar o RoadRunner, você precisa encontrar um ponto de entrada que aceite a solicitação de alguma forma e retorne uma resposta ( exemplo do Symfony ).
Existe um ponto no Magento , \ Magento \ Framework \ AppInterface , existe apenas um problema, essa interface não foi projetada para aceitar a solicitação. Mas espere, de onde ele entra no aplicativo? Vale a pena voltar ao começo e o mantra - o PHP nasceu para morrer . Consequentemente, a grande maioria das bibliotecas, pacotes e estruturas, ao projetar e dividir em camadas, simplesmente não assume que a solicitação, ao que parece, não é apenas uma global.
A camada de transporte Magento é construída com o mesmo princípio. Embora a documentação descreva as diferenças entre objetos injetáveis / novos , na verdade, temos o uso da consulta como um serviço global de estado, inicializando-se a partir de variáveis globais ($ _GET, $ _POST). Além de tudo isso, a injeção desse serviço pode ser vista em todos os níveis do aplicativo no próprio kernel, sem falar na qualidade dos módulos de terceiros.
Com base no exposto, foi gasta a esperança de implementar o RoadRunner apenas através da conversão de solicitações do estilo PSR-7 para o estilo Magento.
Implementar adaptador PSR-7
Formulamos o problema, levando em consideração as informações recebidas.
Gostaria de ter uma certa interface de aplicativo que aceite uma solicitação PSR-7 e retorne uma resposta PSR-7. Também é necessário criar uma implementação da interface criada que adapte esse formato de interação ao aplicativo Magento.

Adaptador PSR-7
Como mencionado acima, o aplicativo magento já retorna uma resposta, portanto, precisamos apenas convertê-lo para o formato PSR-7.
Para a solicitação, você precisa de uma classe que faça proxy de todas as chamadas para o objeto de solicitação atual, que colocamos em um registro especial (sim, os deuses da arquitetura perdoarão essa perversão). Além disso, verificou-se que a classe de solicitação não é usada uma, mas 3, portanto, exige que elas sejam registradas novamente através da configuração de IoC do contêiner.

O conjunto de classes usado para trabalhar com consultas no Magento
Problemas de um aplicativo PHP imortal
Um aplicativo lançado pelo RoadRunner tem os mesmos problemas que qualquer processo php de longa duração, eles são descritos na documentação ( https://roadrunner.dev/docs/usage-production )
Os principais problemas do nível de aplicativo que o desenvolvedor deve monitorar são:
Atenção especial no contexto do Magento deve ser dada ao gerenciamento de estado, uma vez que, tanto no kernel quanto nos módulos de terceiros, o armazenamento em cache do usuário / produto / categoria atual dentro do serviço é uma abordagem muito comum.
protected function getCustomer(): ?CustomerInterface { if (!$this->customer) { if ($this->customerSession->isLoggedIn()) { $this->customer = $this->customerRepository->getById($this->customerSession->getCustomerId()); } else { return null; } } return $this->customer; }
Um exemplo de método do kernel usando o estado de um objeto.
Executando o Magento Rest API Server via RoadRunner
Dados os possíveis problemas com o estado global, com base na experiência de desenvolvimento da parte front-end do Magento, a parte WebApi mais adequada e indolor foi escolhida para o lançamento.
A primeira coisa a fazer é criar nosso trabalhador, que percorrerá o RoadRunner e viverá sem parar (quase). Para fazer isso, pegue um código nos guias do RoadRunner e adicione nosso aplicativo, envolto em um adaptador PSR-7.
$relay = new StreamRelay(STDIN, STDOUT); $psr7 = new PSR7Client(new Worker($relay)); $bootstrap = \Magento\Framework\App\Bootstrap::create(BP, []); $app = $bootstrap->createApplication(\Magento\Framework\App\Http::class); $psr7Application = $bootstrap->getObjectManager()->create( \Isxam\M2RoadRunner\Application\MagentoAppWrapper::class, [ 'magentoApp' => $app ] ); while ($request = $psr7->acceptRequest()) { try { $response = $psr7Application->handle($request); $psr7->respond($response); } catch (\Throwable $e) { $psr7->getWorker()->error((string)$e); } }
O código antes do loop while será executado no início do trabalhador, tudo o que estiver dentro do loop - a cada nova solicitação.
Quando nosso trabalhador estiver pronto, continuaremos a configurar o servidor RoadRunner escrito em Go. Tudo é bom e rápido aqui, apenas o pacote do compositor que baixa o arquivo executável não precisa ser instalado, o que não pode ser agradável. Criamos a configuração do nosso servidor - o mais simples se parece com isso.
http: address: 0.0.0.0:8086 workers: command: "php worker.php" pool: numWorkers: 1
Configuração do RoadRunner.
A documentação contém uma abundância de configurações que permitem configurar o servidor de forma flexível, para que o desejo de compilar seu binário não ocorra exatamente.
./rr serve -v -d
Início do servidor
Teste de solução
As ferramentas
Para testes convenientes, tomamos algo simples, por exemplo, artillery.io.
Testaremos o desempenho com a ajuda de um usuário executando consultas sequencialmente (o RoadRunner também suporta a execução de consultas em vários threads, deixaremos essa pergunta para outros pesquisadores)
Na entrada, temos o arquivo de configuração de artilharia com dois ambientes - Apache e RoadRunner. Ambos trabalham com a mesma instância do Magento, então aqui estão em pé de igualdade.
Cenários de teste
Os cenários a seguir foram usados para medir o desempenho das duas soluções.
Cenário 1. Criando uma categoria - name: "S1. Create category" flow: - loop: - post: url: "/rest/V1/categories" json: category: name: "name-{{prefix}}-{{ $loopCount }}" parent_id: 2 is_active: true count: 100
Cenário 2. Obtendo uma lista de países - name: "S2. Countries list" flow: - loop: - get: url: "/rest/V1/directory/countries" count: 100
Cenário 3: Listando tipos de produtos - name: "S3. Product types list" flow: - loop: - get: url: "/rest/V1/products/types" count: 100
Cenário 4. Obtendo uma lista de conjuntos de atributos - name: "S4. Product attribute sets list" flow: - loop: - get: url: "/rest/V1/products/attribute-sets/sets/list?searchCriteria" count: 100
Cenário 5. Obtendo uma categoria - name: "S5. Category get" flow: - loop: - get: url: "/rest/V1/categories/2" count: 100
Cenário 6. Criação de Produto - name: "S6. Create product" flow: - loop: - post: url: '/rest/V1/products' json: product: sku: "sku-{{prefix}}-{{ $loopCount }}" name: "name-{{prefix}}-{{ $loopCount }}" attribute_set_id: 4 price: 100 type_id: "simple" count: 100
Cenário 7. Recuperando uma lista de produtos - name: "S7. Get product list" flow: - loop: - get: url: "/rest/V1/products?searchCriteria[pageSize]=20" count: 100
Resultado
Depois de executar todos os scripts alternadamente através do RoadRunner e Apache, foram obtidas medianas da duração da consulta. De acordo com as medianas, a velocidade de todos os cenários difere aproximadamente no mesmo valor de ~ 50ms.

Resultado do teste de desempenho.
Sumário
Um experimento prático confirmou as suposições sobre a constância do ganho de desempenho do RoadRunner em um aplicativo específico. O uso desta ferramenta permite acelerar o processamento de solicitações de aplicativos para uma constante igual ao tempo de inicialização do ambiente e das dependências.
Em manipuladores leves, isso permite que você acelere o aplicativo às vezes; em trabalhos pesados, quase não produz um efeito tangível. Se o seu código for lento, provavelmente a banana não o ajudará.
Se seu aplicativo for bem escrito, provavelmente não haverá problemas com sua operação através do RoadRunner, mas se o aplicativo exigir adaptação para uso com o RoadRunner, provavelmente será necessário o mesmo sem o RoadRunner para observar mais claramente as camadas da arquitetura e seguindo os padrões de desenvolvimento em campo.
O Magento Open Source é geralmente adequado para o lançamento em um ambiente determinado, no entanto, requer melhorias na camada de transporte e correção da lógica de negócios para evitar comportamento incorreto durante solicitações repetidas no mesmo processo. Além disso, o uso do RoadRunner impõe certas restrições às abordagens de desenvolvimento, no entanto, elas não contradizem práticas bem estabelecidas.
Finalmente uma bela captura de tela. Quando você ainda verá solicitações do Magento com esse tempo de resposta? Referências
- Exemplo do artigo
- Site oficial do RoadRunner