Preciso criar um mecanismo \ padrão de bloqueio de objeto recursivo personalizado para um sistema distribuído em C #. Basicamente, eu tenho um sistema com vários nós. Cada nó tem permissões de gravação exclusivas em n- número de partes do estado. O mesmo estado também está disponível no formato somente leitura em pelo menos um outro nó. Algumas gravações / atualizações devem ser atômicas em todos os nós, enquanto outras se tornarão consistentes através de processos de replicação em segundo plano, filas, etc.
Para as atualizações atômicas, estou procurando um padrão ou exemplos que permitam marcar com eficiência um objeto como bloqueado para gravações, para que eu possa distribuir, confirmar, reverter, etc ... Como o sistema possui altos níveis de simultaneidade, eu estou assumindo que precisarei ser capaz de empilhar bloqueios que expirarão ou serão desenrolados quando os bloqueios forem liberados.
As partes da transação ou do sistema de mensagens não são o foco desta pergunta, mas forneci a elas um contexto extra. Com isso dito, fique à vontade para articular quais mensagens você acha que seriam necessárias, se quiser.
Aqui está uma amostra vaga do que eu estava imaginando, embora eu esteja aberto a novas idéias além de implementar produtos totalmente novos
thing.AquireLock(LockLevel.Write);
//Do work
thing.ReleaseLock();
Eu estava pensando em usar métodos de extensão, que podem ser algo como isto
public static void AquireLock(this IThing instance, TupleLockLevel lockLevel)
{
//TODO: Add aquisition wait, retry, recursion count, timeout support, etc...
//TODO: Disallow read lock requests if the 'thing' is already write locked
//TODO: Throw exception when aquisition fails
instance.Lock = lockLevel;
}
public static void ReleaseLock(this IThing instance)
{
instance.Lock = TupleLockLevel.None;
}
Para esclarecer alguns detalhes ...
- Todas as comunicações são TCP / IP usando um protocolo de solicitação / resposta binário
- Não há tecnologias intermediárias, como filas ou bancos de dados
- Não há nó principal central. Nesse caso, o arranjo de bloqueio é definido pelo iniciador do bloqueio e pelo parceiro que atenderá à solicitação com alguma forma de tempo limite para controlar seu comportamento
Alguém tem alguma sugestão?
Respostas:
Obrigado pelos esclarecimentos.
Nesse caso, o que eu recomendaria é usar um modelo de publicação / assinatura. Protocolo de bloqueio distribuído Chubby do Google (uma implementação do Paxos )
Eu nunca usei Paxos (ou Chubby), mas parece haver uma implementação de código aberto aqui .
Se isso não funcionar, você pode implementar sua própria versão do Paxos usando, por exemplo, um dos suspeitos comuns em termos de bibliotecas de mensagens: a biblioteca da fila de mensagens zero , RabbitMQ ou ActiveMQ .
Resposta anterior:
A maioria das sugestões em SO ( [A] , [B] ) é usada para usar uma fila de mensagens para obter o bloqueio entre máquinas.
Seu
AcquireLock
método enviaria algo que identificasse o objeto de bloqueio para a fila, procurando por instâncias anteriores de bloqueios antes do sucesso. SeuReleaseLock
método removeria o objeto de bloqueio da fila.O usuário do SO atlantis sugere, neste post , o post de Jeff Key para alguns detalhes.
fonte
Parece-me que você tem algumas tecnologias mistas aqui:
comunicações (nas quais você essencialmente confia como 100% confiável ... o que pode ser fatal)
bloqueio / exclusão mútua
tempos limite (para qual finalidade)?
Uma palavra de aviso: Os tempos limite em sistemas distribuídos podem estar repletos de perigos e dificuldades. Se usados, eles devem ser definidos e usados com muito cuidado, porque o uso indiscriminado de intervalos não corrige um problema, apenas adia a catástrofe. (Se você quiser ver como os intervalos devem ser usados, leia e entenda a documentação do protocolo de comunicação HDLC. Este é um bom exemplo de uso adequado e inteligente, em combinação com um sistema de codificação de bits inteligente para permitir a detecção de coisas como a linha IDLE) .
Durante algum tempo, trabalhei em sistemas distribuídos com vários processadores conectados usando links de comunicação (não TCP, outra coisa). Uma das coisas que aprendi foi que, como uma generalização grosseira, existem alguns lugares perigosos de multiprogramação para ir:
a dependência de filas geralmente termina em lágrimas (se a fila ficar cheia, você estará com problemas. A menos que você possa calcular um tamanho de fila que nunca será preenchido; nesse caso, você provavelmente poderá usar uma solução sem fila)
a dependência do bloqueio é dolorosa, tente e pense se existe outra maneira (se você deve usar o bloqueio, consulte a literatura, o bloqueio distribuído por vários processadores tem sido objeto de muitos artigos acedêmicos das últimas 2-3 décadas)
Se você precisar continuar usando o bloqueio, então:
Assumirei que você usará os tempos limite apenas como um meio de recuperação de último recurso - ou seja, para detectar uma falha do sistema de comunicações subjacente. Assumirei ainda que o seu sistema de comunicação TCP / IP é de alta largura de banda e pode ser considerado baixa latência (idealmente zero, mas isso nunca acontece).
O que eu sugeriria é que cada nó tem uma lista de conectividade de outros nós aos quais pode se conectar. (Os nós não se importam de onde vem uma conexão.) A população das tabelas às quais um nó pode se conectar é deixada como uma coisa separada a ser resolvida, você não disse se isso seria definido estaticamente ou não. Também convenientemente ignorado são coisas como a alocação dos números de porta IP em que as conexões entrariam em um nó - pode haver boas razões para aceitar solicitações em apenas uma única porta ou em várias portas. Isso precisa ser cuidadosamente considerado. Os fatores incluirão filas implícitas, pedidos, uso de recursos, tipo e recursos do sistema operacional.
Depois que os nós souberem com quem se conectam, eles podem enviar para esse nó uma solicitação de bloqueio e devem receber de volta uma resposta de bloqueio desse nó remoto. Você pode agrupar essas duas operações em um wrapper para torná-lo atômico. O efeito disso é que os nós que desejam adquirir um bloqueio farão uma chamada algo como:
as chamadas get_lock e release_lock devem ser algo como (em princípio):
Você precisará tomar muito cuidado, com um sistema de bloqueio distribuído, para que as unidades de trabalho executadas enquanto um bloqueio seja mantido sejam pequenas e rápidas, pois você terá muitos nós remotos potencialmente retidos aguardando o bloqueio. Este é efetivamente um sistema de multiprocessador / comunicação para-e-espera que é robusto, mas não apresenta o melhor desempenho possível.
Uma sugestão é adotar uma abordagem completamente diferente. Você pode usar uma chamada de procedimento remoto em que cada chamada RPC carrega um pacote de informações que podem ser tratadas pelo destinatário e que remove as necessidades de bloqueios?
Ao reler a pergunta, parece que você realmente não quer se preocupar com o lado da comunicação, apenas deseja resolver seu problema de bloqueio.
Minha resposta pode, portanto, parecer um pouco fora de tópico, no entanto, acredito que você não pode resolver seu problema de bloqueio sem acertar as peças abaixo. Analogia: Construir uma casa com fundações ruins faz com que ela caia ... Eventualmente.
fonte
Sua pergunta pode ser facilmente implementada usando um cache distribuído como o NCache. O que você precisa é de um mecanismo de bloqueio pessimista, no qual você pode adquirir um bloqueio usando um objeto. Em seguida, execute suas tarefas e operações e libere a trava para outros aplicativos consumirem posteriormente.
Dê uma olhada no código a seguir;
Aqui você adquire um bloqueio em uma chave específica e, em seguida, executa tarefas (variando de uma ou mais operações) e, finalmente, libera o bloqueio quando terminar.
Retirado do link: http://blogs.alachisoft.com/ncache/distributed-locking/
fonte