Você está na página 1de 9

Tolerância a Falha em Sistemas Distribuídos

Tolerância a Falha
Uma característida de um sistema distribuído, que os diferencia dos sistemas simples, é a noção de
falha parcial. Uma falha parcial pode acontecer, em um sistema distribuído, quando um de seus
componentes falha. Este tipo de falha poderá afetar uma operação do sistema, mas não derrubar o
sistema inteiro. Já nos sistemas simples, não distribuídos, uma falha geralmente derruba o sistema
inteiro, ou seja afeta todos os componentes.
Um dos objetivos de um sistema distribuído é a tolerância a falhas parciais, contornando-as sem que a
performance do sistema seja seriamente afetada. Quando uma falha acontece, o sistema deverá manter-
se em funcionamento enquanto os reparos são realizados. Ou seja, eles devem tolerar falhas, e
continuar operando, mesmo na presença destas falhas, por algum tempo, até que elas sejam corrigidas.
Atomicidade é uma propriedade importante em um sistema distribuído, em uma transação múltipla, ou
seja vários dados sendo atualizados em uma única transação, devem ser atualizados todos como se
fossem uma única operação. Ou todas as operações são gravadas ao mesmo tempo, ou nada será
gravado. Para fazer isso em um sistema distribuído deve-se utilizar um protocolo de commit distribuído.
Introdução a tolerância a falha
Conceitos básicos
Aqui serão descritos os conceitos básicos da tolerância a falha em sistemas distribuídos. Para um
sistema distribuído ser tolerante a falhas, ele deverá disponibilizar os seguintes conceitos:
• Disponibilidade
• Confiabilidade
• Segurança
• Manutenção
Disponibilidade é definido por um sistema que está sempre disponível para ser utilizado imediatamente.
Este conceito se refere a probabilidade do sistema operar corretamente a qualquer hora.
Confiabilidade é definido pelo tempo em que o sistema permanece operando, sem interrupções.
Comparando confiabilidade com disponibilidade, temos que a cofiabilidade define um intervalo de tempo
e a disponibilidade trata-se de um determinado instante temporal. Isto é muito relativo, mas digamos que
um sistema que passa um milissegundo fora do ar a cada hora, dizemos que ele tem uma
disponibilidade de 99.9999% do tempo, porém ele não é confiável. Já um sistema que nunca para, mas
deve ficar fora do ar por duas semanas todo mês de Março possui uma confiabilidade bem alta, porém
ele tem uma disponibilidade de 96%.
Segurança se refere a uma situação onde acontece uma falha e nada de catastrófico acontece. Por
exemplo, o sistema que controla o piloto automático de um avião, ou o sistema que controla os trens de
um metrô são exemplos de sistemas que possuem um alto grau de segurança. Se estes sistemas
falharem temporariamente, mesmo que por alguns segundos, os efeitos poderão ser desastrosos. Estes
sistemas não são fáceis de serem construídos.
Manutenção trata-se da facilidade de um sistema se recuperar em caso de falha, um sistema com fácil
manutenção também possui uma alta disponibilidade, especialmente quando as falhas podem ser
detectadas e corrigidas automaticamente.
Uma falha em um sistema é dada quando ele não consegue cumprir o que ele foi desenvolvido para
fazer. Em um sistema com múltiplos serviços, ele irá falhar quando um ou mais serviços falham. Um erro
é um estado de um sistema que pode levar a uma falha.
A causa de um erro é denominada falha, e controlar estas falhas faz parte do desenvolvimento de um
sistema distribuído. Fazer um sistema que possa continuar operando mesmo com a existência de falhas.

As falhas são classificadas como transiente, intermitente ou permanente, falhas transientes são as falhas
que ocorrem uma vez e nunca mais repetem, mesmo quando executamos a mesma operação. Uma
falha intermitente é aquela que acontece algumas vezes, depois para de acontecer e mais para frente
volta a ocorrer sem nenhuma relação aparente. Estas falhas são bem complicadas de serem
diagnosticadas, uma vez que a causa dela não é conhecida. Uma falha permanente é aquela que
acontece e continua acontecendo até que o componente que esteja falhando é arrumado ou substituído.

