Alura > Cursos de Programação > Cursos de PHP > Conteúdos de PHP > Primeiras aulas do curso PHP Reativo: programação assíncrona em tempo real

PHP Reativo: programação assíncrona em tempo real

Programação assíncrona - Apresentação

E aí, pessoal? Boas-vindas à Alura! Meu nome é Vinicius Dias, e nesse treinamento a gente vai entender o que é programação reativa utilizando PHP.

E antes de aprender sobre programação reativa nós vamos falar de conceitos muito interessantes, como programação assíncrona, I/O não bloqueante, padrões como Event-loop, reactor, para só então nós colocarmos a mão na massa, ver sobre programação reativa. E no final de tudo, utilizando uma sintaxe muito simples com “orientação objetos”, nós vamos criar um web socket. Vamos criar tanto a parte do cliente, quanto um servidor em PHP.

Então durante esse treinamento, como eu comentei, nós veremos bastante coisa. Começaremos com um cenário onde requisições http demoram muito tempo para serem devolvidas, as respostas demoram muito tempo. Então aqui nós veremos onde se faz necessário, talvez, a programação assíncrona. A partir disso, falamos de programação assíncrona e caímos no conceito de I/O não bloqueante.

E para implementar na mão, na unha, vamos aprender funções interessantes, como stream_set_blocking, stream_select. Então veremos como implementar, no mais baixo nível possível, I/O não bloqueante e a programação assíncrona utilizando PHP. Mas tudo aqui com o nosso paradigma imperativo, sem nenhuma boa prática de código ainda, pelo menos por enquanto.

E depois vamos conhecer uma biblioteca que nos ajuda tanto nessa parte de programação assíncrona, mas principalmente na parte de programação reativa, que é a React PHP. Então aqui nós vamos entender como ele aplica o Event Loop que nós vamos entender como aplicar na prática, entendemos sobre o padrão reactor, e começa a brincar só colocando um contador na tela para ser executado de segundo em segundo, nós começamos a brincar. E depois traduzimos toda aquela parte de I/O não bloqueante utilizando o React PHP.

Entendido isso tudo, partiremos para um cenário um pouco mais complexo de criação de uma web socket mas a gente vai ver que é bem simples graças as bibliotecas que já existem no PHP. Tudo que precisamos implementar é uma interface e nós já temos toda essa reatividade acontecendo e a programação reativa acontecendo "por baixo dos panos". Mas claro, nós entendemos tudo que está acontecendo.

Também vou citar uma ferramenta no final, para ajudar com performance caso você tenha um cenário de muita demanda, de alta carga de requisições chegando, então fique até o final para conferir.

Isso é basicamente o que vamos entender nesse treinamento, ou seja, o que é programação reativa, quando programação reativa vai nos ajudar e como ela faz uso de programação assíncrona para ser realmente efetiva. E quando falamos de programação assíncrona, falaremos de I/O não bloqueante.

Então nós veremos como PHP pode se assemelhar ao Node e, inclusive, ganhar do Node em performance, em vários cenários, de forma muito simples.

Então, sem criar confusão te espero no próximo vídeo para começarmos a entender todo esse conceito de programação reativa.

Programação assíncrona - Entendendo o problema

Antes de colocarmos a mão na massa, vamos entender pelo menos a base, a ideia do que é, do que precisaremos para falar sobre programação reativa.

Então antes de qualquer coisa, programação reativa é ou um modelo de arquitetura, ou um paradigma de programação, dependendo da literatura que você consuma. Mas basicamente é uma forma de programarmos, de escrevermos código. E permite que através de programação assíncrona, que é uma outra técnica, reaja a fluxo de eventos, a fluxo de dados, ou seja, que passemos a tomar decisões de forma assíncrona.

Então vamos entender mais ou menos o que é essa tal de programação assíncrona. De forma bem simplificada, sem entrar em muitos detalhes ainda, porque veremos muito na prática durante esse treinamento.

Imagine que temos que fazer quatro requisições HTTP, ou qualquer protocolo, acessar 4 arquivos, enfim. Precisamos realizar quatro requisições que vão passar pela rede, ou que demoram algum tempo, só que não precisam de processamento.

Precisamos ou acessar o disco, que é demorado, ou acessar a rede, que é mais demorado ainda. Então nós temos essas quatro requisições, e elas não são dependentes. A requisição 2, por exemplo, não depende da resposta da requisição 1. Elas não são dependentes, são quatro requisições independentes.

Tendo isso em mente, normalmente, o que estamos habituados com programação síncrona é: realiza a requisição 1, e espera a resposta da requisição 1. Tendo a resposta, nós podemos realizar a requisição 2, para pegar a resposta da requisição 2. Depois realiza a terceira requisição e assim em diante.

Então, nesse meio tempo, enquanto o servidor está demorando para me devolver essa resposta, ou enquanto o HD ainda não terminou de carregar o arquivo. Enquanto eu não tenho a resposta dessa requisição, o processador não está fazendo nada. Ele está simplesmente parado aqui, esperando essa resposta.

