Pergunta

Eu tenho trabalhado nisso por alguns dias agora, e eu encontrei várias soluções, mas nenhum deles incrivelmente simples ou leve. O problema é basicamente este: Temos um cluster de 10 máquinas, cada uma das quais está executando o mesmo software em uma plataforma ESB multithread. Sou capaz de lidar com problemas de simultaneidade entre threads na mesma máquina com bastante facilidade, mas que sobre a concorrência nos mesmos dados em máquinas diferentes?

Essencialmente, o software recebe solicitações de feed de dados de um cliente de uma empresa para outra através de serviços web. No entanto, o cliente pode ou não existir ainda no outro sistema. Se isso não acontecer, nós criá-lo através de um método de serviço web. Por isso requer uma espécie de test-and-set, mas eu preciso de um semáforo de algum tipo para bloquear as outras máquinas de causar condições de corrida. Eu tive situações antes onde um cliente remoto foi criado duas vezes para um único cliente local, que não é realmente desejável.

Soluções eu brincava com conceitualmente são:

  1. Usando nosso tolerante a falhas sistema de arquivos compartilhado para criar arquivos de "lock" que serão controlados por cada máquina, dependendo do cliente

  2. Usando uma tabela especial em nosso banco de dados, e trancar a tabela inteira, a fim de fazer um "test-and-set" para um registro de bloqueio.

  3. Usando Terracotta, um software de servidor de código aberto, que auxilia na escala, mas usa um modelo hub-and-spoke.

  4. Usando EHCache para replicação síncrona de meus in-memory "fechaduras".

Eu não posso imaginar que eu sou a única pessoa que já teve este tipo de problema. Como você resolvê-lo? Você cozinhar alguma coisa em casa ou você tem um produto favorito 3rd-festa?

Foi útil?

Solução

que você pode querer considerar o uso Hazelcast fechaduras distribuídos. Super lite e fácil.

java.util.concurrent.locks.Lock lock = Hazelcast.getLock ("mymonitor");
lock.lock ();
try {
// do your stuff
}finally {
   lock.unlock();
}

Hazelcast - Distributed Queue, Mapa, Set, List, Bloqueio

Outras dicas

Nós usamos Terracotta, então eu gostaria de voto para isso.

Eu tenho acompanhado Hazelcast e parece que outra tecnologia promissora, mas não pode votar por ele desde que eu não usei, e sabendo que ele usa um sistema baseado P2P no seu ouvido, eu realmente não confiaria -lo para grandes necessidades de escala.

Mas eu também ouvi de Zookeeper, que saiu do Yahoo, e está se movendo sob a égide Hadoop. Se você é aventureiro experimentar uma nova tecnologia isso realmente tem muita promessa, já que é muito magra e média, concentrando-se em apenas coordenação. Eu gosto da visão e promessa, embora possa ser muito verde ainda.

Terracotta está mais perto de um modelo "hierárquico" - todos os aplicativos cliente falar com uma matriz Terracotta Server (e mais importante para a escala eles não falam um com o outro). A matriz Terracotta Server é capaz de ser agrupados para ambos escala e disponibilidade (espelhado, disponibilidade, e listrado, para a escala).

Em qualquer caso, como você provavelmente sabe Terracotta dá-lhe a capacidade de expressar a simultaneidade em todo o cluster da mesma forma que você faz em uma única JVM usando POJO sincronizado / wait / notificar ou usando qualquer um dos primitivos java.util.concurrent tais como ReentrantReadWriteLock, CyclicBarrier, AtomicLong, FutureTask e assim por diante.

Há um monte de receitas simples que demonstram a utilização destas primitivas no Terracotta Cookbook .

Como exemplo, vou postar o exemplo ReentrantReadWriteLock (nota não existe uma versão "Terracota" da fechadura - basta usar Java normal ReentrantReadWriteLock)

import java.util.concurrent.locks.*;

public class Main
{
    public static final Main instance = new Main();
    private int counter = 0;
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true);

    public void read()
    {
        while (true) {
            rwl.readLock().lock();
                try {
                System.out.println("Counter is " + counter);
            } finally {
                rwl.readLock().unlock();
            }
            try { Thread.currentThread().sleep(1000); } catch (InterruptedException ie) {  }
        }
    }

    public void write()
    {
        while (true) {
            rwl.writeLock().lock();
            try {
               counter++;
               System.out.println("Incrementing counter.  Counter is " + counter);
            } finally {
                 rwl.writeLock().unlock();
            }
            try { Thread.currentThread().sleep(3000); } catch (InterruptedException ie) {  }
        }
    }

    public static void main(String[] args)
    {
        if (args.length > 0)  {
            // args --> Writer
            instance.write();
        } else {
            // no args --> Reader
            instance.read();
        }
    }
}

Eu recomendo usar Redisson . Ele implementa mais de 30 estruturas e serviços de dados distribuídos, incluindo java.util.Lock. Exemplo de uso:

Config config = new Config();
config.addAddress("some.server.com:8291");
Redisson redisson = Redisson.create(config);

Lock lock = redisson.getLock("anyLock");
lock.lock();
try {
    ...
} finally {
   lock.unlock();
}

redisson.shutdown();

Eu estava indo para conselhos sobre como usar memcached como, um armazenamento de memória RAM muito rápido distribuídos por manter registros; mas parece que EHCache é um projeto semelhante, mas mais java-centric.

