Operações de E/S Escaláveis: Acesso a Webservices

Durante o processamento de uma requisição HTTP ou de uma mensagem Websocket a lógica do algoritmo pode implicar que dados sejam enviados para ou obtidos de um webservice. Em ambos os casos, o programa servidor passa a atuar como um programa cliente do webservice.

O programa no lado servidor da aplicação deve então usar o protocolo HTTP para enviar ou solicitar dados ao webservice. O problema de escalabilidade ocorrerá caso a execução do algoritmo tenha que ser suspensa enquanto o ciclo requisição-processamento-resposta não for completado pelo webservice.

Considere, como exemplo, o seguinte código JavaScript que é executado pelo programa servidor para atender uma requisição.

function processaRequisicao (req, res) {
   const id = req.param.id
   const url = 'http://funcionarios.ws.com?id=' + id

   const respWS = ws.get(url) // invocação do webservice

   const jsonFuncionario = obtenhaFuncionario(respWS)
   res.json(jsonFuncionario)
}

A função processaRequisicao tem por objetivo enviar uma resposta HTTP contendo os dados de um funcionário cujo id foi enviado como parâmetro na requisiço HTTP.

O problema de escalabilidade do algoritmo implementado na função processaRequisicao está na forma como o webservice é invocado. O comando de atribuição presente na linha

const respWS = ws.get(url)

demorará muito tempo para ser executado. Na lógica do algoritmo, é necessário aguardar a execução da linha de código para se poder obter os dados do funcionário.

Assim, se, por exemplo, a execução do algoritmo contido na função processaRequisicao demorar 500 ms é razoável afirmar que quase na totalidade deste tempo a CPU não está executando nenhuma instrução. Literalmente, a execução do algoritmo está suspensa (até que a resposta HTTP enviada pelo webservice chegue).

A execução do método get do objeto ws está bloqueando a execução do algoritmo contido na função processaRequisicao.

A solução para o problema é utilizar uma implementação não bloqueante do método get.

Uma versão não bloqueante poderia ser uma que retornasse um Promise que, em algum momento futuro, conteria a resposta HTTP enviada pelo webservice.

Por exemplo, a versão que utiliza Promise poderia ser

function processaRequisicao (req, res) {
   const id = req.param.id
   const url = 'http://funcionarios.ws.com?id=' + id

   const promRespWS = ws.getAsync(url) // invocação do webservice retornando um objeto da classe Promise

   promRespWS
     .then(respWS => obtenhaFuncionarioAsync(respWS))
     .then(jsonFuncionario => res.json(jsonFuncionario))
}

Nesta versão o tempo de execução do algoritmo contido na função processaRequisicao é de poucos milisegundos. Com isso, a thread que foi alocada para executar a função ficará rapidamente (poucos milisegundos) liberada para ser usada no processamento de outra requisição.

Como consequência, não é necessário definir um pool muito grande de threads para atender as requisições. Alguns frameworks para desenvolvimento web definem que o tamanho do pool seja igual ao número de CPUs do servidor. Outros, como é o caso do Node, define uma única thread para atender todas as requisições. Em ambos os casos, o mais importante é não utilizar bibliotecas que façam com que a execução de um algoritmo fique suspensa aguardando a operação de E/S.

results matching ""

    No results matching ""