Partilhar via


Gestão de clusters em Orleans

Orleans fornece gestão de cluster por meio de um protocolo de associação incorporado, que às vezes chamamos de associação de cluster . 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 mesa plana durável 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, Azure Cosmos DB, ADO.NET (PostgreSQL, MySQL/MariaDB, SQL Server, Oracle), Apache ZooKeeper, Consul IO, AWS DynamoDB, MongoDB, Redis, Apache Cassandrae uma implementação in-memory 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. Descrevemos abaixo a implementação interna do protocolo de adesão do Orleans.

O protocolo de adesão

  1. 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 (ID de cluster) é usada como chaves exclusivas na tabela. Época é apenas o tempo em que este silo começou, e, como tal ip:port:epoch , é garantido ser único em uma determinada Orleans implantação.

  2. Os silos monitorizam-se mutuamente diretamente, através de sondas de aplicação ("are you alive" heartbeats). as sondas são enviadas como mensagens diretas de silo para silo, através dos mesmos soquetes TCP com que os silos comunicam. Dessa forma, as sondas se correlacionam totalmente com problemas reais de rede e integridade do servidor. Cada silo sonda um conjunto configurável de outros silos. Um silo escolhe quem sondar 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).

  3. Se um silo S não obtiver respostas da sonda Y de um servidor monitorado P, ele suspeitará escrevendo sua suspeita com carimbo de data/hora na linha de P no IMembershipTable.

  4. Se P tiver mais de Z suspeitas dentro de K segundos, S escreve que P está morto na linha de P e envia um instantâneo da tabela de membros atual para todos os outros silos. Os silos atualizam a tabela periodicamente, assim, o instantâneo é uma otimização para reduzir o tempo necessário para que todos os silos conheçam a nova visão de associação.

  5. Mais detalhes:

    1. 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".

    2. 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.

    3. O silo suspeito S lê a fila de P.

    4. 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.

    5. Caso contrário, se S não for o último suspeito, S apenas se adiciona à coluna do suspeito.

    6. 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).

    7. Em um alto nível, essa sequência de "ler, modificar local, escrever de volta" é uma transação. No entanto, não estamos necessariamente 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.

  6. 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.

  7. Snapshot broadcast: Para reduzir a frequência de leituras de tabelas periódicas, sempre que um silo grava na tabela (suspeita, nova junção, etc.) ele envia um instantâneo do estado atual da tabela para todos os outros silos. Dado que a tabela de associação é consistente e monotonicamente versionada, cada atualização produz um instantâneo de versão única, podendo ser compartilhado com segurança. Isso permite a propagação imediata de alterações de associação sem esperar pelo ciclo de leitura periódica. A leitura periódica ainda é mantida como um recurso de contingência caso de falha na distribuição do snapshot.

  8. Visualizações de associação ordenadas: O protocolo de associação garante que todas as configurações de associação sejam globalmente totalmente ordenadas. Esta encomenda proporciona duas vantagens principais:

    1. Conectividade garantida: Quando um novo silo se junta ao cluster, ele deve validar a conectividade bidirecional para todos os outros silos ativos. Se algum silo existente não responder (potencialmente indicando um problema de conectividade de rede), o novo silo não poderá ingressar. Isso garante conectividade total entre todos os silos no cluster no momento da inicialização. Consulte a nota sobre IAmAlive abaixo para uma exceção no caso de recuperação de desastres.

    2. Atualizações consistentes de diretórios: Protocolos de nível superior, como o diretório de grãos distribuídos, dependem de todos os silos terem uma visão consistente e monotônica da associação. Isso permite uma resolução mais inteligente de ativações de grãos duplicados. Para obter mais detalhes, consulte a documentação do diretório de grãos .

    Detalhes da implementação:

    1. O IMembershipTable requer atualizações atômicas para garantir uma ordem global total de mudanças:

      • As implementações devem atualizar as entradas da tabela (lista de silos) e o número da versão atomicamente
      • Isso pode ser feito usando transações de banco de dados (como no SQL Server) ou operações de comparação e troca atômicas usando ETags (como no Armazenamento de Tabela do Azure)
      • O mecanismo específico depende dos recursos do sistema de armazenamento subjacente
    2. Uma linha especial de versão de membro na tabela rastreia as alterações.

      • Cada escrita na tabela (suspeitas, declarações de óbito, junções) incrementa este número de versão
      • Todas as gravações são serializadas através desta linha usando atualizações atômicas
      • A versão monotonicamente crescente garante uma ordenação total de todas as alterações de membros
    3. Quando o silo S atualiza o status do silo P:

      • S lê primeiro o estado da tabela mais recente
      • Em uma única operação atômica, ele atualiza a linha de P e incrementa o número de versão.
      • Se a atualização atômica falhar (por exemplo, devido a modificações simultâneas), a operação será repetida com recuo exponencial

    Considerações sobre escalabilidade:

    A serialização de todas as escritas através da linha de versão pode afetar a escalabilidade devido ao aumento da contenção. O protocolo foi comprovado em produção com até 200 silos, mas pode enfrentar desafios além de mil silos. Para implantações muito grandes, outras partes do Orleans (mensagens, diretório de grãos, hospedagem) permanecem escaláveis mesmo se as atualizações de associação se tornarem um gargalo.

  9. Configuração padrão: A configuração padrão foi ajustada manualmente durante o uso de produção no Azure. Por padrão: cada silo é monitorizado por três outros silos, duas suspeitas são suficientes para declarar um silo morto, consideram-se apenas as suspeitas dos últimos três minutos (caso contrário, estão desatualizadas). As sondas são enviadas a cada dez segundos e você precisaria perder três sondas para suspeitar de um silo.

  10. de auto-monitorização: O detetor de falhas incorpora ideias da pesquisa Lifeguard da Hashicorp (artigo, palestra, blog) para melhorar a estabilidade do cluster durante eventos catastróficos onde uma grande parte do cluster experimenta falha parcial. O componente LocalSiloHealthMonitor pontua a saúde de cada silo usando várias heurísticas:

    • Status ativo na tabela de membros
    • Sem suspeitas de outros silos
    • Respostas recentes bem-sucedidas da sonda
    • Solicitações de sonda recentes recebidas
    • Rapidez do grupo de threads (itens de trabalho executados dentro de 1 segundo)
    • Precisão do temporizador (disparo dentro de 3 segundos do horário programado)

    A pontuação de saúde de um silo afeta seus tempos limite de sonda: silos insalubres (pontuação de 1 a 8) aumentaram os tempos limite em comparação com silos saudáveis (pontuação 0). Isto tem duas vantagens:

    • Dá mais tempo para que as sondas tenham sucesso quando a rede ou o sistema está sob estresse
    • Torna mais provável que silos prejudiciais sejam considerados eliminados antes que eles possam eliminar incorretamente silos saudáveis

    Isso é particularmente valioso durante cenários como a exaustão do pool de threads, onde nós lentos podem, de outra forma, suspeitar incorretamente de nós saudáveis simplesmente porque não conseguem processar respostas com rapidez suficiente.

  11. Sondagem indireta: Outro recurso inspirado no Lifeguardque melhora a precisão na deteção de falhas, reduzindo a probabilidade de um silo insalubre ou particionado declarar de forma incorreta um silo saudável como morto. Quando um silo de monitoramento tem duas tentativas de sonda restantes para um silo alvo antes de votar para declará-lo morto, ele emprega sondagem indireta:

    • O silo de monitoramento seleciona aleatoriamente outro silo como intermediário e pede que ele sonde o alvo
    • O intermediário tenta entrar em contato com o silo alvo
    • Se o alvo não responder dentro do período de tempo limite, o intermediário envia uma confirmação negativa
    • Se o silo de monitorização receber um reconhecimento negativo do intermediário e este se declarar saudável (através da automonitorização, acima descrita), o silo de monitorização vota para declarar o alvo morto
    • Com a configuração padrão de dois votos necessários, um reconhecimento negativo de uma sonda indireta conta como os dois votos, permitindo uma declaração mais rápida de silos inativos quando a falha é confirmada por várias perspetivas.
  12. Impondo deteção de falhas perfeitas: Uma vez que um silo é declarado morto na tabela, ele é considerado morto por todos, mesmo que não esteja morto (apenas temporariamente particionado ou os sinais de vida tenham sido perdidos). 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 isso não acontece, é necessária outra infraestrutura, como um Serviço do Windows configurado para reiniciar automaticamente em caso de falha ou uma implantação no Kubernetes.

  13. 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 poderá declarar um silo morto; se detetar que algum silo está morto por meio de sondas perdidas, não poderá escrever esse facto na tabela, além de não conseguir permitir que novos silos se juntem. Portanto, a completude sofrerá, mas a precisão não – um particionamento da tabela nunca fará com que Orleans declare o silo como 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.

  14. IAmAlive escreve para diagnóstico e recuperação de desastres:

    Além dos batimentos cardíacos que são enviados entre os silos, cada silo atualiza periodicamente um carimbo de data/hora "Estou vivo" em sua linha na tabela. Isto serve dois propósitos:

    1. Para diagnóstico, ele fornece aos administradores de sistema uma maneira simples de verificar a vida do cluster e determinar quando um silo foi ativo pela última vez. Normalmente, o carimbo de data/hora é atualizado a cada 5 minutos.
    2. Para recuperação de desastres, se um silo não tiver atualizado o seu timestamp por vários períodos (configurado via NumMissedTableIAmAliveLimit), os novos silos irão ignorá-lo durante as verificações de conectividade ao iniciar, permitindo que o cluster se recupere de cenários em que os silos falharam sem a devida limpeza.

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. O repositório Orleans principal contém implementações para muitos sistemas, como Azure Table Storage, Azure Cosmos DB, PostgreSQL, MySQL/MariaDB, SQL server, Apache ZooKeeper, Consul IO, Apache Cassandra, MongoDB, Redis, AWS DynamoDB e uma implementação na memória para desenvolvimento.

  1. 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.

  2. 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 para NEWID(). No SQL Server 2005 e versões posteriores , ROWVERSION é usado. Orleans lê e grava ETags relacionais como tags opacas VARBINARY(16) e as armazena na memória como cadeias de caracteres codificadas em base64 . Orleans suporta inserções de várias linhas usando UNION 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.

  3. 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.

  4. Consul IO - usamos a loja de chaves/valores da Consul para implementar a tabela de membros. Consulte Consul-Deployment para obter mais detalhes.

  5. 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 simultaneidade ETag otimista é feita pelo atributo fazendo gravações condicionais no DynamoDB. A lógica de implementação é bastante semelhante ao Armazenamento de Tabela do Azure.

  6. Apacha Cassandra - Nesta implementação usamos o composto de Service Id e Cluster Id como chave de partição e a identidade do silo (ip:port:epoch) como chave de linha. Juntos, garantem uma fila única por silo. Para controle de simultaneidade, usamos um controle de simultaneidade otimista com base em uma versão de coluna estática usando uma transação leve. Esta coluna de versão é compartilhada para todas as linhas na partição ou cluster, portanto, fornece o número de versão incremental consistente para a tabela de associação de cada cluster. Não há transações de várias linhas nesta implementação.

  7. Emulação na memória para configuração de desenvolvimento. Usamos um grão especial de sistema 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.