Modelos de falha
Em um sistema distribuído, muitas vezes a falha não é facilmente determinada, já que muitas vezes, os
servidores destes sistemas se comunicam e em uma falha do sistema de banco de dados, pode estar
sendo causada por uma máquina de armazenamento de dados e não em si do banco de dados.
Para melhor entender as falhas em um sistema distribuído, foram desenvolvidas vários esquemas de
classificação. Abaixo mostramos uma delas

• Falha de queda: O servidor morre, porém ele estava operando normalmente antes de
morrer
• Falha de omissão: O servidor falha ao responder as requisições recebidas
o Omissão de recebimento: O servidor falha ao receber uma mensagem
o Omissão de envio: O servidor falha ao enviar mensagens
• Falha de tempo: A resposta do servidor não ocorre após um determinado período de
tempo
• Falha de resposta: O servidor envia uma resposta incorreta
o Falha de valor: O valor da resposta está errado
o Falha de estado de transição: O servidor desvia do fluxo de controle correto
• Falha arbitrária: Um servidor pode produzir respostas arbitrárias a qualquer momento
Uma falha de queda acontece quando um servidor desliga prematuramente, porém ele estava
funcionando corretamente antes da falha. Um ponto importante deste tipo de falha é que nada será
recebido daquele servidor depois que ele desligar.
A falha de omissão acontece quando o servidor falha em responder uma requisição e muitas coisas
podem causar uma falha de omissão, por exemplo em uma omissão de recebimento o servidor nunca
recebe a requisição. Neste caso mesmo que uma conexão tenha sido estabelecida entre o cliente e o
servidor, a mensagem não chega, pois um serviço que "ouça" as requisições contidas nas mensagens,
não foi estabelecido.

Um outro tipo de omissão pode acontecer quando o servidor processa a mensagem, mas falha em
enviar uma mensagem de resposta. Neste caso o servidor precisa estar preparado para receber e
reprocessar a mesma mensagem, cujo envio da resposta falhou.
Outros tipos de falha de omissão, podem estar relacionados a falha do software, como um loop infinito,
ou uma administração errônea da memória.
A falha de resposta de tempo acontece quando a resposta chega fora de um intervalo de tempo
específico. Uma resposta gerada muito cedo, pode causar um erro de memória, quando não há mais
espaço para armazenar aquela mensagem. O mais comum é a resposta de uma mensagem atrasar,
quando temos um problema de performance.

Uma das falhas mais graves é a falha de resposta, que ocorre quando um serviço retorna uma resposta
incorreta. Existem dois tipos de falha de resposta, no caso de uma falha de valor, o serviço retorna um
valor inválido, muitas vezes gerado devido a um bug na implementação do serviço, ou uma falha de
estado de transição, a qual pode ser exemplificada por uma reação inesperada para uma requisição, por
exemplo, quando um servidor recebe uma mensagem que ele não reconhece, uma falha de estado de
transição acontece caso não haja nenhum tipo de medida realizada para administrar este tipo de
mensagem.
O tipo de falha mais grave são as falhas arbitrárias, ou também chamadas de falha Bizantina, quando
este tipo de falha acontece, os clientes devem estar preparados para o pior. Nestes casos, um servidor
pode estar produzindo uma resposta que não havia sido planejada, e que não pode ser detectado como
uma falha. Ou mesmo, um servidor pode estar trabalhando em conjunto com outros servidores, afim de
produzir respostas erradas. Por isso que a segurança é um fator importante quando estamos falando de
sistemas distribuídos.

Mascaramento de falha por redundância


Se um sistema distribuído foi planejado para tolerar falhas, a melhor maneira de fazer isso é tentar
esconder os erros, quando eles acontecem em outros processos, e um ponto chave para esconder as
falhas é através da utilização de redundância. Existem três tipos de redundância disponíveis,
redundância de informação, redundância de tempo e redundância física.
Redundância de informação é feita através da adição de alguns bits nas mensagens, possibilitando
aquela mensagem de ser recuperada em caso de falha de transmissão, como ocorre no protocolo de
rede.
A redundância de tempo acontece quando uma ação é processada e depois, caso necessário, ela será
reprocessada novamente. Um exemplo deste tipo de redundância acontece nas transações, que podem
ser repetidas, sem nenhum aviso, em certos tipos de falhas.

Quando se trata de redundância física, máquinas extras são adicionadas a rede, afim de suportar a falha
de parte das máquinas do sistema, ou mesmo suportar um aumento pontual de requisições em um
serviço específico.