Qualquer um é o caminho a percorrer, contanto que você está certo de usar atualizações atômicas (suportes memcached eles, não sei sobre EHCache). É de longe a solução mais escalável.

Como datapoint, o Google usa 'Chubby' relacionado, um rápido, de armazenamento de bloqueio distribuído RAM baseada em como a raiz de vários sistemas, entre eles BigTable.

Eu fiz um monte de trabalho com coerência, o que permitiu várias abordagens para implementar um bloqueio distribuído. A abordagem ingênua era de pedir para bloquear o mesmo objeto lógico em todos os nós participantes. Em termos de coerência este era de bloqueio de uma chave em um cache replicado. Esta abordagem não escala muito bem, pois o tráfego da rede aumenta linearmente como você adicionar nós. Uma forma mais inteligente era usar um cache distribuído, onde cada nó no cluster é naturalmente responsável por uma parte do espaço de chave, por isso o bloqueio de uma chave em tal cache de uma comunicação sempre envolvida com, no máximo, um nó. Você poderia rolar sua própria abordagem com base nesta ideia, ou melhor ainda, obter coerência. É realmente o kit de ferramentas de escalabilidade dos seus sonhos.

Gostaria de acrescentar que qualquer rede de multi-nó decente metade baseado mecanismo de bloqueio teria que ser razoavelmente sofisticado para agir corretamente em caso de qualquer falha de rede.

Não tenho certeza se eu entender o contexto inteiro, mas parece que você tem um banco de dados único backing isso? Por que não fazer uso de bloqueio do banco de dados: se criar o cliente é um único INSERIR então esta declaração só pode servir como um bloqueio desde que o banco de dados irá rejeitar uma segunda INSERT que violaria uma das suas limitações (por exemplo, o fato de que o nome do cliente é único, por exemplo).

Se a "inserção de um cliente" operação não é atômica e é um lote de instruções então eu iria introduzir (ou uso) um INSERT inicial que cria algum registro básica simples identificar o seu cliente (com as restrições singularidade necessárias) e depois fazer todas as inserções outros / atualizações na mesma transação. Mais uma vez o banco de dados vai cuidar de consistência e quaisquer modificações concorrentes irá resultar em um deles falhar.

Eu fiz um serviço simples RMI com dois métodos: bloqueio e liberação. Ambos os métodos de levar a chave (meu modelo de dados UUID usado como pk de modo que foi também a chave de bloqueio).

RMI é uma boa solução para isso, porque ele está centralizado. você não pode fazer isso com EJBs (specialially em um cluster como você não sabe em qual máquina a sua chamada vai pousar). Além disso, é fácil.

ele trabalhou para mim.

Se você pode configurar o balanceamento de carga de modo que os pedidos de um único cliente sempre são mapeados para o mesmo servidor, então você pode lidar com isso através de sincronização local. Por exemplo, tomar o seu ID do cliente mod 10 para encontrar qual dos 10 nós para uso.

Mesmo se você não quiser fazer isso no caso geral os nós poderia proxy para o outro para este tipo específico de solicitação.

Assumindo que seus usuários são suficientes uniforme (ou seja, se você tem uma tonelada deles) que você não espera hot spots de pop-up, onde um nó fica sobrecarregado, este ainda deve escalar muito bem.

Você também pode considerar Cacheonix para fechaduras distribuídos. Ao contrário de qualquer outra coisa mencionado aqui Cacheonix fechaduras apoio ReadWrite com o escalonamento de bloqueio de leitura para gravação quando necessário:

ReadWriteLock rwLock = Cacheonix.getInstance().getCluster().getReadWriteLock();
Lock lock = rwLock.getWriteLock();
try {
  ...
} finally {
  lock.unlock();
}

Divulgação completa: Eu sou um desenvolvedor Cacheonix

.

Uma vez que você já está conectado a um banco de dados, antes de adicionar outro pedaço infra, dar uma olhada em JdbcSemaphore , é simples de usar:

JdbcSemaphore semaphore = new JdbcSemaphore(ds, semName, maxReservations);
boolean acq = semaphore.acquire(acquire, 1, TimeUnit.MINUTES);
if (acq) {
 // do stuff
 semaphore.release();
} else {
  throw new TimeoutException();
}

É parte do spf4j biblioteca.

De volta ao dia, usaríamos um "servidor de bloqueio" específico na rede para lidar com isso. Bleh.

O seu servidor de banco de dados pode ter recursos especificamente para fazer este tipo de coisa. MS-SQL Server tem fechaduras de aplicação utilizável através do sp_getapplock / sp_releaseapplock procedimentos.

Temos vindo a desenvolver uma fonte aberta, framework de sincronização distribuído, atualmente DistributedReentrantLock e DistributedReentrantReadWrite bloqueio foi implementado, mas ainda estão em teste e refatoração fase. Em nossas chaves arquitetura de bloqueio são divididos em baldes e cada nó é resonsible para certo número de baldes. Então, efetivamente por solicitações de bloqueio bem sucedido, há apenas uma solicitação de rede. Também estamos usando a classe AbstractQueuedSynchronizer como estado de bloqueio local, para que todas as solicitações de bloqueio fracassadas são tratadas localmente, o que reduz drasticamente trafic rede. Estamos usando JGroups ( http://jgroups.org ) para comunicação em grupo e de Hesse para serialização.

Para mais detalhes, confira http://code.google.com/p/vitrit/ .

Por favor me envie o seu feedback valioso.

Kamran

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top