
Motivações e Objetivos
Ao realizarmos trabalhos em pequena escala a fim de conhecermos melhor o sistema RMI, normalmente utilizamos a mesma máquina para atuar, simultaneamente, como cliente e servidor. Afinal, ao estudarmos sobre o tema, nem sempre temos acesso a diferentes computadores que nos possibilitem realmente perceber como uma invocação remota se comporta, incluindo seu tempo de execução e sua tolerânica a falhas.
Com estes princípios em mente, utilizaremos esta seção para submeter duas máquinas (conectadas à mesma rede sem fio local) a chamadas RMI, a fim de examinar o comportamento do sistema como um todo e observar suas respostas a certas situações adversas.

Tempo de Invocação
Para avaliarmos o tempo que o sistema RMI leva para realizar suas invocações, criamos um método de "somar offset" na máquina servidor que será invocado pela máquina cliente. Como parâmetros da chamada, iremos enviar um array de floats, seu tamanho e um offset, o qual deverá ser somado a todos os elementos do array. Foram utilizadas 4 threads para subdividir o array e realizar 4 invocações simultâneas, cada uma em 1/4 do array; o tempo medido para cada iteração é, portanto, a diferença entre o horário de início da primeira thread e o horário de retorno da última delas.
Tamanho do Array (elementos) | Tamanho do Array (bytes) | Média de 8 invocações (segundos) | Desvio Padrão (segundos) |
---|---|---|---|
1.000 | 4 KB | 0,03324 | 0,02036 |
10.000 | 40 KB | 0,07359 | 0,01165 |
100.000 | 400 KB | 0,64190 | 0,13845 |
1.000.000 | 4 MB | 6,43782 | 0,36490 |
10.000.000 | 40 MB | 59,86252 | 3,37119 |
Tendo em vista que, durante todo o processo de invocação, a utilização de CPU em ambas as máquinas permanece bastante baixa (variando em torno de 4% no servidor - convidamos o leitor a repetir o experimento e averiguar), podemos concluir que o gargalo de nosso sistema RMI, neste caso, é a largura de banda. De fato, ao passarmos como argumento um array de 40 MB (como no último registro da tabela), teremos um tráfego de dados total equivalente a 80 MB (levando em conta a ida de uma cópia do array para o servidor e sua posterior volta, já modificado, para o cliente).
Este experimento nos mostra que, ao distribuirmos nosso processamento, precisamos levar em conta os diferentes espaços de endereçamento de cada máquina. Para efeitos de comparação, uma simples soma de offset de complexidade O(n) em um array de 40 MB, se feita localmente em um computador moderno, não leva mais de 10% do tempo registrado na última linha da tabela (novamente, convidamos o leitor a averiguar). Além disso, existem técnicas para acessos a endereços de memória sequenciais (e.g. memória cache) que agilizam imensamente o processo quando realizado localmente. Portanto, a decisão de distribuir o processamento deve levar em conta diversos fatores, não se restringindo apenas à disponibilidade de recursos.
Tolerância a Falhas
Para estes experimentos, nosso objetivo foi observer como o RMI reage a alguns erros que podem ocorrer durante sua utilização. Primeiramente, foi tentado remover a conexão de rede sem fio por diferentes intervalos de tempo durante a execução da chamada. Para tal, apenas utilizamos o atalho do teclado para habilitar e desabilitar a conexão do servidor, como pode ser observado na imagem abaixo:

Foi possível observar resultados diversos. Cronometramos inicialmente 5 segundos entre a remoção da conexão até seu restabelecimento. Para um array com 1.000.000 elementos do tipo float, sendo passado como argumento para a invocação de "somar offset", foi possível observar:
Intervalo sem conexão (segundos) | Invocações bem-sucedidas (total de 5) | Média das invocações bem-sucedidas (segundos) | Desvio Padrão (segundos) |
---|---|---|---|
3 ± 2 | 5 | 18,28779 | 0,97279 |
5 ± 2 | 4 | 19,60036 | 1,03307 |
7 ± 2 | 5 | 24,14289 | 2,02709 |
9 ± 2 | 5 | 25,04582 | 3,57361 |
11 ± 2 | 5 | 30,03137 | 4,11127 |
13 ± 2 | 5 | 33,77045 | 4,80847 |
15 ± 2 | 4 | 37,73766 | 2,54372 |
17 ± 2 | 5 | 41,46283 | 7,13920 |
19 ± 2 | 4 | 39,52466 | 2,37662 |
21 ± 2 | 3 | 37,29614 | 0,69318 |
23 ± 2 | 3 | 46,30076 | 15,31812 |
25 ± 2 | 2 | 38,34023 | 0,37112 |
Quando uma invocação falhava, uma exceção era gerada. Os dois tipos de exceções encontrados durante a execução dos testes foram:
- java.rmi.UnmarshalException: Error unmarshaling return header; nested exception is:
java.net.SocketTimeoutException: Read timed out - java.rmi.MarshalException: error marshalling arguments; nested exception is:
java.net.SocketException: Connection reset by peer: socket write error
De acordo com o experimento acima, não foi possível determinar o intervalo de tempo aproximado após o qual o sistema RMI considera a perda total da comunicação e lança uma exceção. No entanto, observamos que, para intervalos maiores, o número de exceções aumenta (pois a quantidade de invocações bem-sucedidas diminui). A tabela nos mostra que o sistema RMI conseguiu se recuperar diversas vezes de falhas de comunicação bastante severas (maiores que 20 segundos), algo relativamente surpreendente.
Nos casos em que ocorreram exceções, é necessário lembrar que a semântica do RMI é "no-máximo-uma vez". Isto significa que, ao recebermos uma exceção, temos a certeza de que o servidor processou a invocação ou uma vez ou nenhuma vez. Podemos criar programas que implementem outras semânticas como, por exemplo, "pelo-menos-uma-vez". Para tal, ao recebermos uma exceção, basta repetirmos a chamada quantas vezes forem necessárias até que uma resposta de sucesso seja encontrada. Note que semânticas do tipo "exatamente-uma vez" são bem mais sofisticadas de um ponto de vista arquitetural, devido ao fato de não haver uma solução trivial para garantir exatamente um único processamento.
Assim, concluímos nossos breves experimentos acerca do sistema RMI, podendo agora entender um pouco melhor como seu funcionamento ocorre na prática e quais adversidades podem trazer problemas para nossos planejamentos.