Resiliência de Processo
Agora que vimos o básico da tolerância a falha, vamos explicar em como implementar sistemas com esta
característica

Problemas de Design
Um dos principais pontos em um sistema tolerante a falha é organizar os processos em vários grupos. O
ponto chave deste sistema é quando uma mensagem é enviada para um grupo, todos os membros
daquele grupo devem receber aquela mensagem. Com isso caso algum elemento do grupo falhe, um
outro membro poderá cuidar daquela mensagem.
A proposta de introduzir grupos é fazer com que eles sejam classificados com uma abstração simples.
Um processo pode ter que enviar uma mensagem para um grupo de servidores sem saber quem e
quantos são estes servidores e isto pode se modificar de uma chamada a outra.

Grupos horizontais vs grupos hierárquicos


Uma diferença importante entre grupos tem haver com a organização da sua estrutura interna. Em
alguns deles todos os processos são iguais, ninguém é o chefe e todas as decisões são tomadas
coletivamente, estes grupos são conhecidos como:
Falha de queda: O servidor morre, porém ele estava operando normalmente antes de morrer
Falha de omissão: O servidor falha ao responder as requisições recebidas
Omissão de recebimento: O servidor falha ao receber uma mensagem
Omissão de envio: O servidor falha ao enviar mensagens

Falha de tempo: A resposta do servidor não ocorre após um determinado período de tempo
Falha de resposta: O servidor envia uma resposta incorreta
Falha de valor: O valor da resposta está errado
Falha de estado de transição: O servidor desvia do fluxo de controle correto
Falha arbitrária: omo grupos horizontais. Já outros grupos possuem uma hierarquia, por exemplo um dos
processos é o coordenador e todos os outros são trabalhadores. Neste caso qualquer requisição, seja
ela realizada por um trabalhador interno, ou um processo externo, será direcionada ao coordenador, e
ele é que decidirá qual processo será responsável por processar aquela requisição.
Cada uma destas organizações tem suas vantagens e desvantagens, os grupos horizontais não
possuem um único ponto de falha, além de ele ser simétrico e no caso de um processo falhar, o grupo
ficará menor, mas continuará o seu funcionamento. Porém a tomada de decisão é mais complicada, já
que uma votação deverá ser realizada para a tomada de decisão.
O grupo hierárquico tem as propriedades opostas, caso o coordenador falhe, todo o grupo para de
funcionar, porém enquanto o coordenador esta rodando, as decisões serão tomadas sem consultar
ninguém mais.

Membro do grupo
Para estabelecer um grupo, primeiro devemos ter um método que administra os membros deste grupo,
tanto para adicionar, quanto para remover os membros deste grupo. Pode-se criar um servidor do grupo
que pode manter os dados de todos os membros do grupo, assim como estes métodos de alteração do
grupo, porém isto nos leva a um único ponto de falha.

Uma outra possibilidade é administrar os membros do grupo de uma maneira distribuída, por exemplo se
tivermos um serviço de multicasting confiável, um processo externo poderá enviar uma mensagem para
todos os membros do grupo.

Tanto a entrada, quanto a saída do grupo deverá ser síncrona, ou seja, quando um processo se junta ao
grupo ele deverá receber todas as mensagens enviadas, e quando um processo deixa o grupo ele não
poderá mais receber mensagens daquele grupo.

Quando muitas máquinas, ou processos do grupo desligam e o grupo não consegue funcionar da
maneira correta, algum protocolo de reconstrução do grupo será necessário. Porém e se dois ou três
processos de reconstrução forem inicializados ao mesmo tempo, este protocolo deverá tratar este tipo de
problema.

Mascaramento de falha e replicação


Os grupos de processos são uma das formas de se construir um sistema tolerante a falha. A construção
de um grupo de processos idênticos habilita o nosso sistema a mascarar uma ou mais falhas de
processamento daquele grupo. A replicação de um único processo, que é vulnerável, por um grupo de
processos, tolerante a falha. Como fazer esta replicação foi explicado no post anterior de consistência e
replicação.

Um ponto importante na utilização de grupos para tolerar falhas em um sistema distribuído é a


quantidade de replicação necessária. Um sistema é denominado X tolerante a falha, caso ele suporte
falha em X componentes e mesmo assim continue funcionando corretamente. Quando os processos
deste sistema falham silenciosamente, então termos X +1 componente é suficiente para prover um
sistema com X de tolerância a falha.

