O poder do HTTP/2

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:

  1. 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!

Leave a Reply

O seu endereço de email não será publicado. Campos obrigatórios marcados com *