Как эффективно использовать рецепт LeaderElection с помощью куратора для Zookeeper?

Я использую библиотеку Apache Curator для проведения выборов лидеров в Zookeeper. У меня есть код приложения, развернутый на разных машинах, и мне нужно выполнить мой код с одной машины только потому, что я делаю выборы лидеров в zookeeper, чтобы я мог проверить, являюсь ли я лидером, а затем выполнить этот код.

Ниже мой класс LeaderElectionExecutor, который гарантирует, что у меня есть один экземпляр куратора для каждого приложения

public class LeaderElectionExecutor {

    private ZookeeperClient zookClient;

    private static final String LEADER_NODE = "/testleader";

    private static class Holder {
        static final LeaderElectionExecutor INSTANCE = new LeaderElectionExecutor();
    }

    public static LeaderElectionExecutor getInstance() {
        return Holder.INSTANCE;
    }

    private LeaderElectionExecutor() {
        try {
            String hostname = Utils.getHostName();

            String nodes = "host1:2181,host2:2181;

            zookClient = new ZookeeperClient(nodes, LEADER_NODE, hostname);
            zookClient.start();

            // added sleep specifically for the leader to get selected
            // since I cannot call isLeader method immediately after starting the latch
            TimeUnit.MINUTES.sleep(1);
        } catch (Exception ex) {
            // logging error
            System.exit(1);
        }
    }

    public ZookeeperClient getZookClient() {
        return zookClient;
    }
}

И ниже мой код ZookeeperClient -

// can this class be improved in any ways?
public class ZookeeperClient {

    private CuratorFramework client;
    private String latchPath;
    private String id;
    private LeaderLatch leaderLatch;

    public ZookeeperClient(String connString, String latchPath, String id) {
        client = CuratorFrameworkFactory.newClient(connString, new ExponentialBackoffRetry(1000, Integer.MAX_VALUE));
        this.id = id;
        this.latchPath = latchPath;
    }

    public void start() throws Exception {
        client.start();
        leaderLatch = new LeaderLatch(client, latchPath, id);
        leaderLatch.start();
    }

    public boolean isLeader() {
        return leaderLatch.hasLeadership();
    }

    public Participant currentLeader() throws Exception {
        return leaderLatch.getLeader();
    }

    public void close() throws IOException {
        leaderLatch.close();
        client.close();
    }

    public CuratorFramework getClient() {
        return client;
    }

    public String getLatchPath() {
        return latchPath;
    }

    public String getId() {
        return id;
    }

    public LeaderLatch getLeaderLatch() {
        return leaderLatch;
    }
}

Теперь в моем приложении я использую такой код:

public void method01() {
    ZookeeperClient zookClient = LeaderElectionExecutor.getInstance().getZookClient();
    if (zookClient.isLeader()) {
        // do something
    }
}

public void method02() {
    ZookeeperClient zookClient = LeaderElectionExecutor.getInstance().getZookClient();
    if (zookClient.isLeader()) {
        // do something
    }
}

Проблема: -

В библиотеке куратора - вызов isLeader() сразу после запуска защелки не будет работать. Для лидера требуется время. И только по этой причине я добавил спать 1 минута в моем LeaderElectionExecutor коде, который отлично работает, но я думаю, что это неправильный способ сделать это.

Есть ли лучший способ сделать это? Помня об этом, мне нужен способ проверить, являюсь ли я лидером, затем исполню этот кусок кода. Я не могу сделать все в одном методе, поэтому мне нужно вызвать метод isLeader из разных классов и методов, чтобы проверить, являюсь ли я лидером, а затем выполнять только этот фрагмент кода.

Я использую Zookeeper 3.4.5 и версию Curator 1.7.1.

Ответ 1

Как только я решил проблему, очень похожую на вашу. Вот как я это сделал.

