Serviços para Tolerância a Faltas no Ambiente Operacional Seljuk

Propaganda
Serviços para Tolerância a Faltas no
Ambiente Operacional Seljuk-Amoeba
Sheila Regine A. Vasconcelos
[email protected]
Francisco Vilar Brasileiro
[email protected]
Universidade Federal da Paraíba - UFPB/Campus II
Centro de Ciências e Tecnologia - CCT
Departamento de Sistemas e Computação - DSC
Laboratório de Sistemas Distribuídos - LSD
Av. Aprígio Veloso, 882
58109-970, Campina Grande, Paraíba
http://www.dsc.ufpb.br/~lsd
RESUMO
A obtenção de confiança no funcionamento em aplicações distribuídas depende da
utilização de mecanismos apropriados para tolerância a faltas. A arquitetura Seljuk se
propõe a fornecer um ambiente propício ao desenvolvimento e à execução de
aplicações distribuídas robustas. Os principais serviços para tolerância a faltas usados
por aplicações distribuídas robustas são discutidos, e a forma como estes serviços são
fornecidos pelo ambiente operacional Seljuk-Amoeba é descrita.
ABSTRACT
Fault tolerance mechanisms must be introduced into systems if a dependable
behaviour is to be attained. The Seljuk architecture provides a number of services to
facilitate the implementation and support the execution of dependable distributed
applications. This paper discusses the main fault tolerance services used by
dependable distributed applications and how these services are provided by the
Seljuk-Amoeba operating environment.
1.
INTRODUÇÃO
A sociedade moderna está cada vez mais dependente dos sistemas de computação para a
realização de tarefas no seu dia-a-dia. Muitos destes sistemas fornecem a base para serviços
críticos, como transações financeiras, dispositivos de segurança, controle de tráfego aéreo,
monitoração de pacientes, entre outros, cuja falha pode levar a uma catástrofe com graves
conseqüências como a perda de dinheiro ou, o que é pior, de vidas humanas.
Este crescimento da dependência da sociedade no correto funcionamento dos sistemas
computacionais, ao mesmo tempo que aumenta as oportunidades para os desenvolvedores de tais
sistemas, aumenta também a responsabilidade desta comunidade de profissionais no sentido de
encontrar maneiras cada vez mais eficientes de atender às expectativas da sociedade. Para
conseguir isto, é necessário lançar mão de técnicas especiais que viabilizem a construção de
sistemas com alto nível de confiança no funcionamento1. Neste cenário, despontam as técnicas
1
Confiança no funcionamento é o termo sugerido por [Lemos-Veríssimo 91] como tradução para o
termo em inglês dependability introduzido em [Laprie 89].
para tolerância a faltas2, cuja meta é possibilitar um sistema a prover o serviço adequado mesmo
na presença de um certo número de faltas; ou seja, tais técnicas procuram evitar a falha do
sistema como um todo apesar da ocorrência de falhas em alguns dos seus componentes.
Diversos mecanismos vêm sendo desenvolvidos para garantir tolerância a faltas sob uma
variedade de ambientes. Porém, na grande maioria dos casos, a implementação destes
mecanismos fica a cargo da aplicação, com pouco ou nenhum suporte do sistema operacional, o
que aumenta consideravelmente a complexidade do desenvolvimento de tais aplicações. De fato,
um dos principais problemas encontrados no desenvolvimento de aplicações distribuídas robustas
(aquelas com requisitos consideráveis de confiança no funcionamento) reside justamente na
dificuldade introduzida pela necessidade de se tolerar e tratar faltas [Cristian 91].
Uma possível maneira de reduzir esta complexidade é disponibilizar os mecanismos para
tolerância a faltas sob a forma de serviços, que poderiam ser utilizados diretamente pela aplicação
sem que esta tivesse que se preocupar com sua implementação [Mullender et al. 90, Ng 90,
Huang-Kintala 93].
Dentre os diversos serviços para tolerância a faltas, destacam-se: replicação do
processamento, comunicação em grupo, diagnóstico de faltas e reconfiguração do sistema. Tais
serviços, por sua vez, são mais facilmente implementados se for possível impor alguma restrição à
semântica de falha dos componentes do sistema que formam a infra-estrutura de processamento.
A arquitetura Seljuk [Brasileiro 97] define um ambiente operacional para desenvolvimento e
execução de aplicações distribuídas tolerantes a faltas, que se propõe a reduzir a complexidade do
desenvolvimento destas aplicações, atacando as duas vertentes acima colocadas: ele tanto provê
mecanismos que possibilitam restringir a semântica de falha da infra-estrutura de execução das
aplicações quanto disponibiliza serviços de apoio à construção de aplicações robustas.
Além disso, o Seljuk se apresenta como uma arquitetura flexível que oferece ao usuário a
opção de obter níveis especificáveis de confiança no funcionamento na base de “serviço por
serviço”, considerando o fato que muitas aplicações não requerem alta confiabilidade e
disponibilidade de todos os seus serviços e nem sempre podem admitir o custo envolvido na
utilização de mecanismos especiais para tolerância a faltas.
Este artigo descreve uma proposta de como os serviços básicos para tolerância a faltas
podem ser oferecidos à aplicação pela arquitetura Seljuk, com o máximo de transparência
possível, permitindo que os desenvolvedores de sistemas se libertem da obrigação de construir,
eles próprios, os mecanismos para tolerância a faltas e possam voltar seus esforços para a
implementação do real serviço que o sistema deve fornecer, assim facilitando e minimizando o seu
trabalho. Os serviços são especificados no contexto do ambiente operacional Seljuk-Amoeba, que
é uma instanciação da arquitetura Seljuk baseada no micro-núcleo distribuído Amoeba [Mullender
et al. 90].
O restante deste texto está organizado da seguinte forma. Na Seção 2, visando tornar o
artigo auto-contido, fazemos uma breve apresentação da arquitetura Seljuk, enfatizando os seus
objetivos e a sua estrutura. A Seção 3 discute aspectos de tolerância a faltas em sistemas
distribuído, identificando os principais serviços comumente usados na contrução e execução de
aplicações distribuídas robustas. A proposta para implementação de tais serviços no ambiente
Seljuk-Amoeba é discutida na Seção 4. Por fim, a Seção 5 traz as nossas conclusões.
2.
2
A ARQUITETURA SELJUK
De acordo com a terminologia proposta por [Lemos-Veríssimo 91], os termos falta, erro e falha
traduzem, respectivamente, os termos em inglês fault, error e failure.
2.1.
Objetivos
A arquitetura Seljuk tem como objetivo principal prover uma plataforma de
desenvolvimento que facilite a construção de aplicações distribuídas robustas através da
disponibilização de duas classes de serviços:
i) serviços que permitem restringir a semântica de falha dos componentes que
formam a infra-estrutura de processamento sobre a qual a aplicação irá executar;
e
ii) serviços que implementam, ou auxiliam a implementação, dos principais
mecanismos para tolerância a faltas.
A primeira classe de serviços é responsável pela disponibilização de serviços de
processamento com diferentes semânticas de falha, que possam satisfazer a suposição estabelecida
pela aplicação sobre o modo de falha dos componentes do sistema distribuído. A definição da
semântica de falha assumida é feita pela aplicação no instante de sua ativação. A plataforma fica
então responsável por prover os mecanismos necessários para garantir tal semântica, considerando
obviamente as limitações do hardware disponível (processadores e canais de comunicação) e a
semântica de falha real deste hardware (que também é definida pela aplicação). Tais serviços são
discutidos detalhadamente em [Brasileiro 97] e [Gallindo-Brasileiro 97].
A segunda classe, por sua vez, inclui os serviços de mais alto nível que provêem o suporte
básico para a construção de aplicações distribuídas robustas, quais sejam: replicação do
processamento, comunicação em grupo, diagnóstico de faltas e reconfiguração do sistema. A
implementação destes serviços deve levar em conta a semântica de falha assumida pela aplicação,
requisitando ao próprio ambiente operacional do Seljuk os serviços de processamento que
garantam tal semântica. Neste caso, tal requisição é feita no instante da ativação do serviço
solicitado pela aplicação.
2.2.
Estrutura
A arquitetura Seljuk segue um modelo organizado em camadas, conforme ilustra a Figura
1.
Aplicações
Middleware
Sistema Operacional Distribuído
Processadores e Canais de Comunicação
Figura 1: Estrutura do Ambiente Operacional Seljuk
A camada mais baixa da arquitetura, denominada “Processadores e Canais de
Comunicação”, abriga os componentes de hardware que fornecem os serviços básicos de
processamento e comunicação. A camada seguinte é constituída pelo “Sistema Operacional
Distribuído”, responsável por controlar e gerenciar os diversos componentes do sistema,
considerando principalmente a distribuição de tarefas pelos vários processadores e as necessidades
de comunicação decorrentes disto. É nesta camada onde estão implementados os serviços que
garantem a semântica de falha requerida pela aplicação e alguns dos serviços necessários para a
implementação dos mecanismos para tolerância a faltas.
A camada intermediária, denominada “Middleware”, implementa os demais serviços para
tolerância a faltas, tirando proveito dos serviços fornecidos pelas camadas inferiores. Finalmente, a
camada superior agrupa os componentes que implementam o serviço oferecido pela aplicação
propriamente dita.
3.
TOLERÂNCIA A FALTAS EM SISTEMAS DISTRIBUÍDOS
Tolerância a faltas em sistemas distribuídos é realizada ao longo de várias fases relacionadas
tanto ao processamento do erro quanto ao tratamento da falta. Enquanto o processamento do erro
tem por objetivo a remoção de erros do estado computacional, se possível antes da ocorrência de
uma falha no serviço entregue pelo sistema, o tratamento da falta tenta prevenir que as faltas que
geraram tais erros venham a ser ativadas novamente [Lee-Anderson 90].
3.1.
Processamento do Erro
A replicação de componentes de software individuais em unidades de processamento
distintas de um sistema distribuído provê a redundância que é necessária para o processamento do
erro. Neste contexto, o processamento do erro envolve o gerenciamento das interações entre as
várias réplicas do componente de software para detecção, recuperação ou compensação do erro a
fim de mascarar o fato de que um ou mais componentes do sistema possam ter falhado. Três
modelos básicos de computação replicada estão disponíveis:
a) Modelo de Réplicas Ativas. Neste modelo, todas as réplicas processam
concorrentemente e na mesma ordem todas as mensagens de entrada de modo
que seus estados são sincronizados e, na ausência de faltas, todas elas produzem
as mesmas mensagens de saída e na mesma ordem. Isto requer que as réplicas
apresentem comportamento determinístico na ausência de faltas. A técnica de
replicação ativa pode ser usada para tolerar faltas do serviço de processamento ou
do próprio componente de software3 sob suposições tanto de falha silenciosa
quanto de falha arbitrária (Bizantina). A fim de tolerar falhas arbitrárias, as saídas
de todas as réplicas são comparadas e a decisão majoritária é usada. Se, por outro
lado, os componentes do sistema possuem semântica de falha silenciosa, a
replicação ativa pode ser usada sem votação, uma vez que toda mensagem
produzida por qualquer uma das réplicas pode ser assumida como correta. Como
resultado, os requisitos do sistema de comunicação são simplificados e melhor
desempenho é alcançado, já que os resultados podem ser propagados
imediatamente após terem sido gerados em vez de ficarem pendentes esperando o
processo de votação.
b) Modelo de Réplicas Passivas. Nesta abordagem, uma das réplicas (a réplica
primária) processa as mensagens de entrada e provê as mensagens de saída. Os
estados internos das demais réplicas (as réplicas backup) são regularmente
atualizados por meio de checkpoints da réplica primária. Se a réplica primária (ou
a unidade de processamento na qual ela executa) falha, uma das réplicas backups
é ativada e começa a executar a partir de seu checkpoint mais recente. Para
muitas aplicações, a principal vantagem desta técnica é que ela não requer que as
réplicas sejam determinísticas. Além do mais, os requisitos de processamento são
minimizados: o processamento de checkpoints geralmente requer menos recursos
3
Desde que métodos para tolerância a faltas de projeto, tais como blocos de recuperação [Randell 75]
ou programação com N-versões [Avizienis 85], tenham sido usados na implementação das diferentes
réplicas do componente de software.
do que a execução replicada. Como a técnica de replicação passiva não possui
qualquer mecanismo para validar as mensagens de saída da réplica primária, esta
abordagem assume semântica de falha silenciosa tanto para o componente de
software replicado quanto para a infra-estrutura de processamento.
c) Modelo de Réplicas Semi-ativas. Esta técnica pode ser vista como um híbrido das
duas técnicas anteriores. Apenas uma das réplicas (a réplica líder) processa as
mensagens de entrada e provê as mensagens de saída. Os estados internos das
demais réplicas (as réplicas seguidoras) são atualizados por processamento direto
das mensagens de entrada ou por processamento das mensagens de notificação
enviadas pela líder. As decisões sobre operações que afetam o determinismo da
réplica e, possivelmente, sobre a ordem de processamento das mensagens de
entrada são tomadas pela líder e comunicadas às seguidoras através de mensagens
de notificação. A suposição de falha silenciosa, assumida por este modelo, garante
que as mensagens propagadas pela líder são sempre corretas. Em caso de falha da
réplica líder, um algoritmo de eleição é executado para escolher uma nova líder
entre as secundárias.
A Tabela 1 apresenta os parâmetros a serem considerados no momento da escolha entre
uma das técnicas apresentadas acima [Powell 92].
Técnica de
replicação
Overhead do
processamento de erro
Não-determinismo
da réplica
Comportamento em
falha arbitrária
Ativa
Mais baixo
Proibido
Tolerado
Passiva
Mais alto
Permitido
Proibido
Semi-ativa
Baixo
Resolvido
Proibido
Tabela 1. Principais Características das Técnicas de Replicação
As técnicas de replicação descritas acima possuem diferentes requisitos quanto à
comunicação entre as diversas réplicas do componente de software. Tais requisitos são satisfeitos
por propriedades específicas do serviço de comunicação. Uma destas propriedades é a
confiabilidade, que garante que a mensagem enviada para o grupo de réplicas é recebida por todos
os destinos operacionais. Além do aspecto de confiabilidade, uma outra característica importante
dos serviços de comunicação diz respeito à ordem de entrega das mensagens. Há basicamente
quatro formas de ordenação de mensagens [Powell 92]:
Nenhuma ordenação: a entrega de mensagens não obedece qualquer tipo de
ordenação.
Ordenação fifo (first-in-first-out): todas as mensagens enviadas por um transmissor
são entregues a todos os destinos operacionais na ordem em que foram enviadas;
nada se pode afirmar sobre a ordem das mensagens enviadas por transmissores
distintos.
Ordenação causal: mensagens não concorrentes [Lamport 78] são entregues a todos
os destinos operacionais obedecendo suas relações de causalidade; se as mensagens
são concorrentes, a ordem de entrega é indefinida.
Ordenação total: todos os destinos operacionais recebem todas as mensagens na
mesma ordem.
O modelo de replicação ativa necessita de um serviço de comunicação que ofereça as
propriedades de unanimidade4 e ordenação total, isto é, todas as réplicas corretas recebem as
mesmas mensagens e na mesma ordem. Os serviços de comunicação em grupo [ChangMaxemchuk 84, Cristian et al. 85, Birman-Joseph 87, Brasileiro-Ezhichelvan 95] são normalmente
usados para esse fim.
Já no caso das outras duas técnicas de replicação, a passiva e a semi-ativa, há uma réplica
privilegiada que pode realizar as operações de ordenação e instruir as demais réplicas. Neste caso,
um protocolo de comunicação mais simples pode ser usado, provendo apenas unanimidade e
nenhuma ordenação ou, no máximo, ordenação fifo.
3.2.
Tratamento da Falta
A replicação de componentes de software é a abordagem utilizada para processar os erros
gerados por faltas no sistema. Se o erro é causado por uma falta temporária, a simples
reinicialização do sistema a partir de um estado válido eliminará completamente o erro, visto que a
falta não mais existe. Porém, se a falta é permanente, o erro reincidirá, já que a falta que o gerou
permanece no sistema. A fim de evitar isto, o componente apresentando a falta deve ser
identificado e não mais utilizado na computação subseqüente à recuperação do erro. O tratamento
da falta, portanto, pode ser visto como a facilidade de autoconserto que identifica e desativa os
componentes em falta e, dependendo da disponibilidade de recursos, cria novas réplicas,
permitindo assim que o nível de tolerância a faltas seja mantido.
O tratamento da falta envolve: diagnóstico da falta (para determinar a causa dos erros
observados), apassivação da falta (para prevenir que faltas diagnosticadas sejam ativadas
novamente) e, se possível, reconfiguração do sistema (para restaurar o nível de redundância do
sistema) [Powell 92].
A atividade de diagnóstico da falta é necessária para: i) encontrar o componente que está
apresentando a falta; e ii) decidir se a falta é permanente ou não. Caso seja detectado que a falta é
permanente, a apassivação da falta deve ser executada5 e a reconfiguração do sistema,
considerada.
O diagnóstico é alcançado através da realização de testes de um componente do sistema
sobre outros componentes. Qualquer algoritmo de diagnóstico deve idealmente satisfazer as
seguintes propriedades [Shin-Ramanathan 87]:
Corretude: todo componente diagnosticado pelo algoritmo como sendo incorreto é
de fato incorreto; e
Completude: todo componente incorreto no sistema é identificado pelo algoritmo.
Além disso, um serviço de diagnóstico ideal deve permitir que todos os componentes
corretos do sistema concordem sobre quais componentes apresentam falha, ou seja, cada
componente correto deve chegar a um mesmo diagnóstico do sistema.
Quando os componentes do sistema apresentam semântica de falha silenciosa, estes
requisitos podem ser alcançados de forma razoavelmente simples. Por outro, se os componentes
se comportam de forma arbitrária quando em falha, não se conhece nenhum protocolo que possa
garantir um diagnóstico completo em todas as situações. Neste caso, o máximo que se pode
conseguir é um diagnóstico parcial do sistema.
Após a fase de diagnóstico da falta, os componentes diagnosticados como incorretos devem
ser removidos do sistema. Entretanto, a exclusão destes componentes degrada o nível de
redundância do sistema e, por conseguinte, a qualidade dos serviços para tolerância a faltas. Se o
4
A unanimidade é uma forma mais forte de confiabilidade que garante que qualquer mensagem entregue
a um receptor, é entregue a todos os receptores corretos [Powell 92]. Ou seja, a mensagem enviada ou é
entregue a todos os receptores corretos ou a nenhum deles.
5
As atividades de apassivação da falta geralmente são realizadas implicitamente na fase de
reconfiguração do sistema.
tempo de missão da aplicação não é conhecido ou se ainda está longe de se esgotar, faz-se
necessário que o sistema seja reconfigurado a fim de restaurar o nível de redundância requerido
pelos protocolos de processamento de erro, para garantir que o sistema continuará funcionando
corretamente durante todo o seu tempo de missão, mesmo diante de faltas posteriores.
A reconfiguração do sistema, porém, só pode ser considerada se há recursos redundantes
suficientes, já que tal tarefa acarreta a realocação e reinicialização das réplicas que falharam ou
que residiam em unidades de processamento que falharam.
Quando faltas de projeto são consideradas e, portanto, as próprias réplicas do componente
de software são passíveis de falha, a nova réplica pode ser ativada na mesma unidade de
processamento daquela que falhou ou em qualquer outra unidade correta do sistema. Em ambos
os casos, porém, deve-se considerar que a nova réplica precisa ter projeto e implementação
diferentes da anterior.
Quando a falha ocorre em uma unidade de processamento, algumas ou todas as réplicas
que estavam executando naquela unidade passam a funcionar incorretamente ou simplesmente
param de funcionar. Para que o grau de tolerância a faltas seja mantido de forma que faltas
futuras possam ainda ser toleradas, tais réplicas precisam ser criadas novamente. Se a falta pode
ser considerada temporária, e assim a simples reinicialização do estado da réplica é suficiente para
trazê-la de volta ao funcionamento normal, ou se a manutenção corretiva da unidade de
processamento que falhou foi realizada, a localização em que a nova réplica é criada pode ser a
mesma em que a réplica original estava executando antes da falha. Se, por outro lado, a antiga
localização da réplica não está disponível, qualquer outra localização pode ser usada, desde que
esta faça parte do domínio de replicação6 do componente [Powell 92].
No caso de não haver recursos suficientes para a realocação de novas réplicas, alguns
componentes de software terão de ser abandonados em favor de outros mais críticos ou, no
mínimo, a operação tolerante a faltas será degradada. Na ausência de recursos disponíveis,
portanto, a reconfiguração do sistema deve ser adiada até que alguma unidade se recupere (após a
manutenção).
Concluindo, a reconfiguração de um sistema é tal que o componente apresentando falta ou
é abandonado ou é usado numa configuração diferente de forma que não conduza à reativação da
falta e, conseqüentemente, da falha. O importante é que esta tarefa seja executada on-line e sem
nenhuma intervenção manual. Além disso, ela deve ser dinâmica no sentido de que a redundância
presente no sistema é usada para realizar a tarefa do componente incorreto [Jalote 94].
Com base nas considerações levantadas nas sub-seções acima, podemos identificar quatro
serviços básicos para a construção de aplicações tolerantes a faltas:
i) Replicação dos componentes de software;
ii) Comunicação em grupo;
iii) Diagnóstico de faltas; e
iv) Reconfiguração do sistema.
Ao se analisar a tecnologia atualmente disponível para a construção de aplicações
distribuídas, observa-se que a maior parte destes serviços são implementados pela própria
aplicação, com pouco ou nenhum suporte do sistema operacional. A construção de aplicações
distribuídas robustas poderia ser bastante simplificada, se a aplicação pudesse utilizar diretamente
estes serviços, sem precisar implementá-los. Ou seja, se a aplicação pudesse contar com um
ambiente operacional que fornecesse tais serviços de forma transparente e flexível e com ônus
apenas para aquelas aplicações que realmente fizessem uso deles. É a isto que se propõe o
ambiente operacional Seljuk.
6
O domínio de replicação de um componente de software é o conjunto de localidades onde as réplicas
de tal componente podem ser alocadas de modo que a consistência de processamento seja mantida.
4.
SERVIÇOS PARA TOLERÂNCIA A FALTAS NO SELJUK-AMOEBA
A implementação da arquitetura Seljuk será desenvolvida sobre sistemas operacionais
baseados na tecnologia de micro-núcleo, cujo princípio básico é minimizar o tamanho do sistema
operacional que executa em modo supervisor com o objetivo de aumentar a flexibilidade do
sistema. Toda a funcionalidade do sistema operacional que não é provida pelo micro-núcleo fica a
cargo de processos servidores que executam em modo usuário e, portanto, podem ser
modificados/adaptados mais facilmente para atender às exigências das diferentes aplicações.
Um dos micro-núcleos de maior destaque no meio acadêmico e científico, por sua
disponibilidade tanto a nível de código fonte quanto de documentação, é o Amoeba [Mullender et
al. 90], que, por este motivo, foi escolhido como o sistema hospedeiro para a primeira
implementação do Seljuk, batizada de Seljuk-Amoeba. Nesta seção, descrevemos uma proposta
para implementação de serviços para tolerância a faltas no ambiente operacional Seljuk-Amoeba.
A implementação dos serviços para tolerância a faltas neste ambiente assume que os
componentes do sistema possuem semântica de falha silenciosa. Para garantir tal semântica, estes
serviços invocam o serviço de processamento confiável oferecido pelo próprio Seljuk-Amoeba.
Este serviço é fornecido por um servidor denominado FT Run Server, cujos detalhes são
apresentados em [Gallindo-Brasileiro 97]. Os servidores que implementam os serviços para
tolerância a faltas são considerados livres de faltas (a realização desse pressuposto pode ser
atingida através da utilização de um nodo com semântica de falha mascarada [Gallindo-Brasileiro
97] para executar os servidores).
Como a implementação dos serviços de replicação, diagnóstico e reconfiguração se apóiam
no serviço de comunicação, começaremos a proposta tratando deste serviço.
4.1.
Serviço de Comunicação Confiável e em Grupo
O sistema de comunicação do Amoeba é implementado no núcleo em duas camadas. A
camada inferior implementa o Fast Local Internet Protocol (FLIP), que é um protocolo que opera
em modo datagrama, não orientado a conexão. Comunicação confiável é fornecida pelo Amoeba
pelos dois serviços da camada superior - comunicação em grupo e RPC (Remote Procedure Call),
que utilizam o serviço não confiável fornecido pelo FLIP para enviar mensagens.
Portanto, o Amoeba já oferece primitivas para comunicação em grupo que provêem entrega
atômica de mensagens a todos os membros do grupo mesmo na presença de falhas dos canais de
comunicação e, opcionalmente, dos processadores. Ou seja, a comunicação em grupo do Amoeba
garante entrega confiável e ordenada de mensagens a todos os membros corretos do grupo.
As primitivas de grupo provêem, portanto, uma abstração que permite os programadores
construirem aplicações consistindo de um ou mais processos executando em diferentes máquinas.
Todos os membros do grupo vêem todos os eventos relacionados ao seu grupo na mesma ordem,
incluindo aqueles eventos de junção/remoção de um membro e de recuperação de falhas no
grupo.
As primitivas para gerenciamento de grupo e comunicação em grupo integradas ao Amoeba,
bem como os seus algoritmos e medidas de desempenho, são descritas detalhadamente em
[Kaashoek-Tanenbaum 94] e apresentadas resumidamente na Tabela 2 abaixo. Tais primitivas só
podem ser chamadas por processos que fazem parte do grupo.
Primitiva
CreateGroup
JoinGroup
LeaveGroup
SendToGroup
ReceiveFromGroup
Descrição
Cria um novo grupo com as características definidas pelo chamador.
Torna o chamador um membro do grupo.
Remove o chamador do grupo.
Envia atomicamente uma mensagem para todos os membros do grupo.
Bloqueia o chamador até a chegada de uma mensagem.
ResetGroup
GetInfoGroup
ForwardRequest
Inicia a recuperação depois da falha de um membro do grupo.
Retorna informações sobre o estado do grupo, tais como: número de
membros e identificador do chamador no grupo.
Repassa um pedido endereçado ao grupo para um outro membro do
grupo.
Tabela 2: Primitivas para comunicação em grupo do Amoeba
A estrutura de grupos do Amoeba é fechada, significando que para um cliente ter acesso
ao serviço provido por um grupo, ele deve fazer uma RPC com um dos membros do grupo. Este
membro, por sua vez, usa comunicação em grupo para informar aos outros membros do seu
grupo, de forma consistente, o serviço solicitado pelo cliente.
No contexto do nosso trabalho, um grupo é formado por réplicas de um mesmo processo
(digamos, um servidor) que cooperam para fornecer um serviço único correto. A cada grupo é
associado um identificador único, denominado porta. Todos os membros do grupo que
implementa aquele servidor conhecem a porta que ele escuta. O conceito de portas provê a
transparência desejada para os clientes do serviço, que não precisam tomar conhecimento que o
servidor que está atendendo seu pedido é replicado.
Usando a comunicação em grupo nativa do Amoeba, baseada em grupos fechados, a
interação dos clientes com o servidor replicado se daria da seguinte forma: o cliente que deseja se
comunicar com o servidor faz uma RPC, indicando a porta do servidor. Apenas uma réplica está
escutando efetivamente aquela porta e recebe o pedido, ficando encarregada de distribuí-lo para as
demais réplicas. Isto, porém, requer que a aplicação implementando aquele servidor seja
codificada usando as primitivas de comunicação em grupo (CreateGroup(), JoinGroup(),
SendToGroup(), ReceiveFromGroup(), etc.). Isto vai de encontro ao nosso objetivo de tornar a
replicação o mais transparente possível para o programador. Cabe então ao ambiente operacional
Seljuk-Amoeba prover facilidades que atendam aos requisitos de comunicação e transparência,
simultaneamente.
Do mesmo modo que as primitivas nativas do Amoeba, as primitivas a serem acrescentadas
pelo Seljuk-Amoeba deverão ser flexíveis, permitindo que se possa balancear desempenho contra
requisitos de tolerância a faltas. Três novas primitivas foram identificadas: CreateRepGroup(), que
permite a criação de um grupo de réplicas; JoinRepGroup() que permite que novos membros
sejam adicionados a um grupo criado por CreateRepGroup(); e ResetRepGroup(), que permite a
inicialização do processo de recuperação do grupo. Todas elas podem ser ativadas por um
processo que não faz parte do grupo. Estas primitivas serão melhor compreendidas no decorrer
deste texto.
4.2.
Servidor de Replicação
O serviço de replicação de processamento no Seljuk-Amoeba é oferecido por um servidor
de replicação - o RepServer - implementado na camada Middleware da arquitetura Seljuk. A fim
de criar um componente de software replicado, daqui por diante chamado de processo replicado, a
seguinte invocação é feita ao RepServer, através de uma RPC:
Replicate(file, rep-type, rep-degree, failure-semantics)
Esta chamada solicita ao RepServer a criação de um processo replicado para executar o
código contido em file. A técnica de replicação a ser usada é indicada pelo parâmetro rep-type; o
grau de replicação (isto é, o número de réplicas a serem criadas) é determinado por rep-degree e a
semântica de falha real considerada para os processadores do sistema é definida em failuresemantics.
Se a semântica de falha definida para os processadores é menos restritiva do que a
semântica de falha silenciosa (que é a exigida por esta implementação do RepServer), ao receber
tal pedido, o RepServer deve requisitar ao FT Run Server, também via RPC, a criação de um
nodo7 com semântica de falha silenciosa. Esta chamada, quando realizada com sucesso, retorna
para o RepServer um descritor do nodo criado, que contém, entre outras informações, um
identificador para o nodo e uma lista dos processadores que compõem o nodo. O FT Run Server
cria nodos a partir de processadores que estejam com menor carga de processamento.
O RepServer faz tantas chamadas ao FT Run Server quantas forem necessárias para criar
um número de nodos que seja suficiente para executar todas as réplicas solicitadas, ou seja, repdegree chamadas são feitas. Em cada uma destas chamadas (exceto a primeira), o RepServer
repassa para o FT Run Server uma lista com todos os processadores que já foram utilizados na
composição dos nodos criados pelas chamadas anteriores. Isto evita que mais de uma réplica do
mesmo processo sejam executadas em um mesmo processador, o que levaria a diminuição do
grau de resiliência daquele processo, contrariando o nosso objetivo. Se todos os nodos forem
criados com sucesso8, o RepServer pode proceder; caso contrário, um erro é retornado para o
chamador.
Quando a aplicação já considera uma semântica de falha silenciosa para a infra-estrutura de
processamento, o serviço de criação de nodos não é necessário. Neste caso, o FT Run Server é
invocado apenas para disponibilizar para a aplicação rep-degree processadores, selecionando, mais
uma vez, aqueles que disponham de melhor condição para executar as réplicas do processo.
O fato de uma réplica está sendo executada em um processador único ou em um nodo, que
é um conjunto de processadores, é indiferente para o nosso nível de abstração, já que o ambiente
Seljuk-Amoeba provê o serviço de processamento confiável, implementado pelo conceito de
nodos, de forma transparente para os usuários deste serviço [Gallindo-Brasileiro 97]. Sob o ponto
de vista do RepServer, o comportamento de um nodo é equivalente ao comportamento de um
processador que possua a mesma semântica de falha implementada pelo nodo. Por questões de
generalidade, daqui por diante, adotaremos o termo nodo para referenciar tanto um processador
quanto um conjunto deles.
Caso todos os nodos tenham sido alocados com sucesso, o RepServer solicita o serviço de
processamento de cada um destes nodos para que ele crie um processo para executar o código
contido em file. Feito isto, é necessário que se forme um grupo com todas as réplicas do processo
para que elas possam interagir a fim de proverem um serviço único e confiável. Isto é feito pelo
RepServer por meio de uma nova primitiva de gerenciamento de grupo oferecida pelo SeljukAmoeba:
CreateRepGroup(port, member-list)
Esta chamada cria um grupo composto pelos membros indicados em member-list, que
deverão escutar todas as mensagens enviadas para a porta port, e retorna um identificador de
grupo, utilizado nas chamadas posteriores. Além disso, ela cria um objeto de configuração para o
grupo, que guarda as informações relativas à composição corrente do grupo, tais como: uma lista
dos membros do grupo e uma lista dos nodos que hospedam estes membros. Note que este grupo
só é visível para o processo que o criou (neste caso, o RepServer) e para o núcleo do sistema
operacional.
As ações realizadas daqui por diante dependem da técnica de replicação usada e, por este
motivo, serão tratadas separadamente logo abaixo.
Replicação Ativa
Na replicação ativa, cada uma das réplicas processa todas as mensagens destinadas ao
processo replicado e provê mensagens de saída. Ou seja, todas as réplicas executam num modo
ativo. Desta forma, mensagens de saída duplicadas são inevitavelmente geradas. Porém, todas elas
possuem o mesmo número de porta do transmissor e o mesmo número de sequência, podendo
assim ser facilmente descartadas pelo núcleo da máquina receptora.
7
Nodos são as unidades de processamento fornecidas pelo FT Run Server com a semântica de falha
requerida pela aplicação (no nosso caso, pelo servidor de replicação).
8
Um nodo pode não ser criado devido a restrições na disponibilidade de recursos.
No Seljuk-Amoeba, qualquer aplicação distribuída que utilize apenas troca de mensagens na
comunicação e que tenha um comportamento determinístico, pode ser replicada de forma ativa de
maneira completamente transparente, ou seja, o ambiente disponibiliza suporte para execução
replicada mesmo para aquelas aplicações que não foram projetadas para serem executadas de
forma replicada (desde que elas atendam às restrições mencionadas acima).
Replicação Passiva
Na replicação passiva, apenas uma réplica (a primária) processa as mensagens de entrada e
provê as mensagens de saída. As demais réplicas (as backups) são passivas visto que, na ausência
de faltas, elas não executam qualquer processamento, exceto a atualização de seus estados
internos. Um processo a ser replicado passivamente deve, portanto, ser implementado de forma
que ele possa operar num modo ativo, executando efetivamente o processamento, ou num modo
passivo, onde ele simplesmente atualiza seu estado. Durante sua execução, a réplica do processo
pode reverter de modo passivo para ativo, o que significa que ela deixou de ser backup e passou a
ser primária.
A plataforma em estudo provê uma biblioteca de funções a serem usadas na implementação
de processos seguindo os modelos de replicação passiva e semi-ativa. Dentre elas, temos a função
Election(), que executa um algoritmo para decidir qual das réplicas do grupo será a primária. Esta
função retorna um código indicando se a réplica que fez a chamada deve operar em modo ativo
ou passivo (i.e., deve ser primária ou backup).
Uma outra função importante é a InputLog(), utilizada pelas backups para guardar todas as
mensagens de entrada recebidas pela primária. Tal função é necessária para que, quando uma
réplica backup assume o papel de primária, ela não precise solicitar o reenvio das mensagens de
entrada já recebidas (e possivelmente processadas) pela antiga réplica primária antes da sua falha,
o que poderia conduzir ao chamado efeito dominó [Randell 75].
A atualização do estado das réplicas backups é feita através das funções Checkpoint() e
GetCheckpoint(). A réplica primária chama a função Checkpoint() passando como parâmetro uma
função de empacotamento, que é responsável por compor uma mensagem que reflita o estado
interno atual da réplica primária. Esta mensagem é recebida pelas réplicas backups por meio da
chamada GetCheckpoint(), que, por sua vez, tem como parâmetro uma função de
desempacotamento capaz de decompor a mensagem recebida e atualizar o estado da réplica a
partir da informação nela contida. As funções de empacotamento e desempacotamento são
específicas da aplicação.
Cada vez que uma operação de atualização de estado é executada com sucesso, as
mensagens existentes no log de entrada podem ser descartadas, pois apenas as mensagens
recebidas a partir deste ponto serão necessárias para a recuperação em caso de falha da réplica
primária.
Quando a falha de uma réplica primária é detectada, a função Election() é chamada para
eleger, entre as réplicas backups, uma nova réplica primária. O modo de operação da réplica eleita
reverte do modo passivo para o modo ativo e ela começa a executar a partir do último checkpoint,
processando as mensagens guardadas no log. Isto provavelmente vai gerar algumas mensagens de
saída idênticas àquelas já produzidas pela réplica primária anterior, porém tais mensagens são
descartadas pelo destinatário, como explicado na replicação ativa.
Replicação Semi-Ativa
Na replicação semi-ativa, apesar de apenas uma das réplicas (a líder) produzir mensagens
de saída, todas as outras réplicas (as seguidoras) também recebem e processam as mensagens de
entrada. Este processamento é necessário para que as seguidoras atualizem seus estados internos
e, em caso de falha da líder, possam assumir o seu papel logo após esta falha tenha sido
detectada, com o mínimo de atraso possível. Como na replicação passiva, a função Election()
deve ser executada no início do processamento para decidir qual réplica será a líder.
Em caso de falha da réplica líder, uma nova líder é eleita entre as seguidoras, passando a
disseminar mensagens para o mundo exterior. Como pode haver uma diferença de sincronização
entre as réplicas, pode ocorrer que a réplica seguidora esteja um pouco à frente da líder e, no
momento que ela assume seu novo papel, algumas mensagens de saída deixem de ser
disseminadas. Para evitar isto, a réplica líder envia, de tempos em tempos, uma mensagem
especial notificando as suas seguidoras sobre o número sequencial da última mensagem gerada.
Este valor é armazenado pela réplica seguidora e atualizado sempre que uma nova mensagem
deste tipo chega. Além disso, cada seguidora utiliza a função OutputLog() para guardar as
mensagens por ela produzidas (mas não disseminadas para o mundo exterior). Quando uma
mensagem de notificação enviada pela líder chega, as mensagens no log de saída que possuam
identificador menor ou igual aquele indicado pela notificação são eliminadas do log.
Quando uma seguidora assume o papel de líder, ela pode se encontrar em duas situações:
(a) o log está vazio, o que indica que a líder estava à frente da seguidora ou que a seguidora e a
líder estavam aproximadamente sincronizadas; ou (b) o log não está vazio, o que indica que a
seguidora estava processando mais rápido que a líder. Assim, quando uma seguidora passa a ser
líder ela deve verificar se o log está vazio ou não; se não está, ela primeiro envia todas as
mensagens no log, para só depois começar a enviar as novas mensagens produzidas. Por outro
lado, se o log está vazio, ela deve verificar qual foi o valor indicado na última mensagem de
notificação do líder e só enviar, dentre as novas mensagens produzidas, aquelas que tenham um
número de seqüência maior que aquele indicado para a última mensagem enviada pela líder, antes
da falha.
Note que, as mensagens enviadas pela réplica líder entre o envio de uma mensagem de
notificação e a sua falha seriam novamente enviadas pela nova líder. Mas isto não é um problema,
pois o próprio receptor destas mensagens se encarrega de descartar as duplicatas.
Como visto anteriormente, na replicação semi-ativa o potencial comportamento não
determinístico das réplicas pode levar à divergência de estados entre elas. É preciso, portanto,
controlar possíveis comportamentos não determinísticos. Este tipo de comportamento pode ser
conseqüência de duas situações: i) processamento não determinístico da aplicação (p. ex., um
cálculo feito a partir dos dados lidos de um sensor); e ii) não determinismo de componentes do
sistema operacional (p. ex., tratamento de eventos assíncronos).
Estes problemas são resolvidos no Seljuk-Amoeba da seguinte maneira. Toda operação que
tenha comportamento não determinístico é implementada de forma que o seu resultado é
calculado pela réplica e, em seguida, enviado para si mesma através de uma mensagem. Esta
mensagem seria, a priori, recebida por todas as réplicas do grupo. Porém, como cada réplica
segue esta implementação, várias mensagens com mesmo número de seqüência são produzidas.
Note que estas mensagens serão ordenadas normalmente pelo mecanismo de ordenação que
gerencia o grupo de réplicas e, desta forma, apenas um dos valores calculados será recebido e
utilizado por todas as réplicas (aquele valor calculado pela réplica que conseguiu ordenar seu valor
em primeiro lugar); os outros valores serão descartados como duplicatas de uma mensagem já
recebida.
No caso dos eventos assíncronos, estes são transformados em mensagens especiais, que
são enviadas para as outras réplicas do nodo. Os handlers destes eventos são implementados na
forma de threads que bloqueiam a espera de uma mensagem sinalizando o evento correspondente.
Novamente, o mecanismo de ordenação do grupo de réplicas garante que os mesmos eventos
serão tratados no mesmo ponto de execução por todas as réplicas. Um seqüenciador de eventos é
usado para possibilitar o descarte de eventos duplicados, já tratados.
4.3.
Serviço de Diagnóstico de Falta e Detecção de Falhas
Há dois níveis de falhas a se considerar: a falha dos nodos e a falha das próprias réplicas. O
próprio protocolo de comunicação em grupo do Amoeba [Kaashoek-Tanenbaum 94] provê
detecção de falhas dos nodos onde as réplicas de um processo estão executando de acordo com o
grau de resiliência especificado na chamada CreateGroup(). Uma falha é detectada pelo núcleo
rodando em um nodo por meio do envio de uma mensagem BC_ALIVEREQ para o nodo que
está há um certo tempo sem enviar qualquer mensagem. Se após algumas tentativas nenhuma
mensagem de resposta BC_ALIVE chega, o nodo solicitante assume que o destino falhou e entra
no modo de recuperação, marcando o grupo que possuia réplica executando naquele nodo como
não-usável. Todas as chamadas ReceiveFromGroup() subseqüentes, feitas pela réplica local,
retornam um erro e a réplica local deve chamar ResetGroup() para reorganizar o grupo. Tal
réplica se torna coordenadora do processo de recuperação, ficando responsável, entre outras
coisas, por atualizar os logs de mensagens de todas as réplicas do grupo. Neste ponto, todos os
membros do grupo revertem para um modo de recuperação.
Esta mesma idéia poderia ser utilizada em um nível mais alto para detectar falhas das
réplicas. Neste caso, sempre que uma réplica faz uma chamada ReceiveFromGroup(), ela
inicializa um temporizador. Se nenhuma mensagem chega no tempo estabelecido, uma falha é
assumida e uma chamada ResetGroup() é feita. Mais uma vez, observamos que esta
funcionalidade não é transparente para a aplicação, que inevitavelmente toma conhecimento da
ocorrência da falha.
Por este motivo, optou-se por executar em cada nodo um servidor de detecção que
funciona como um daemon que, de tempos em tempos, inspeciona o funcionamento da réplica
local e dos outros nodos que executam réplicas do mesmo grupo (esta informação é mantida em
cada nodo). Quando a falha de uma réplica é detectada, o servidor de detecção de falhas faz uma
chamada ResetRepGroup(group-id), solicitando ao núcleo que ele inicie o processo de
recuperação para reorganizar o grupo identificado por group-id.
4.4.
Serviço de Reconfiguração
Cada vez que uma réplica (ou um nodo) falha e uma chamada ResetRepGroup() é feita, o
número de membros do grupo diminui e, conseqüentemente, a capacidade de tolerar novas faltas
vai sendo paulatinamente reduzida. A fim de manter o grau de resiliência da aplicação, a
reconfiguração do processo replicado se faz necessária. Tal reconfiguração implica que um novo
nodo deve ser alocado e que uma nova réplica do processo deve ser iniciada. Esta função é
realizada pelo próprio RepServer.
Para tanto, após todos os procedimentos de recuperação terem sido concluídos, o servidor
de detecção que requisitou a recomposição do grupo, faz a seguinte solicitação ao RepServer:
CreateNewRep(file,new-rep-degree, failure-semantics, group-id)
Ao receber tal pedido, o RepServer examina as informações armazenadas para o grupo
identificado por group-id para verificar quantos membros compõem atualmente o grupo. Caso
este número seja menor que new-rep-degree (o que certamente vai ocorrer), novas réplicas
precisam ser criadas. Neste caso, RepServer passa a agir da mesma forma descrita na Sub-seção
4.2. Ele requisita os serviços do FT Run Server para a criação de novos nodos e o serviço de
processamento dos nodos criados para executar o código indicado em file. Além disso, o
RepServer faz uma chamada a primitiva de gerenciamento de grupo JoinRepGroup(group-id,
member-list) para que os membros indicados em member-list sejam acrescentados ao grupo
group-id.
Observe a flexibilidade de reconfiguração provida pelo ambiente Seljuk-Amoeba. O
servidor de detecção de falhas pode decidir que o grau de resiliência inicialmente previsto para
aquele processo replicado não mais é necessário ser mantido, porque, por exemplo, a execução do
processamento já está próxima do fim. Neste caso, ele pode simplesmente não fazer a chamada
CreateNewRep ou ainda pode fazê-la indicando um grau de resiliência menor que o indicado na
chamada Replicate(). Por outro lado, se for constatado que a freqüência da ocorrência de falhas
está muito alta, o grau de resiliência pode ser aumentado passando um new-rep-degree maior.
5.
CONCLUSÕES
Os primeiros sistemas construídos para serem tolerantes a faltas eram soluções ad-hoc
aplicáveis primariamente a problemas específicos. Porém, com o aumento da dependência da
sociedade nos sistemas de computador e com o conseqüente crescimento dos requisitos de
confiança no funcionamento das aplicações, surgiu a necessidade de se proporcionar, ao
desenvolvedor de aplicações, meios que permitissem o reaproveitamento do esforço desprendido
na construção de outros sistemas tolerantes a faltas, minimizando assim o seu trabalho.
Uma forma de se conseguir isto é prover um ambiente operacional que ofereça suporte para
construção e execução de aplicações distribuídas robustas, livrando o programador do trabalho de
implementar os mecanismos para tolerância a faltas. A plataforma aqui apresentada provê serviços
que implementam os principais mecanismos para tolerância a faltas utilizados na construção de
aplicações distribuídas robustas.
Uma das vantagens da nossa proposta é que ela é implementada inteiramente em software,
não requerendo qualquer recurso especial de hardware. Além disso, os serviços propostos
apresentam-se bastante flexíveis à medida que a aplicação pode, em tempo de ativação, selecionar
o grau de tolerância a faltas desejado. Além disso, apenas aquelas aplicações que requisitem tais
serviços pagam (sob a forma de queda de desempenho) pelo seu uso.
As diferentes abordagens para replicação podem conviver e cooperar no ambiente SeljukAmoeba. A replicação do processamento é transparente para o ambiente externo, de modo que
uma aplicação replicada se comporta aparentemente da mesma forma que uma não-replicada, com
a grande diferença que uma aplicação replicada é resiliente, conseguindo prover o serviço
adequado mesmo na presença de um certo número de faltas.
AGRADECIMENTOS
Os autores agradecem o apoio financeiro do CNPq (processo 300.646/96-8).
REFERÊNCIAS
[Birman-Joseph 87]
K.P. Birman e T.A. Joseph, “Reliable Communication in the Presence of
Failures,” ACM Transactions on Computer Systems, Vol. 5, No. 1, pp.
47-76, fevereiro de 1987.
[Brasileiro 97]
F.V.Brasileiro, “Seljuk: Um ambiente para Suporte ao Desenvolvimento
e à Execução de Aplicações Distribuídas Robustas”, submetido ao VII
Simpósio de Computadores Tolerantes a Falhas, fevereiro de 1997.
[Brasileiro-Ezhichelvan 95]
F.V. Brasileiro e P.D. Ezhilchelvan, “Atomic Broadcast Using Time-outs
instead of Synchronised Time”, Anais do VI Simpósio de Computadores
Tolerantes a Falhas, Canela, Brasil, pp. 223-238, agosto de 1995.
[Chang-Maxemchuk 84]
Jo-Mei Chang e N. F. Maxemchuk, “Reliable Broadcast Protocols”,
ACM Transactions on Computer Systems, Vol. 2, No. 3, pp. 251-273,
agosto de 1984.
[Cristian 91]
F. Cristian, “Understanding Fault-Tolerant Distributed Systems,”
Communications of the ACM, Vol. 34, N. 2, pp. 56-78, fevereiro de
1991.
[Cristian et al. 85]
F. Cristian, H. Aghili, R. Strong, e D. Dolev, “Atomic Broadcast: from
Simple Message Diffusion to Byzantine Agreement”, Digest of Papers,
FTCS-15, Ann Arbor, USA, pp. 200-206, junho de 1985.
[Gallindo-Brasileiro 97]
E.L. Gallindo e F.V. Brasileiro, “Processamento Confiável no Ambiente
Operacional Seljuk-Amoeba”, submetido ao VII Simpósio de
Computadores Tolerantes a Falhas, fevereiro de 1997.
[Huang-Kintala 93]
Y. Huang e C. Kintala, “Software Implemented Fault Tolerance:
Technologies and Experience”, Proceedings of the 23rd FTCS, Tolouse,
França, pp. 2-9, 1993.
[Jalote 94]
Pankaj Jalote, Fault Tolerance in Distributed Systems, Prentice Hall,
New Jersey, 1994, ISBN 0-13-301367-7.
[Kaashoek-Tanenbaum 94]
F. Kaashoek e A.S. Tanenbaum, “Efficient Reliable Group
communication for Distributed Systems”, Submetido à publicação em
1994.
[Lamport 78]
L. Lamport, “Time, Clocks and Ordering of Events”, Communications of
the ACM, Vol. 21, No. 7, pp. 558-565, julho de 1978.
[Laprie 89]
J. C. Laprie, “Dependability: a Unifying Concept for Computing and
Fault Tolerance”, in Dependabilty of Resilient Computers, T. Anderson
(Ed.), BSP Professional Books, 1989.
[Lee-Anderson 90]
P.A. Lee. e D. A. Anderson, Fault Tolerance - Principles and Practice,
Spring-Verlag, 1990.
[Lemos-Veríssimo 91]
R. de Lemos e Paulo Veríssimo, “Confiança no Funcionamento Proposta para uma Terminologia em Português”, comunicação pessoal,
dezembro de 1991.
[Mullender et al. 90]
S.J. Mullender, G. van Rossum, A.S. Tanembaum, R. van Renesse, e H.
van Staveren, “Amoeba: A Distributed Operating System for the 1990's”,
IEEE Computer, Vol. 23, No. 5, pp. 44-53, maio de 1990.
[Ng 90]
T.P. Ng, “The Design and Implementation of a Reliable Distributed
Operating System - ROSE”, Proceedings of ICDCS, pp. 2-11, 1990.
[Powell 92]
D. Powell (Ed.), Delta-4 - A Generic Architecture for Dependable
Distributed Computing, Spring-Verlag, 1992, ISBN 3-540-54985-4.
[Randell 75]
B. Randell, “System Structure for Software Fault Tolerance,” IEEE
Transactions on Software Engineering, Vol. 1, No. 2, pp. 220-232, junho
de 1975.
[Shin-Ramanathan 87]
K.G. Shin, e P. Ramanathan, “Diagnosis of Processors with Byzantine
Faults in a Distributed Computing System”, Digest of Papers, FTCS-17,
Pittsburgh, USA, pp. 55-60, junho de 1987.
Download