Pesquisar este blog

terça-feira, 13 de maio de 2014

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
    • 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: 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.

Apresentação


2 comentários:

  1. Meus filhos gostam muito de celular mas fico com medo sobre o eles vão acessar no celular por esse motivo no celular dele tem um programa que me permite ver tudo que é feito celular assim consigo impor limite e protege-los, é muito bom recomendo. https://brunoespiao.com.br/espiao-de-contatos

    ResponderExcluir
  2. Excelente post professor! Posso adicionar ele como umas das referências no meu https://codigo35.com/2022/05/31/falhas-em-sistemas-distribuidos/ ?


    Obrigado!

    ResponderExcluir