Entretanto caso os processos deste sistema apresentem falhas Bizantinas e continuam enviando
mensagens, mesmo quando eles estão falhando, o mínimo de 2X+1 de componentes é necessário para
atingir um sistema com X de tolerância a falha.
Acordo em sistemas falhos
Organizar processos replicados em grupos ajudam a aumentar a tolerância a falha. Como explicado
acima, se um cliente baseia suas decisões em um sistema de voto, podemos tolerar que X processos de
um número 2x+1 processos apresentem falhas, que mesmo assim o sistema continuará funcionando
corretamente.
Geralmente os problemas tornam-se mais intrínsecos quando o grupo de processos necessita cumprir
um acordo, ou realizar uma função, por exemplo decidir se realiza um commit, ou não, de uma
transação, eleger um coordenador, divisão de tarefas entre grupos, sincronização, etc
Quando o sistema está funcionando corretamente, é fácil atingir estes objetivos, porém quando um ou
mais componentes falham, ai as coisas ficam complicadas.
O objetivo dos algorítimos distribuídos de acordo é fazer com que todos os componentes, que não
estejam falhando, cheguem a um consenso. Porém este é um problema muito complicado já que
diferentes soluções podem ser adotadas, isso quando elas existem. Estas soluções podem ser divididas
em:
1. Sistemas síncronos versus sistemas assíncronos. Um sistema pode ser considerado
síncrono quando existe um relógio controlando os processos deste sistema, e a cada passo
deste relógio, todos os processos realizam uma operação. Um sistema que não é síncrono é
conhecido como assíncrono
2. O tempo que leva a comunicação é limitado ou não. O tempo da comunicação é limitado
quando há uma garantia que todas as mensagens do sistema são entregues com um tempo
máximo global.
3. Entrega de mensagens é ordenada ou não. Nestes sistemas as mensagens são
entregues na mesma ordem em que elas foram enviadas, ou nos casos em que não há esta
garantia.
4. Transmissão das mensagens é feita através de unicasting ou multicasting.
Um tipo de algoritmo de acordo é conhecido como Problema de acordo Bizantino, nome que lhe foi dado
devido a história das inúmeras guerras que aconteceram no império Bizantino e dos respectivos acordos
que eram feitos entre os exércitos nestas guerras.
Este algoritmo funciona em um conjunto de processos, como exemplo usaremos um conjunto de 4
processos, cada um dos processos envia uma mensagem de resposta de processamento para os outros
processos que estão dentro do conjunto de processos.

Cada um dos processos irá armazenar os valores recebidos em um vetor, o qual será enviado a todos os
outros processos deste conjunto. Cada processo irá receber três vetores dos outros processos do
conjunto. Caso um processo esteja falhando, ele pode enviar mensagens aleatórias aos outros
processos do conjunto, e ao final, somando-se os elementos de cada vetor, quando houver maioria
aquele valor será considerado correto. Caso nenhum dos valores tenha maioria de presença nos vetores
o resultado será marcado como Desconhecido.

Este protocolo deve ser utilizado em conjuntos com mais de 4 processos, já que neste caso cada
processo receberá três respostas, caso haja somente 3 processos no conjunto, ao receber apenas 2
respostas, estando os valores delas diferentes, fica impossível detectar qual é o valor correto.

Este protocolo não funciona corretamente em casos de sistemas onde as mensagens não possuem
garantia de entrega. Já que isso gera um grande problema para detectar-se se a mensagem falhou, ou
se o processo está demorando para executar a mensagem,