Então com isso nós perdemos tempo, nós desperdiçamos ciclos do processador. Isso quer dizer que o processador está parado, fazendo nada, enquanto ele poderia estar trabalhando. Então uma das soluções para resolvermos esse tipo de problema, ou seja, uma solução para muito I/O, para muita entrada e saída, seja através de rede, de arquivos, é a programação assíncrona.

Então no modelo de programação assíncrona, independente de como seja implementado, teremos mais ou menos isso aqui.

Nós realizamos a requisição 1, independente da resposta, nós não vamos esperar o servidor fazer nada, já mandamos a requisição 2 e deixa o servidor processando, e sem esperar a resposta, já manda outra requisição. Enquanto nós estamos preparando a quarta requisição, nesse tempo que o processador vai enviar, a resposta da requisição 2 chegou para nós.

Então nesse momento nós temos o que fazer. O processador vai pegar essa resposta e processá-la, trabalhar em cima dela e fazer o que tem que fazer. E nós já estamos prontos para mandar a quarta requisição. E depois de mandar todas as requisições, nós já estamos recebendo a resposta da primeira requisição, depois a resposta da última requisição, depois da terceira requisição.

Com isso, nós não temos o tempo do processador bloqueado. Pelo menos não tanto tempo do processador bloqueado, não fazendo nada. Então quando nós temos muito I/O, nós falaremos bastante sobre esse I/O, que significa entrada e saída. Que pode ser leitura de arquivos, comunicação na rede, portas seriais, e afins.

Quando nós temos muito I/O programação assíncrona vai salvar nossa vida, vamos dizer assim. Então, basicamente, a lógica por trás da programação assíncrona é não esperar por uma resposta de I/O e continuar realizando outras tarefas. Quando a resposta chegar, nós paramos, processamos aquela resposta, e depois voltamos a fazer o que temos que fazer.

Então, o que fazemos basicamente é, e nisso já começamos talvez a entrar nesse mundo de programação reativa, nós começamos a reagir a eventos.

Então nós fizemos a requisição 1, a requisição 2 e a requisição 3. Quando a resposta da requisição 2 chegar, nós vamos reagir a essa resposta, a esse evento, a esse fluxo de dados que chegou para nós, e tomar alguma decisão. Só que isso desenhado dessa forma, ainda fica muito abstrato.

É muito confuso e muito difícil de pensar como que eu vou aplicar isso na prática. Então o que eu vou propor de exercício para esse primeiro capítulo ter uma parte prática e nós entendermos o que que é essa tal de programação assíncrona. Eu vou realizar duas ou três, vou realizar algumas requisições HTTP de forma síncrona e depois de forma assíncrona, para nós entendermos a diferença na prática do que acontece quando realizamos essa forma de programar.

Então nós vamos entender como realizar, como reagir a uma resposta, como realizar requisições. E para isso tudo a gente vai utilizar uma biblioteca externa específica de HTTP.

Então no próximo vídeo nós vamos colocar a mão na massa e utilizar a GuzzleHttp para realizar requisições HTTP assíncronas.

Programação assíncrona - GuzzleHTTP

Vamos entender como a programação assíncrona pode nos ajudar para depois começarmos a tratar sobre programação reativa.

O cenário que nós temos aqui é: eu criei um arquivo que vai simular um servidor http e esse servidor http, nessa simulação, vai ter uma demora na rede. Então, a cada requisição ele vai levar de 1 a 5 segundos, eu tenho na linha 4 um sleep, ele vai demorar de 1 a 5 segundos para nos devolver uma resposta. E a resposta simplesmente vai informar quanto tempo ele demorou.

Eu levantei na aba “Local (2)” um servidor web na porta 8080 e outro servidor web na porta 8000 na aba “Local (3)”. Vinicius, porque você levantou dois servidores? Levantei dois servidores porque eu estou usando php –S e ele só recebe uma requisição por vez. Então no exemplo que eu vou fazer, não funcionaria com servidor de testes.

Mas, basicamente, eu vou realizar duas requisições http, vou ver como o PHP se comporta, como que é a programação, e depois vou fazer essas requisições de forma assíncrona, para entendermos as vantagens e desvantagens de cada caso.

Então vamos criar um arquivo de teste “New > PHP File” eu vou fazer um require_once_ do nosso vendor/autoload.php e eu estou com um teclado diferente, então vou apanhar um pouco nesse treinamento, mas show de bola. Eu preciso de um cliente http, então vou pegar um $client na linha 5 que vai ser o cliente do GuzzleHttp.

New \GuzzleHttp\Client(), agora sim. O que eu tenho aqui, importei essa classe e eu tenho um cliente.