Во-первых, у меня были объекты, которыми управлял Spring. Итак, у меня был LeaderLatch, который можно вводить через контейнер. Одним из компонентов, которые использовали LeaderLatch, была LeadershipWatcher, реализация интерфейса Runnable, которая отправила событие лидерства другим компонентам. Этими последними компонентами были реализации интерфейса, который я назвал LeadershipObserver. Реализация LeadershipWatcher была в основном похожа на следующий код:

@Component
public class LeadershipWatcher implements Runnable {
  private final LeaderLatch leaderLatch;
  private final Collection<LeadershipObserver> leadershipObservers;

  /* constructor with @Inject */

  @Override
  public void run() {
    try {
      leaderLatch.await();

      for (LeadershipObserver observer : leadershipObservers) {
        observer.granted();
      }
    } catch (InterruptedException e) {
      for (LeadershipObserver observer : leadershipObservers) {
        observer.interrupted();
      }
    }
  }
}

Поскольку это всего лишь эскиз, я рекомендую вам улучшить этот код, возможно, применить шаблон команды для вызова наблюдателей или даже отправить наблюдателей в пулы потоков, если их работа блокируется или длительный интенсивный процессор задачи.

Ответ 2

leaderLatch = new LeaderLatch(curatorClient, zkPath, String.valueOf(new Random().nextInt()));
leaderLatch.start();
Participant participant;
while(true) {
  participant = leaderLatch.getLeader();
  // Leader election happens asynchronously after calling start, this is a hack to wait until election happens
  if (!(participant.getId().isEmpty() || participant.getId().equalsIgnoreCase(""))) {
    break;
  }
}
if(leaderLatch.hasLeadership()) {
...
}

Обратите внимание, что getLeader возвращает фиктивного участника с id "", пока он не выберет лидера.

Ответ 3

Здесь для возрождения старого вопроса...

Это похоже на ответ srav, но я бы предостерег от использования этого кода, потому что он использует ожидание-ожидание и может вызывать определенные обратные вызовы, выпущенные в потоке, чтобы никогда не вызываться, возможно, блокируя навсегда. Кроме того, он может повторять попытку навсегда, если есть реальные проблемы.

Это было мое решение, в котором используется политика повтора CuratorClient, чтобы попытаться в ожидании выбора руководства.

    RetryPolicy retryPolicy = _client.getZookeeperClient().getRetryPolicy();
    RetrySleeper awaitLeadership = _leaderLatch::await;

    final long start = System.currentTimeMillis();
    int count = 0;

    do {
        try {
            // curator will return a dummy leader in the case when a leader has
            // not yet actually been elected. This dummy leader will have isLeader
            // set to false, so we need to check that we got a true leader
            if (_leaderLatch.getLeader().isLeader()) {
                return;
            }
        } catch (KeeperException.NoNodeException e) {
            // this is the case when the leader node has not yet been created
            // by any client - this is fine because we are still waiting for
            // the algorithm to start up so we ignore the error
        }
    } while (retryPolicy.allowRetry(count++, System.currentTimeMillis() - start, awaitLeadership));

    // we have exhausted the retry policy and still have not elected a leader
    throw new IOException("No leader was elected within the specified retry policy!");

Если вы посмотрите на свою инициализацию CuratorFramework, я бы предостерег от использования Integer.MAX_VALUE при указании политики повтора...

Надеюсь, это поможет!

Ответ 4

Я раньше не работал с зоопарком или куратором, поэтому отвечай мой ответ с солью.

Установите флаг.

Boolean isLeaderSelected = false;

В начале защелки установите флаг в значение false. Когда лидер выбран, установите флаг в true.

В функции isLeader():

isLeader(){
while(!isLeaderSelected){} //waits until leader is selected

//do the rest of the function
}

Это также довольно хакерское решение, но оно должно позволить методу isLeader выполнять, как только это возможно. В случае, если они находятся в разных классах, геттер должен иметь возможность предоставлять isLeaderSelected.