Deteção de falha
A detecção de uma falha é um ponto muito importante para podermos mascarar o acontecimento dela.
Em um grupo de processos, eles devem possuir a habilidade de detectar quais processos ainda fazem
parte daquele grupo e isto é realizado através da monitorização das falhas.
Existem duas maneiras de se detectar a falha de um processo, na primeira delas o processo ativo envia
uma mensagem de "Você ainda está vivo" para os outros processos, e através da resposta a esta
mensagem é possível determinarmos quem ainda está vivo ou não. Ou ficar aguardando passivamente
mensagens dos outros processos. Este segundo caso faz sentido apenas quando há muita comunicação
entre os processos.
Um dos pontos cruciais de se detectar uma falha, é estabelecer um mecanismo de timeout. Existem dois
grandes problemas de se detectar uma falha através de timeout, primeiro devido a problemas de
comunicação entre redes, nem sempre quando uma resposta não chega, é sinal que ela não foi dada, ou
seja a geração de falsos positivos é bem alta. E caso este falso positivo seja responsável por retirar um
processo saudável, da lista de processos disponíveis, fica claro que existe algo de errado.
Um outro problema é que os timeouts são muito básicos, é difícil confiar em um sistema de detecção de
falha que considere um processo falho, quando ele apenas deixa de responder uma mensagem.
Existem muitos fatores que devem ser levados em conta, quando estamos desenvolvendo um sistema
de deteção de falha. Pode-se considerar a utilização do protocolo de fofoca, onde cada processo do
sistema envia regularmente mensagens de, estou vivo e rodando, para seus vizinhos.
A utilização do protocolo de fofoca, para envio de mensagens de estou vivo irá eventualmente atingir
todos os pontos do sistema, e cada um deles terá um controle de quais processos estão vivos, ou não.
Um membro, o qual esta informação for muito antiga, será presumidamente considerado como falho.
Uma funcionalidade importante destes sistemas é conseguir diferenciar uma falha de comunicação, da
rede, de uma falha de processamento do nó e uma as formas de resolver este problema é não deixar
que apenas um processo decida se um outro processo esta falhando ou não. Neste caso ao notar que
um processo não está mais respondendo aquele nó deve perguntar aos seus vizinhos se eles
conseguem se comunicar com processo presumidamente falho.

Comunicação confiável entre cliente e servidor


Muitos casos de tolerância a falha em sistemas distribuídos se concentram nos processos falhos, porém
também precisamos considerar as falhas de comunicação. O canal de comunicação pode apresentar
falhas de queda, omissão, tempo e falhas arbitrárias. Para construir um canal de comunicação confiável
o foco é omitir falhas de colisão e omissão.

Comunicação ponto a ponto


A comunicação ponto a ponto confiável é estabelecida através de protocolos de transporte confiáveis,
como o TCP, que omite falhas de omissão, que ocorrem por causa das mensagens perdidas, pelo uso
de confirmações e retransmissões.
Falhas de colisão não podem ser mascaradas. Estas falhas acontecem quando uma conexão TCP
quebra abruptamente e nenhuma mensagem podem ser transmitidas através do canal. Na maioria das
vezes o cliente é informado que existe uma colisão no canal através do disparo de uma exceção. A única
forma de mascarar este tipo de falha é deixar o sistema tentar se reconectar automaticamente, o que
pode ser feito através do reenvio de uma requisição de conexão.

Semântica RPC na presença de falhas


Nesta seção vamos analisar a comunicação cliente-servidor quando elas utilizam um tipo de
comunicação como o RPC. O objetivo de uma RPC é esconder a operação de comunicação fazendo
com que as chamadas RPC sejam realizadas como se fossem chamadas locais. E isso funciona muito
bem enquanto o cliente e o servidor funcionem corretamente. O problema acontece quando os erros
acontecem, nestes casos fica difícil mascarar os problemas como se eles fossem chamadas locais.
Vamos dividir as falhas RPCs em 5 diferentes classes de erros

1. O cliente não consegue localizar o servidor


2. A mensagem é perdida entre o cliente e o servidor
3. O servidor quebra após receber a requisição
4. A mensagem de resposta do servidor ao cliente é perdida
5. O cliente quebra após enviar a requisição
A resolução de cada um destes problemas deverá ser realizada de uma maneira diferente
O cliente não consegue localizar o servidor
Este erro pode acontecer quando, por exemplo, todos os servidores estão desligados, quando o cliente
está esperando uma certa versão dos serviços que já não está mais disponível. Neste caso ao tentar se
conectar com este servidor uma falha acontecerá. Este tipo de erro é usado para proteger o cliente de
tentar usar uma versão errada do serviço, que contenha parâmetros diferentes dos quais o cliente está
preparado para lidar. O grande problema é como atuar quando este tipo de erro acontece.
Uma solução possível é fazer com que este erro dispare uma exceção ou sinais. Esta solução não pode
ser utilizada em todas as linguagens, já que em algumas delas não é possível disparar uma exceção ou
um sinal. Um outro problema é que ter que escrever exceções para este tipo de erro vai contra o objetivo
de ser transparente quanto a execução remota deste tipo de chamada.
Mensagem de requisição perdida
Dos tipos de erro de RPC este é o mais fácil de ser tratado, basta iniciar um contador, assim que a
mensagem é enviada. Se o tempo do contador ultrapassar um limite estabelecido antes que uma
resposta chegue, a mensagem será reenviada. Caso a mensagem tenha sido realmente perdida o
servidor não vai conseguir identificar nenhuma diferença entre a mensagem original e a cópia e tudo
funcionará corretamente. A não ser que muitas mensagens sejam perdidas e o cliente desiste achando
que o servidor quebrou, o que nos leva a um erro de servidor não encontrado.