Lógica do design

Uma pergunta natural que pode ser feita é por que não confiar completamente em Apache ZooKeeper ou etcd para a implementação de associação de clusters, potencialmente utilizando o suporte pronto-a-usar do ZooKeeper para associação de grupo com nós temporários? Por que nos preocupamos em implementar nosso protocolo de adesão? Havia principalmente três razões:

  1. Implantação/Hospedagem na nuvem:

    Zookeeper não é um serviço hospedado. 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.

  2. 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.

  3. 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.

Propriedades do protocolo de associação

  1. 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.

  2. O tráfego para a mesa é muito leve:

    As sondas 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.

  3. 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 as sondas como mortas ou perdidas permitem alternar entre essas duas opções. Para obter mais informações, consulte Yale University: Computer Science Failure Detectors.

  4. Dimensionamento:

    O protocolo 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.

  5. 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.

  6. Por que precisamos de armazenamento persistente confiável para a implementação do IMembershipTable:

    Usamos armazenamento persistente para o IMembershipTable para duas 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).

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

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 IMembershipTable baseado no ZooKeeper foi feita pelo Shay Hazor, a implementação do SQL IMembershipTable foi feita pelo Veikko Eeva, a implementação do AWS DynamoDB IMembershipTable foi feita pelo Gutemberg Ribeiro e a implementação do IMembershipTable baseado no Consul foi feita pelo Paul North, e finalmente a implementação do Apache Cassandra IMembershipTable foi adaptado do OrleansCassandraUtils por Arshia001.