Fala meus queridos, belezinha?
Hoje vou falar um pouco sobre HTTP/2, sei que muita gente vem falando de gRPC e muitas vezes não explicam que o real poder do gRPC depende muito do HTTP/2 e vou demonstrar com alguns exemplos quantos benefícios você pode ter apenas migrando suas conexões HTTP/1.1 para HTTP/2 e vou começar contando uma história de um projeto que trabalhei recentemente.
Algumas semanas atrás estava trabalhando em um projeto onde temos dependências de outra aplicação e a comunicação é realizada através do protocolo HTTP utilizando o HttpClient em .net core e a aplicação roda em máquinas virtuais com linux. Após algum tempo rodando a aplicação em produção com uma boa carga, começamos a ter erros baixo nível do linux “Too many open files”, e com isso a aplicação caia. Tentamos várias soluções de configuração no HttpClient baseado neste artigo do Steve Gordon alterando o lifetime da conexão e também tentando limitar a quantidade de sockets por domínio, mas não parecia efetivo, e no fim para ter uma solução rápida acabamos limitando a quantidade de requisições por máquina nas políticas do auto scaling group. Com isso conseguimos evitar os problemas até achar uma solução melhor.
Após esse caso comecei a estudar a migração para gRPC, comecei a fazer vários testes para entender melhor como funcionava e os benefícios reais que teríamos migrando de protocolo e tendo que provavelmente implementar vários endpoints novos além dos clients, com isso cheguei na raíz do gRPC: O HTTP/2.
O HTTP/2 tem algumas diferenças importantes se comparado ao 1.1, abaixo as duas mais importantes pra mim:
- Conexões simultâneas ou multiplexing: HTTP/1.1 apenas consegue realizar a conexão de um recurso por vez, enquanto HTTP/2 pode fazer várias requisições diferentes na mesma conexão, na imagem abaixo fica mais claro.
2. Formato binário: HTTP/1.1 trafega plain text o que facilita para entender e implementar, porém tem um payload normalmente maior, com o binário conseguimos um tempo menor e também menos dados sendo trafegados.
Após entender isso, verifiquei que talvez seria possível reduzir a quantidade de sockets consumidos apenas migrando para HTTP/2 ao invés de gRPC.
Como o projeto era em .net core, basta definir a versão na mensagem conforme o exemplo abaixo:
var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Get, "https://google.com"){
Version = new Version(2, 0)
};
var x = await client.SendAsync(req);
var version = x.Version;
Console.WriteLine(version);
Se na resposta o header for 2.0, sucesso, o servidor que você está chamando tem suporte, caso contrário, será necessário configurar o servidor para expor o endpoint como HTTP/2, no meu caso a aplicação estava na AWS e o ALB automaticamente expõe o endpoint com suporte para HTTP/2, então basicamente uma linha de código trouxe resultados incríveis, com redução para até 30% do consumo de sockets comparado ao 1.1, com isso fomos capaz de aumentar novamente a capacidade de processamento por máquina sem tomar erro do linux por falta de sockets. Abaixo seguem alguns testes feitos localmente com um código em .net onde disparo 200 conexões para o google com ambas versões do protocolo e verifico no windows 10 quantas conexões realmente foram abertas.
HTTP/1.1
Código:
var client = new HttpClient();
var actions = new List<Action>();
for (int i = 0; i < 200; i++)
{
actions.Add(() =>
{
var req = new HttpRequestMessage(HttpMethod.Get, "https://google.com")
{
Version = new Version(1, 1)
};
var x = client.SendAsync(req).Result;
var version = x.Version;
Console.WriteLine(version);
});
}
Parallel.Invoke(actions.ToArray());
Console.ReadKey();
Resultados:
HTTP/2
Código:
var client = new HttpClient();
var actions = new List<Action>();
for (int i = 0; i < 200; i++)
{
actions.Add(() =>
{
var req = new HttpRequestMessage(HttpMethod.Get, "https://google.com")
{
Version = new Version(2, 0)
};
var x = client.SendAsync(req).Result;
var version = x.Version;
Console.WriteLine(version);
});
}
Parallel.Invoke(actions.ToArray());
Console.ReadKey();
Resultados:
Script que utilizei para verificar as conexões por processo:
$netstat = netstat -aon | Select-String -pattern "(TCP|UDP)"
$processList = Get-Process
foreach ($result in $netstat) {
$splitArray = $result -split " "
$procID = $splitArray[$splitArray.length – 1]
$processName = $processList | Where-Object {$_.id -eq $procID} |
select processname
$splitArray[$splitArray.length – 1] = $procID + " " +
$processName.processname
$splitArray -join " "
}
Como podemos ver, com HTTP/2 foi possível utilizar apenas 2 conexões para executar 200 requisições, enquanto com HTTP/1.1 foram criadas 18 conexões, o gerenciamento das conexões o próprio HttpClient que faz, mas normalmente no caso do HTTP/2 é possível ver a diferença enorme na quantidade de conexões, isso provavelmente vai trazer muitos benefícios para a sua aplicação caso tenha dependências de recursos onde o meio de comunicação é HTTP.
Mais detalhes sobre HTTP/2 nesse artigo.
Por hoje era isso, até a próxima!