Servidor quebrou
A sequência normal de eventos é a mensagem chega, é processada e uma resposta é enviada. Existem
dois caminhos possíveis de falha quando o servidor quebra, na primeira delas, a mensagem é recebida o
servidor processa ela e logo após ele quebra, não enviando nenhuma resposta. Na segunda, o servidor
recebe a mensagem e quebra, antes mesmo de processar a mensagem.
A parte mais complicada de se tratar este tipo de erro é que atitudes diferentes devem ser tomadas para
solucionar estes dois tipos de problema. No primeiro caso o sistema deve enviar um relatório de erro de
volta ao cliente, já no segundo uma simples retransmissão resolve o problema.
Existem três tipos de filosofias para estes problemas, na primeira os processos ficam esperando o
servidor ser reinicializado e tentam realizar a operação novamente. A mensagem será retransmitida até
que o servidor responda. Esta técnica é chamada de pelo menos uma vez, já que neste caso há uma
garantia que o RPC foi executado pelo menos uma vez, porém possivelmente mais de uma.
No segundo tipo de filosofia, ele desiste de executar a requisição e emite uma mensagem de erro. Esta
solução é chamada de no máximo uma vez, já que só uma requisição será enviada.
A terceira filosofia não garante nada, quando um servidor quebra, os clientes não recebem nenhuma
ajuda ou pista sobre o que aconteceu. A chamada de RPC pode ter sido processada nenhuma ou muitas
vezes.
Nenhuma destas três filosofias é muito atrativa. Imagine uma operação remota para imprimir algum texto,
e que o servidor de impressão envie uma mensagem de confirmação para o cliente, quando o texto é
impresso. Neste caso, quando o servidor recebe uma instrução de impressão ele envia uma mensagem
de confirmação de recebimento da instrução para o cliente. Existem duas estratégias que o servidor
pode seguir, ele pode enviar uma mensagem de que o texto foi impresso antes de mandar a instrução de
impressão para a impressora, ou após o texto ter sido impresso.
Imagine que o servidor pare de funcionar e logo depois se recupere. Ele envia uma mensagem para os
clientes dizendo que o servidor caiu e voltou. O grande problema é que os clientes não sabem se a sua
requisição de impressão foi recebida ou não.
O cliente pode seguir quatro estratégias. Primeiro o cliente pode decidir não reenviar a requisição,
assumindo o risco do texto nunca ser impresso. Segundo o cliente pode decidir sempre reenviar a
requisição de impressão, correndo o risco de o texto ser impresso duas vezes. Terceira ele pode decidir
reenviar a requisição somente se ele não receber a mensagem de confirmação de recebimento da
instrução de impressão pelo servidor, neste caso o cliente estão levando em conta que o servidor caiu
antes que a mensagem da instrução de impressão tenha sido recebida. A quarta estratégia é reenviar a
requisição somente quando ele receba uma mensagem de confirmação de recebimento da mensagem
de solicitação de impressão pelo servidor.
Contando com duas estratégias para os servidores e quatro para os clientes, temos oito possíveis
caminhos, mas nenhum deles será satisfatório.
Resumindo a possibilidade do servidor cair, muda radicalmente a natureza de uma chamada RPC e
claramente a distingue de um sistema simples.

Perda de mensagens de resposta


A perda de mensagens de resposta, também pode ser difícil de lidar. A solução mais óbvia é configurar
um temporizador e caso nenhuma mensagem de resposta chegue após este temporizador estourar um
tempo limite, configurado no sistema operacional, ele pode reenviar esta mensagem. O problema desta
solução é que é impossível saber se a mensagem se perdeu, ou se o servidor está lento.
Alguns tipos de operação, como as mensagens de busca em banco de dados, podem ser repetidas
infinitamente, sem causar qualquer problema de consistência. Este tipo de requisição é considerada
idempotente.

Porém uma mensagem de atualização, como uma transferência de dinheiro entre duas contas bancárias.
Se esta requisição for processada, porém a mensagem de resposta se perder, o cliente não saberá e vai
retransmitir a mensagem para o servidor. O servidor do banco irá interpretar isso como uma nova
requisição e realizará a transferência duas vezes.

Uma maneira de se tentar resolver este problema é tentar estruturar todas as requisições de uma
maneira idempotente. Porém nem sempre isto é possível, este caso de transferência bancária é um
exemplo clássico, por isso precisamos de um outro método para resolver este problema. A segunda
forma de resolvermos este problema é inserir na mensagem um número sequencial e fazendo com que o
servidor cuide deste número sequencial de cada cliente. Com isso é possível determinar se uma
mensagem já foi processada ou se ela esta sendo retransmitida.

Neste caso o servidor deverá administrar cada cliente e também não fica claro por quanto tempo
deveremos manter estes dados armazenados.

A terceira forma de se resolver este problema é manter um bit no cabeçalho da mensagem marcando-a
como retransmitida.

Queda do cliente
O item final a ser analisado nesta lista de falhas acontece quando o cliente envia uma requisição ao
servidor e antes que o servidor responda, o cliente sofre uma pane e desliga. Com isso temos uma
requisição sendo processada no servidor, sem ter nenhum cliente aguardando a sua resposta. O que
resulta em um processo órfão.

Estes processos órfãos podem causar uma série de problemas na operação normal do sistema. Desde
um desperdício de ciclos de processamento, um travamento de arquivos ou ocupar recursos vitais do
servidor. Finalmente se o cliente reiniciar e a requisição for realizada novamente mas a resposta do órfão
chega logo após este reinicio, neste caso podemos ter uma confusão.

Existem quatro maneiras de se tratar o problema dos processos órfãos. Na primeira delas, antes de
realizar uma chamada RPC o cliente armazena dados dela em um log que sobreviva as interrupções da
máquina. Ao reiniciar este log será checado e os órfãos serão exterminados.

A desvantagem deste esquema é a escrita no log para cada mensagem RPC enviada. Além de, em
alguns casos, este extermínio não funcionará, já que os processos órfãos também podem criar outros
processos, que seriam impossíveis de serem detectados. Finalmente se houver um problema de rede
será impossível matar estes processos órfãos.

Na segunda solução, chamada de reincarnação, todos estes problemas poderão ser resolvidos, sem ter
que escrever no disco. Ela funciona dividindo o tempo em épocas e a cada reinício do cliente, ele enviará
uma mensagem para todas as máquinas declarando o início de uma nova época. Quando este tipo de
mensagem chega em um servidor, todos os processos referentes a épocas passadas são exterminados.
Neste caso, havendo um problema de rede alguns processos podem não ser exterminados, mas as
respostas destes processos carregam um número de uma época inválida e elas serão descartadas.

A terceira solução é uma variação da segunda, também chamada de reincarnação gentil. Quando uma
mensagem de época chega, ela tentará localizar todos os processos remotos que estão rodando
localmente, e quando acha, ela faz o possível para achar os seus donos. Somente quando os donos não
podem ser encontrados é que os processos serão eliminados.

Para finalizar, a quarta solução é chamada de expiração, onde para cada chamada RPC será informado
um tempo de vida T para se completar o processamento. Se o processo não conseguir terminar no
tempo estimado, ele poderá pedir uma prorrogação deste tempo de vida. Do outro lado, o cliente
esperará um tempo T antes de reiniciar o serviço, desta forma é possível saber que todos os órfãos
foram eliminados. O problema aqui é encontrar um tempo T correto em face dos diversos tipos de RPC
existentes.

Na prática todos estes quatro métodos são indesejáveis, já que eliminar um órfão pode ter graves
consequencias, uma vez que eles podem ter obtido travas nos arquivos de dados do sistema e ao serem
eliminados estas travas podem permanecer ativas, gerando uma trava infinita.

Você também pode gostar