Antes de qualquer coisa vou fazer duas requisições, uma para o localhost na 8080, outra para o localhost na porta 8000. A $resposta1 vai ser utilizando esse client fazendo uma requisição get(url ‘http://localhost:8080 e o nome do arquivo que eu criei http-server.php’) .

Fiz a primeira requisição, agora posso fazer a segunda requisição. $resposta2, e vamos exibir agora o conteúdo da $resposta1. Criar: getBody(), getContents() Então vou colocar aqui a resposta 1 foi essa. Então a resposta 1 é isso daqui ' . $resposta1->getBody()->getContents();

E echo na resposta 2. E $resposta 2 pegando o corpo, e do corpo pegando o conteúdo, a string da resposta em si, ' . $resposta2->getBody()->getContents();. Tenho aqui: realizo a requisição, depois realizo a outra e depois exibo as respostas das duas.

Aqui como eu estou em um ambiente Linux, para executar eu vou fazer isso aqui: time, e aí php teste.php. Esse time antes de qualquer comando mostra para nós quanto tempo o comando levou para executar. Dei “enter” e o programa vai realizar a primeira requisição para o primeiro servidor, depois para o segundo, e exibir resposta dos dois.

Então repara que a primeira resposta levou 2 segundos, a segunda resposta levou 3 segundos, então somando tiveram 5 segundos de espera e é basicamente o tempo que esse comando levou para executar.

É o esperado, mas, inclusive deixa eu executar de novo enquanto eu vou falando, a resposta dois poderia ter vindo antes. Então por que não pegá-la? Na aba local, repara que as duas levaram um segundo só, então aí não teria problema. Mas a resposta dois poderia ter vindo antes, então porque já não pegar essa resposta?

E outra coisa, se a resposta 1 levar cinco segundos, só depois desses cinco segundos eu vou realizar a requisição 2. Então não preciso ficar esperando. E no total eu teria oito segundos de espera, eu não preciso disso, eu posso mandar todas as requisições de uma vez e depois pegar as respostas delas. Então essa a ideia por trás da programação assíncrona.

E usando Guzzle nós podemos fazer algo próximo disso. Então ao invés de realizar a requisição em si, eu vou informar que eu quero realizar essa requisição, mas de forma assíncrona. Então eu estou falando: “Guzzle, eu quero montar essa requisição aqui, mas de forma assíncrona. Não envia ela ainda não”. Então isso aqui é uma $promessa de requisição.

Nós falaremos bastante sobre essa palavra “promessa” aqui no futuro. Mas por enquanto, basicamente, isso que significa que eu vou ter uma resposta. Ainda não tenho.

Para eu pegar essas duas respostas eu posso utilizar um utilitário de promise, que é justamente promessa do GuzzleHttp, depois vamos falar mais sobre isso. Mas eu tenho aqui esse método unwrap, que basicamente espera todas as requisições serem processadas e depois me devolve todas as respostas.

Então vou ter aqui $promessa1 e $promessa2, e aqui vou ter as $respostas. Essas respostas vão ser do tipo ResponseInterface. Então isso aqui é um array.

E deixa eu importar também. Tenho as respostas, então na linha 19, ao invés de colocar na $resposta1, vai ser $respostas[0] e na linha 20 $respostas[1]. O que eu atingi com isso? Eu estou dizendo: “Guzzle, manda essas requisições de forma assíncrona, quando chegar você pega de volta e já adiciona nesse array aqui. Não precisa esperar a primeira resposta chegar para depois mandar a segunda requisição”.

Basicamente, simplificando o conceito, foi isso que nós fizemos. Vamos executar de novo e ver o que muda.

Então vai levar aquele tempo de demora, de cada um dos servidores, normal. Só que no final, olha só, e eu estava quebrando a cabeça aqui, tentando entender porque que ele somou as duas respostas, o tempo, e o que acontece que eu mandei as duas requisições para o mesmo servidor. Vamos trocar, deixa eu limpar a tela, clear.

Agora teoricamente nós vamos atingir o resultado esperado se eu não dei nenhuma outra bobeira. Agora eu não quero que ele some as duas respostas, o tempo demorado tem que ser só da resposta mais demorada. Então vamos ver se realmente o erro era só esse, e agora sim. Note que a resposta mais demorada levou 5 segundos, mas a segunda resposta chegou antes, então eu só precisei esperar 5 segundos para pegar as duas respostas.

Só para você ver que antes realmente estava demorando a soma dos tempos e não era por causa do erro das linhas 11 e 12, deixa eu desfazer essa parte de requisições assíncronas, trazer tudo de forma síncrona de novo e corrigir o servidor.

Agora vou realizar, e sem essa parte de requisições assíncronas, o tempo que ele vai levar é a soma das duas requisições. Então ele leva bastante mais tempo para realizar a tarefa.

Exatamente como o esperado, a primeira requisição levou 5 segundos, depois de 5 segundos ele fez a segunda requisição, que levou 4 segundos, então no total nós levamos 9 segundos.

Aqui nós já começamos a entender que programação assíncrona traz muitas vantagens, principalmente em performance no sentido de entrada e saída, I/O. Comunicação com rede, com arquivos, com portas seriais, teclado, enfim.

Quando nós temos I/O, dados sendo transferidos, vamos nos beneficiar da programação assíncrona e a programação reativa tem como base a programação assíncrona. No próximo vídeo nós falaremos de programação assíncrona e de um termo muito interessante que é I/O não-bloqueante.

Sobre o curso PHP Reativo: programação assíncrona em tempo real

O curso PHP Reativo: programação assíncrona em tempo real possui 133 minutos de vídeos, em um total de 51 atividades. Gostou? Conheça nossos outros cursos de PHP em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda PHP acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas