Gestão de clusters em Orleans
Orleans fornece gerenciamento de cluster por meio de um protocolo de associação integrado, que às vezes chamamos de Associação de Silo. O objetivo deste protocolo é que todos os silos (Orleans servidores) concordem com o conjunto de silos atualmente ativos, detetem silos com falha e permitam que novos silos se juntem ao cluster.
O protocolo depende de um serviço externo para fornecer uma abstração de IMembershipTable. IMembershipTable
é uma tabela durável plana do tipo No-SQL que usamos para dois fins. Primeiro, é usado como um ponto de encontro para silos encontrarem uns aos outros e Orleans clientes encontrarem silos. Em segundo lugar, ele é usado para armazenar a exibição de associação atual (lista de silos vivos) e ajuda a coordenar o contrato na exibição de associação. Atualmente, temos 6 implementações do IMembershipTable
: com base no Azure Table Storage, SQL server, Apache ZooKeeper, Consul IO, AWS DynamoDB e emulação na memória para desenvolvimento.
Além disso, IMembershipTable cada silo participa de um protocolo de associação peer-to-peer totalmente distribuído que deteta silos com falha e chega a um acordo sobre um conjunto de silos vivos. Começamos descrevendo a implementação interna do protocolo de Orleansassociação do abaixo e, mais tarde, descrevemos a implementação do IMembershipTable
.
O Protocolo Básico de Adesão
Após a inicialização, cada silo adiciona uma entrada para si mesmo em uma tabela compartilhada bem conhecida, usando uma implementação de IMembershipTable. Uma combinação de identidade de silo (
ip:port:epoch
) e id de implantação de serviço é usada como chaves exclusivas na tabela. Época é apenas o tempo em que este silo começou, e, como talip:port:epoch
, é garantido ser único em uma determinada Orleans implantação.Os silos monitorizam-se uns aos outros diretamente, através de pings de aplicação ("are you alive"
heartbeats
). Pings são enviados como mensagens diretas de silo para silo, através dos mesmos soquetes TCP que os silos comunicam. Dessa forma, os pings se correlacionam totalmente com problemas reais de rede e integridade do servidor. Cada silo executa um conjunto configurável de outros silos. Um silo escolhe quem fazer ping calculando hashes consistentes na identidade de outros silos, formando um anel virtual de todas as identidades e escolhendo silos sucessores X no anel (esta é uma técnica distribuída bem conhecida chamada hashing consistente e é amplamente usada em muitas tabelas de hash distribuídas, como Chord DHT).Se um silo S não receber respostas de ping Y de um servidor monitorado P, ele suspeita escrevendo sua suspeita com carimbo de data/hora na linha de P no
IMembershipTable
.Se P tem mais do que Z suspeitas dentro de K segundos, então S escreve que P está morto na linha de P, e transmite um pedido para que todos os silos releiam a tabela de membros (o que eles farão de qualquer maneira periodicamente).
Mais detalhes:
A suspeita é escrita para o
IMembershipTable
, em uma coluna especial na linha correspondente a P. Quando S suspeita de P, escreve: "no momento TTT S suspeitou de P".Uma suspeita não é suficiente para declarar P como morto. Você precisa de suspeitas Z de silos diferentes em uma janela de tempo configurável T, normalmente 3 minutos, para declarar P como morto. A suspeita é escrita usando o controle de simultaneidade otimista fornecido pelo
IMembershipTable
.O silo suspeito S lê a fila de P.
Se
S
for o último suspeito (já houve suspeitos Z-1 dentro de um período de T, como escrito na coluna de suspeita), S decide declarar P como morto. Neste caso, S adiciona-se à lista de suspeitos e também escreve na coluna Status de P que P está morto.Caso contrário, se S não for o último suspeito, S apenas se adiciona à coluna do suspeito.
Em ambos os casos, o write-back usa o número da versão ou ETag que foi lido, portanto, as atualizações para essa linha são serializadas. Caso a gravação tenha falhado devido a incompatibilidade de versão/ETag, S tenta novamente (leia novamente e tente escrever, a menos que P já esteja marcado como morto).
Em um alto nível, essa sequência de "ler, modificar local, escrever de volta" é uma transação. No entanto, não estamos usando transações de armazenamento para fazer isso. O código "Transação" é executado localmente em um servidor e usamos simultaneidade otimista fornecida pelo para garantir isolamento
IMembershipTable
e atomicidade.
Cada silo lê periodicamente toda a tabela de membros para sua implantação. Dessa forma, os silos aprendem sobre a junção de novos silos e sobre outros silos que estão sendo declarados mortos.
Configuração: fornecemos uma configuração padrão, que foi ajustada manualmente durante nosso uso de produção no Azure. Atualmente, o padrão é: cada silo é monitorado por outros três silos, duas suspeitas são suficientes para declarar um silo morto, suspeitas apenas dos últimos três minutos (caso contrário, estão desatualizadas). Pings são enviados a cada dez segundos e você precisaria perder três pings para suspeitar de um silo.
Forçando a deteção de falha perfeita – é teoricamente possível que um silo seja declarado morto se perder a comunicação com outros silos, enquanto o processo do silo em si ainda está em execução. Para resolver este problema, uma vez que o silo é declarado morto na tabela, ele é considerado morto por todos, mesmo que não esteja morto (apenas particionado temporariamente ou mensagens de pulsação foram perdidas). Todo mundo para de se comunicar com ele e uma vez que ele descobre que está morto (lendo seu novo status da mesa) ele comete suicídio e encerra seu processo. Como resultado, deve haver uma infraestrutura para reiniciar o silo como um novo processo (um novo número de época é gerado no início). Quando está hospedado no Azure, isso acontece automaticamente. Quando não é, outra infraestrutura é necessária. Por exemplo, um serviço do Windows configurado para reiniciar automaticamente em caso de falha ou uma implantação do Kubernetes.
Otimização para reduzir a frequência de leituras periódicas de tabelas e acelerar todos os silos aprendendo sobre novos silos de junção e silos mortos. Toda vez que qualquer silo escreve algo com sucesso para a mesa (suspeita, nova adesão, etc.) ele também transmite para todos os outros silos – "vá e releia a tabela agora". O silo NÃO diz aos outros o que escreveu na tabela (uma vez que esta informação já pode estar desatualizada/errada), apenas lhes diz para relerem a tabela. Dessa forma, aprendemos muito rapidamente sobre as mudanças de membros, sem a necessidade de esperar pelo ciclo completo de leitura periódica. Ainda precisamos da leitura periódica, caso a mensagem "releia a tabela" se perca.
Propriedades do protocolo de associação básico
Pode lidar com qualquer número de falhas:
Nosso algoritmo pode lidar com qualquer número de falhas (ou seja, f< = n), incluindo a reinicialização completa do cluster. Isso contrasta com as soluções "tradicionais" baseadas em Paxos , que exigem quórum, que geralmente é majoritário. Vimos em situações de produção em que mais da metade dos silos estavam inoperantes. Nosso sistema permaneceu funcional, enquanto a associação baseada em Paxos não seria capaz de progredir.
O tráfego para a mesa é muito leve:
Os pings reais vão diretamente entre servidores e não para a mesa. Isso geraria muito tráfego, além de ser menos preciso do ponto de vista da deteção de falhas - se um silo não pudesse chegar à mesa, ele deixaria de escrever seu batimento cardíaco Estou vivo, e outros o matariam.
Precisão ajustável versus completude:
Embora você não possa alcançar a deteção de falhas perfeita e precisa, geralmente se quer uma capacidade de trocar a precisão (não quer declarar um silo que está vivo como morto) com a completude (quer declarar morto um silo que está realmente morto o mais rápido possível). Os votos configuráveis para declarar pings mortos e perdidos permitem negociar esses dois. Para obter mais informações, consulte Yale University: Computer Science Failure Detectors.
Dimensionamento:
O protocolo básico pode lidar com milhares e provavelmente até dezenas de milhares de servidores. Isso contrasta com as soluções tradicionais baseadas em Paxos, como protocolos de comunicação em grupo, que são conhecidos por não escalar além de dezenas.
Diagnósticos:
A tabela também é muito conveniente para diagnósticos e solução de problemas. Os administradores do sistema podem encontrar instantaneamente na tabela a lista atual de silos vivos, bem como ver o histórico de todos os silos mortos e suspeitas. Isto é especialmente útil no diagnóstico de problemas.
Por que precisamos de armazenamento persistente confiável para a implementação do
IMembershipTable
:Usamos armazenamento persistente (tabela do Azure, SQL Server, AWS DynamoDB, Apache ZooKeeper ou Consul IO KV) para duas
IMembershipTable
finalidades. Primeiro, é usado como um ponto de encontro para silos encontrarem uns aos outros e Orleans clientes encontrarem silos. Em segundo lugar, usamos armazenamento confiável para nos ajudar a coordenar o acordo sobre a visão da associação. Enquanto realizamos a deteção de falhas diretamente de forma ponto a ponto entre os silos, armazenamos a visão da associação em armazenamento confiável e usamos o mecanismo de controle de simultaneidade fornecido por esse armazenamento para chegar a um acordo sobre quem está vivo e quem está morto. Dessa forma, em certo sentido, nosso protocolo terceiriza o difícil problema do consenso distribuído para a nuvem. Na medida em que utilizamos plenamente o poder da plataforma de nuvem subjacente, usando-a verdadeiramente como Plataforma como Serviço (PaaS).O que acontece se a tabela não estiver acessível por algum tempo:
Quando o serviço de armazenamento está inativo, indisponível ou há problemas de comunicação com ele, o Orleans protocolo NÃO declara silos como mortos por engano. Os silos operacionais continuarão funcionando sem problemas. No entanto, Orleans não será capaz de declarar um silo morto (se detetar que algum silo está morto através de pings perdidos, não será capaz de escrever esse fato na mesa) e também não será capaz de permitir que novos silos se juntem. Portanto, a completude sofrerá, mas a precisão não: particionar da tabela nunca fará com que Orleans o silo seja declarado morto por engano. Além disso, no caso de uma partição de rede parcial (se alguns silos podem acessar a tabela e outros não), pode acontecer que declare um silo morto como morto, mas levará algum tempo até que Orleans todos os outros silos aprendam sobre isso. Assim, a deteção pode ser atrasada, mas Orleans nunca matará erroneamente um silo devido à indisponibilidade da tabela.
O Direct IAmAlive grava na tabela apenas para diagnóstico:
Além dos batimentos cardíacos que são enviados entre os silos, cada silo também atualiza periodicamente uma coluna "Estou vivo" em sua linha na tabela. Esta coluna "Estou vivo" é usada apenas para solução de problemas e diagnósticos manuais e não é usada pelo protocolo de associação em si. Geralmente é escrito em uma frequência muito menor (uma vez a cada 5 minutos) e serve como uma ferramenta muito útil para os administradores de sistema verificarem a vida do cluster ou descobrirem facilmente quando o silo estava vivo pela última vez.
Extensão para ordenar visualizações de associação
O protocolo de associação básico descrito acima foi posteriormente estendido para suportar visualizações de associação ordenadas. Descreveremos brevemente as razões para esta extensão e como ela é implementada. A extensão não altera nada no design acima, apenas adiciona propriedade de que todas as configurações de associação são globalmente totalmente ordenadas.
Por que é útil ordenar visualizações de membros?
Isso permite serializar a junção de novos silos ao cluster. Dessa forma, quando um novo silo se junta ao cluster, ele pode validar a conectividade bidirecional com todos os outros silos já iniciados. Se alguns deles já uniram silos não responderem (potencialmente indicando um problema de conectividade de rede com o novo silo), o novo silo não poderá entrar. Isso garante que, pelo menos quando um silo é iniciado, haja conectividade total entre todos os silos no cluster (isso é implementado).
Protocolos de nível mais alto no silo, como o diretório de grãos distribuídos, podem utilizar o fato de que as visualizações de associação são ordenadas e usar essas informações para executar uma resolução de ativações duplicadas mais inteligente. Em particular, quando o diretório descobre que 2 ativações foram criadas quando a associação estava em fluxo, ele pode decidir desativar a ativação mais antiga que foi criada com base nas informações de associação agora desatualizadas.
Protocolo de associação estendida:
Para a implementação deste recurso, utilizamos o suporte para transações em várias linhas que são fornecidas pelo
MembershipTable
.Adicionamos uma linha de versão de associação à tabela que controla as alterações da tabela.
Quando o silo S quer escrever uma declaração de suspeita ou morte para o silo P:
- S lê o conteúdo da tabela mais recente. Se P já está morto, não faça nada. Caso contrário,
- Na mesma transação, escreva as alterações na linha de P, bem como incremente o número da versão e escreva-o de volta na tabela.
- Ambas as gravações são condicionadas com ETags.
- Se a transação for abortada devido a incompatibilidade de ETag na linha de P ou na linha de versão, tente novamente.
Todas as gravações na tabela modificam e incrementam a linha de versão. Dessa forma, todas as gravações na tabela são serializadas (através da serialização das atualizações para a linha de versão) e, como os silos apenas incrementam o número da versão, as gravações também são totalmente ordenadas em ordem crescente.
Escalabilidade do protocolo de associação estendida:
Na versão estendida do protocolo, todas as gravações são serializadas por meio de uma linha. Isso pode prejudicar a escalabilidade do protocolo de gerenciamento de cluster, uma vez que aumenta o risco de conflitos entre gravações de tabela simultâneas. Para atenuar parcialmente esse problema, os silos tentem novamente todas as suas gravações na tabela usando backoff exponencial. Observamos os protocolos estendidos para funcionar sem problemas em um ambiente de produção no Azure com até 200 silos. No entanto, achamos que o protocolo pode ter problemas para escalar além de mil silos. Em configurações tão grandes, as atualizações para a linha de versão podem ser facilmente desativadas, essencialmente mantendo o resto do protocolo de gerenciamento de cluster e desistindo da propriedade de ordenação total. Observe também que nos referimos aqui à escalabilidade do protocolo de gerenciamento de cluster, não ao restante do Orleans. Acreditamos que outras partes do tempo de Orleans execução (mensagens, diretório distribuído, hospedagem de grãos, conectividade cliente a gateway) são escaláveis muito além de centenas de silos.
Tabela de membros
Como já mencionado, IMembershipTable
é usado como um ponto de encontro para silos para encontrar uns aos outros e Orleans clientes para encontrar silos e também ajuda a coordenar o acordo sobre a visão de adesão. Atualmente, temos seis implementações do IMembershipTable
: com base no Azure Table, SQL server, Apache ZooKeeper, Consul IO, AWS DynamoDB e emulação na memória para desenvolvimento.
Armazenamento de Tabela do Azure - nesta implementação, usamos a ID de implantação do Azure como chave de partição e a identidade do silo (
ip:port:epoch
) como chave de linha. Juntos, garantem uma chave única por silo. Para controle de simultaneidade, usamos o controle de simultaneidade otimista com base no Azure Table ETags. Toda vez que lemos da tabela, armazenamos o ETag para cada linha de leitura e usamos esse ETag quando tentamos escrever de volta. Os ETags são atribuídos e verificados automaticamente pelo serviço de Tabela do Azure em cada gravação. Para transações de várias linhas, utilizamos o suporte para transações em lote fornecido pela tabela do Azure, que garante transações serializáveis em linhas com a mesma chave de partição.SQL Server - nesta implementação, a ID de implantação configurada é usada para distinguir entre implantações e quais silos pertencem a quais implantações. A identidade do silo é definida como uma combinação de
deploymentID, ip, port, epoch
tabelas e colunas apropriadas. O back-end relacional usa transações e controle de simultaneidade otimistas, semelhante ao procedimento de usar ETags na implementação da Tabela do Azure. A implementação relacional espera que o mecanismo de banco de dados gere o ETag usado. No caso do SQL Server, no SQL Server 2000 o ETag gerado é aquele adquirido de uma chamada paraNEWID()
. No SQL Server 2005 e versões posteriores , ROWVERSION é usado. Orleans lê e grava ETags relacionais como tags opacasVARBINARY(16)
e as armazena na memória como cadeias de caracteres codificadas em base64 . Orleans suporta inserções de várias linhas usandoUNION ALL
(para Oracle, incluindo DUAL), que é atualmente usado para inserir dados estatísticos. A implementação exata e a lógica do SQL Server podem ser vistas em CreateOrleansTables_SqlServer.sql.Apache ZooKeeper - nesta implementação, usamos o ID de implementação configurado como um nó raiz e a identidade do silo (
ip:port@epoch
) como seu nó filho. Juntos, garantem um caminho único por silo. Para controle de simultaneidade, usamos controle de simultaneidade otimista com base na versão do nó. Toda vez que lemos a partir do nó raiz de implantação, armazenamos a versão para cada nó de silo filho lido e usamos essa versão quando tentamos escrever de volta. Cada vez que os dados de um nó mudam, o número da versão aumenta atomicamente pelo serviço ZooKeeper. Para transações de várias linhas, utilizamos o método multi, que garante transações serializáveis em nós de silo com o mesmo nó de ID de implantação pai.Consul IO - usamos a loja de chaves/valores da Consul para implementar a tabela de membros. Consulte Consul-Deployment para obter mais detalhes.
AWS DynamoDB - Nesta implementação, usamos o ID de implantação do cluster como a chave de partição e a identidade do silo (
ip-port-generation
) como a RangeKey que torna a unidade de registro. A simultaneidadeETag
otimista é feita pelo atributo fazendo gravações condicionais no DynamoDB. A lógica de implementação é bastante semelhante ao Armazenamento de Tabela do Azure.Emulação na memória para configuração de desenvolvimento. Usamos um sistema especial de grãos, chamado MembershipTableGrain, para essa implementação. Este grão vive em um silo primário designado, que é usado apenas para uma configuração de desenvolvimento. Em qualquer silo primário de produção real não é necessário.
Configuração
O protocolo de associação é configurado através do Liveness
elemento na Globals
seção em OrleansConfiguration.xml arquivo. Os valores padrão foram ajustados em anos de uso de produção no Azure e acreditamos que eles representam boas configurações padrão. Não há necessidade, em geral, de alterá-los.
Exemplo de elemento config:
<Liveness ProbeTimeout="5s"
TableRefreshTimeout="10s"
DeathVoteExpirationTimeout="80s"
NumMissedProbesLimit="3"
NumProbedSilos="3"
NumVotesForDeathDeclaration="2" />
Existem 4 tipos de liveness implementados. O tipo do protocolo liveness é configurado através SystemStoreType
do atributo do SystemStore
elemento na Globals
seção em OrleansConfiguration.xml arquivo.
MembershipTableGrain
: A tabela de membros é armazenada em um grão no silo primário. Esta é apenas uma configuração de desenvolvimento.AzureTable
: a tabela de associação é armazenada na tabela do Azure.SqlServer
: A tabela de membros é armazenada em um banco de dados relacional.ZooKeeper
: a tabela de membros é armazenada em um conjunto do ZooKeeper.Consul
: configurado como armazenamento de sistema personalizado comMembershipTableAssembly = "OrleansConsulUtils"
. Consulte Consul-Deployment para obter mais detalhes.DynamoDB
: configurado como um armazenamento de sistema personalizado comMembershipTableAssembly = "OrleansAWSUtils"
.
Para todos os tipos de vivacidade, as variáveis de configuração comuns são definidas no Globals.Liveness
elemento:
ProbeTimeout
: O número de segundos para sondar outros silos para sua vivacidade ou para o silo enviar mensagens de batimento cardíaco "Estou vivo" sobre si mesmo. O padrão é 10 segundos.TableRefreshTimeout
: O número de segundos para buscar atualizações na tabela de membros. A predefinição é 60 segundos.DeathVoteExpirationTimeout
: Tempo de expiração em segundos para votação de morte na tabela de membros. O padrão é 120 segundosNumMissedProbesLimit
: O número de mensagens perdidas de batimento cardíaco "Estou vivo" de um silo ou o número de sondas não respondidas que levam a suspeitar que este silo está morto. O padrão é 3.NumProbedSilos
: O número de silos que cada silo sonda para a vivacidade. O padrão é 3.NumVotesForDeathDeclaration
: O número de votos não expirados que são necessários para declarar algum silo como morto (deve ser, no máximo, NumMissedProbesLimit). O padrão é 2.UseLivenessGossip
: Se deve usar a otimização de fofocas para acelerar a disseminação de informações de vivacidade. A predefinição é verdadeiro.IAmAliveTablePublishTimeout
: O número de segundos para escrever periodicamente na tabela de membros que este silo está ativo. Usado apenas para diagnósticos. A predefinição é 5 minutos.NumMissedTableIAmAliveLimit
: O número de atualizações perdidas de "Estou vivo" na tabela de um silo que faz com que um aviso seja registrado. Não impacta o protocolo de liveness. O padrão é 2.MaxJoinAttemptTime
: O número de segundos para tentar juntar-se a um grupo de silos antes de desistir. A predefinição é 5 minutos.ExpectedClusterSize
: O tamanho esperado de um cluster. Não precisa ser muito preciso, pode ser uma superestimativa. Usado para ajustar o algoritmo de backoff exponencial de tentativas de gravação na tabela do Azure. O padrão é 20.
Lógica do design
Uma pergunta natural que pode ser feita é por que não confiar completamente no Apache ZooKeeper para a implementação de associação de cluster, potencialmente usando seu suporte pronto para associação de grupo com nós efêmeros? Por que nos preocupamos em implementar nosso protocolo de adesão? Havia principalmente três razões:
Implantação/Hospedagem na nuvem:
O Zookeeper não é um serviço hospedado (pelo menos no momento em que este artigo foi escrito, julho de 2015 e, definitivamente, quando implementamos este protocolo pela primeira vez no verão de 2011, não havia nenhuma versão do Zookeeper funcionando como um serviço hospedado por qualquer grande provedor de nuvem). Isso significa que, no ambiente Orleans de nuvem, os clientes teriam que implantar/executar/gerenciar sua instância de um cluster ZK. Este é apenas mais um fardo desnecessário, que não queríamos impor aos nossos clientes. Ao usar a Tabela do Azure, contamos com um serviço hospedado e gerenciado que torna a vida de nossos clientes muito mais simples. Basicamente, na Cloud, use a Cloud como uma Plataforma, não como uma Infraestrutura. Por outro lado, ao executar no local e gerenciar seus servidores, confiar no ZK como uma implementação do
IMembershipTable
é uma opção viável.Deteção direta de falhas:
Ao usar a associação de grupo do ZK com nós efêmeros, a deteção de falhas é realizada entre os servidores (clientes ZK) e os Orleans servidores ZK. Isso pode não estar necessariamente correlacionado com os problemas reais de rede entre Orleans servidores. Nosso desejo era que a deteção de falhas refletisse com precisão o estado intra-cluster da comunicação. Especificamente, em nosso design, se um Orleans silo não pode se comunicar com o
IMembershipTable
ele não é considerado morto e pode continuar funcionando. Ao contrário disso, usamos a associação ao grupo ZK com nós efêmeros, uma desconexão de um servidor ZK pode fazer com que um Orleans silo (cliente ZK) seja declarado morto, enquanto ele pode estar vivo e totalmente funcional.Portabilidade e flexibilidade:
Como parte da filosofia da Orleans, não queremos forçar uma forte dependência de qualquer tecnologia em particular, mas sim ter um design flexível onde diferentes componentes podem ser facilmente trocados com diferentes implementações. Este é exatamente o propósito que
IMembershipTable
a abstração serve.
Confirmações
Gostaríamos de reconhecer a contribuição de Alex Kogan para a conceção e implementação da primeira versão deste protocolo. Este trabalho foi feito como parte de um estágio de verão na Microsoft Research no verão de 2011.
A implementação do ZooKeeper baseado IMembershipTable
foi feita por Shay Hazor, a implementação do SQL IMembershipTable
foi feita pela Veikko Eeva, a implementação do AWS DynamoDB IMembershipTable
foi feita por Gutemberg Ribeiro e a implementação do Consul baseado IMembershipTable
foi feita por Paul North.