Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Três questões principais prendem-se com a utilização ótima da rede:
- Latência da rede
- Saturação da rede
- Implicações do processamento de pacotes
Esta seção apresenta uma tarefa de programação que requer o uso de RPC e, em seguida, projeta duas soluções: uma mal escrita e outra bem escrita. Ambas as soluções são então examinadas e seu impacto no desempenho da rede é discutido.
Antes de discutir as duas soluções, as próximas seções discutem e esclarecem problemas de desempenho relacionados à rede.
Latência da rede
Largura de banda de rede e latência de rede são termos separados. Redes com alta largura de banda não garantem baixa latência. Por exemplo, um caminho de rede que atravessa um link de satélite geralmente tem alta latência, mesmo que a taxa de transferência seja muito alta. Não é incomum que uma viagem de ida e volta de rede atravessando um link de satélite tenha cinco ou mais segundos de latência. A implicação de tal atraso é a seguinte: um aplicativo projetado para enviar uma solicitação, esperar por uma resposta, enviar outra solicitação, esperar por outra resposta, e assim por diante, esperará pelo menos cinco segundos para cada troca de pacotes, independentemente da velocidade do servidor. Apesar do aumento da velocidade dos computadores, as transmissões por satélite e os meios de rede baseiam-se na velocidade da luz, que geralmente se mantém constante. Como tal, é pouco provável que ocorram melhorias na latência das redes de satélites existentes.
Saturação da rede
Alguma saturação ocorre em muitas redes. As redes mais fáceis de saturar são links de modem lentos, como modems analógicos padrão de 56k. No entanto, os links Ethernet com muitos computadores em um único segmento também podem ficar saturados. O mesmo é verdade para redes de longa distância com uma largura de banda baixa ou link sobrecarregado, como um roteador ou switch que pode lidar com uma quantidade limitada de tráfego. Nesses casos, se a rede enviar mais pacotes do que seu elo mais fraco pode lidar, ela descarta pacotes. Para evitar congestionamento, a pilha TCP do Windows diminui quando pacotes descartados são detetados, o que pode resultar em atrasos significativos.
Implicações do processamento de pacotes
Quando programas são desenvolvidos para ambientes de nível superior como RPC, COM e até mesmo Windows Sockets, os desenvolvedores tendem a esquecer quanto trabalho ocorre nos bastidores para cada pacote enviado ou recebido. Quando um pacote chega da rede, uma interrupção da placa de rede é atendida pelo computador. Em seguida, uma chamada de procedimento adiado (DPC) é enfileirada e deve passar pelos drivers. Se qualquer forma de segurança for usada, o pacote pode ter que ser descriptografado ou o hash criptográfico verificado. Uma série de verificações de validade também devem ser realizadas em cada estado. Só então o pacote chega ao destino final: o código do servidor. O envio de muitos pequenos blocos de dados resulta em sobrecarga de processamento de pacotes para cada pequeno bloco de dados. O envio de um grande bloco de dados tende a consumir significativamente menos tempo de CPU em todo o sistema, mesmo que o custo de execução para muitos blocos pequenos em comparação com um grande bloco possa ser o mesmo para o aplicativo de servidor.
Exemplo 1: Um servidor RPC mal projetado
Imagine um aplicativo que deve acessar arquivos remotos, e a tarefa em questão é projetar uma interface RPC para manipular o arquivo remoto. A solução mais simples é espelhar as rotinas de arquivos de estúdio para arquivos locais. Fazer isso pode resultar em uma interface enganosamente limpa e familiar. Aqui está um arquivo .idl abreviado:
typedef [context_handle] void *remote_file;
... .
interface remote_file
{
remote_file remote_fopen(file_name);
void remote_fclose(remote_file ...);
size_t remote_fread(void *, size_t, size_t, remote_file ...);
size_t remote_fwrite(const void *, size_t, size_t, remote_file ...);
size_t remote_fseek(remote_file ..., long, int);
}
Isso parece elegante o suficiente, mas, na verdade, esta é uma receita consagrada pelo tempo para o desastre de desempenho. Ao contrário da opinião popular, a chamada de procedimento remoto não é simplesmente uma chamada de procedimento local com um fio entre o chamador e o chamador.
Para ver como essa receita queima o desempenho, considere um arquivo 2K, onde 20 bytes são lidos desde o início e, em seguida, 20 bytes do final, e veja como isso funciona. No lado do cliente, as seguintes chamadas são feitas (muitos caminhos de código são omitidos para brevidade):
rfp = remote_fopen("c:\\sample.txt");
remote_read(...);
remote_fseek(...);
remote_read(...);
remote_fclose(rfp);
Agora imagine que o servidor está separado do cliente por um link de satélite com um tempo de ida e volta de cinco segundos. Cada uma dessas chamadas deve aguardar uma resposta antes de poder prosseguir, o que significa um mínimo absoluto para executar esta sequência de 25 segundos. Considerando que estamos recuperando apenas 40 bytes, este é um desempenho escandalosamente lento. Os clientes desta aplicação ficariam furiosos.
Agora imagine que a rede está saturada, porque a capacidade de um roteador em algum lugar no caminho da rede está sobrecarregada. Esse design força o roteador a lidar com pelo menos 10 pacotes se não tivermos segurança (um para cada solicitação e um para cada resposta). Isso também não é bom.
Esse design também força o servidor a receber cinco pacotes e enviar cinco pacotes. Mais uma vez, não é uma implementação muito boa.
Exemplo 2: Um servidor RPC melhor projetado
Vamos redesenhar a interface discutida no Exemplo 1 e ver se podemos melhorá-la. É importante notar que tornar este servidor realmente bom requer conhecimento do padrão de uso para os arquivos fornecidos: tal conhecimento não é assumido para este exemplo. Portanto, este é um servidor RPC melhor projetado, mas não um servidor RPC idealmente projetado.
A ideia neste exemplo é recolher o maior número possível de operações remotas em uma operação. A primeira tentativa é a seguinte:
typedef [context_handle] void *remote_file;
typedef struct
{
long position;
int origin;
} remote_seek_instruction;
... .
interface remote_file
{
remote_fread(file_name, void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
size_t remote_fwrite(file_name, const void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
}
Este exemplo recolhe todas as operações para uma leitura e gravação, o que permite uma abertura opcional na mesma operação, bem como um fechamento e busca opcionais.
Esta mesma sequência de operação, quando escrita de forma abreviada, tem a seguinte aparência:
remote_read("c:\\sample.txt", ..., &rfp, FALSE, NULL);
remote_read(NULL, ..., &rfp, TRUE, seek_to_20_bytes_before_end);
Ao considerar o servidor RPC melhor projetado, na segunda chamada o servidor verifica se o file_name está NULL e usa o arquivo aberto armazenado em rfp. Em seguida, ele vê que há instruções de busca e posicionará o ponteiro do arquivo 20 bytes antes do final antes de ler. Quando terminar, ele reconhecerá o sinalizador CloseWhenDone estiver definido como TRUE, fechará o arquivo e fechará rfp.
Na rede de alta latência, esta versão melhor leva 10 segundos para ser concluída (2,5 vezes mais rápido) e requer processamento de apenas quatro pacotes; dois recebem do servidor e dois enviam do servidor. Os extras ifs e desmarshaling que o servidor executa são insignificantes em comparação com todo o resto.
Se a ordenação causal for especificada corretamente, a interface pode até ser assíncrona, e as duas chamadas podem ser enviadas em paralelo. Quando a ordem causal é usada, as chamadas ainda são despachadas em ordem, o que significa que na rede de alta latência apenas um atraso de cinco segundos é suportado, mesmo que o número de pacotes enviados e recebidos seja o mesmo.
Podemos colapsar isso ainda mais criando um método que usa uma matriz de estruturas, cada membro da matriz descrevendo uma operação de arquivo específica; uma variação remota de E/S de dispersão/coleta. A abordagem compensa desde que o resultado de cada operação não exija processamento adicional no cliente; Em outras palavras, o aplicativo vai ler os 20 bytes no final, independentemente de quais são os primeiros 20 bytes lidos.
No entanto, se algum processamento deve ser executado nos primeiros 20 bytes depois de lê-los para determinar a próxima operação, recolher tudo em uma operação não funciona (pelo menos não em todos os casos). A elegância do RPC é que um aplicativo pode ter ambos os métodos na interface e chamar qualquer um dos métodos, dependendo da necessidade.
Em geral, quando a rede está envolvida, é melhor combinar o maior número possível de chamadas numa única chamada. Se um aplicativo tiver duas atividades independentes, use operações assíncronas e deixe-as ser executadas em paralelo. Essencialmente, mantenha o gasoduto cheio.