본문 바로가기
Server-side 개발 & 트러블 슈팅/🦍 ZooKeeper (주키퍼)

[ZooKeeper] 주키퍼 기반 리더 선출 알고리즘 구현

by 코딩하는 동현 2025. 4. 12.

Zookeeper 기반 리더 선출 알고리즘 구현

이번 글에서는 Zookeeper를 활용한 리더 선출 알고리즘 구현, Jar 패키징 후 실행 할 예정이고,

다음 실습에서  장애 감지 및 군집 효과(Herd Effect) 대응 전략과 재선출 알고리즘을 설명하겠습니다.


리더 선출 알고리즘 구현

Zookeeper는 분산 시스템의 코디네이터 역할을 수행할 수 있도록 강력한 API를 제공합니다. 그 중 하나가 리더 선출 메커니즘입니다. 핵심은 다음과 같습니다:

  • 각 클러스터 노드는 Zookeeper에 /election이라는 부모 노드 아래에 EPHEMERAL_SEQUENTIAL 모드의 자식 노드를 등록합니다.
  • Zookeeper는 이 자식 노드들에 대해 고유한 순번을 부여합니다.
  • 노드 중 가장 작은 번호를 가진 노드가 리더가 됩니다.

Java 구현 예시 (LeaderElection.java)

String znodePrefix = "/election/c_";
String fullPath = zooKeeper.create(znodePrefix, new byte[]{}, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
this.currentZnodeName = fullPath.replace("/election/", "");

리더 여부 확인은 다음과 같이 진행됩니다:

List<String> children = zooKeeper.getChildren("/election", false);
Collections.sort(children);
if (children.get(0).equals(currentZnodeName)) {
    System.out.println("I am the leader");
} else {
    System.out.println("I am not the leader, " + children.get(0) + " is the leader");
}

 

 

전체코드

import org.apache.zookeeper.*;

import java.io.IOException;
import java.util.Collections;
import java.util.List;


public class LeaderElection implements Watcher {
    private static final String ZOOKEEPER_ADDRESS = "localhost:2181";
    private static final int SESSION_TIMEOUT = 3000;
    private static final String ELECTION_NAMESPACE = "/election";
    private ZooKeeper zooKeeper;
    private String currentZnodeName;

    // NOTE - Don't forget to create the /election ZNode
    public static void main(String[] arg) throws IOException, InterruptedException, KeeperException {
        LeaderElection leaderElection = new LeaderElection();

        leaderElection.connectToZookeeper();
        leaderElection.volunteerForLeadership();
        leaderElection.electLeader();
        leaderElection.run();
        leaderElection.close();
        System.out.println("Disconnected from Zookeeper, exiting application");
    }

    public void volunteerForLeadership() throws KeeperException, InterruptedException {
        String znodePrefix = ELECTION_NAMESPACE + "/c_";
        String znodeFullPath = zooKeeper.create(znodePrefix, new byte[]{}, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        System.out.println("znode name " + znodeFullPath);
        this.currentZnodeName = znodeFullPath.replace("/election/", "");
    }

    public void electLeader() throws KeeperException, InterruptedException {
        List<String> children = zooKeeper.getChildren(ELECTION_NAMESPACE, false);

        Collections.sort(children);
        String smallestChild = children.get(0);

        if (smallestChild.equals(currentZnodeName)) {
            System.out.println("I am the leader");
            return;
        }

        System.out.println("I am not the leader, " + smallestChild + " is the leader");
    }

    public void connectToZookeeper() throws IOException {
        this.zooKeeper = new ZooKeeper(ZOOKEEPER_ADDRESS, SESSION_TIMEOUT, this);
    }

    private void run() throws InterruptedException {
        synchronized (zooKeeper) {
            zooKeeper.wait();
        }
    }

    private void close() throws InterruptedException {
        this.zooKeeper.close();
    }

    @Override
    public void process(WatchedEvent event) {
        switch (event.getType()) {
            case None:
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    System.out.println("Successfully connected to Zookeeper");
                } else {
                    synchronized (zooKeeper) {
                        System.out.println("Disconnected from Zookeeper event");
                        zooKeeper.notifyAll();
                    }
                }
        }
    }
}

2. 배포 가능한 Jar 패키징

리더 선출 프로그램은 다양한 서버에 배포될 수 있어야 하므로, 종속성을 모두 포함한 단일 .jar 파일로 패키징해야 합니다.

Maven을 사용하는 경우

pom.xml maven-assembly-plugin 추가:

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  ...
  <configuration>
    <archive>
      <manifest>
        <mainClass>LeaderElection</mainClass>
      </manifest>
    </archive>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
  </configuration>
</plugin>

Gradle을 사용하는 경우

jar {
    manifest {
        attributes(
            'Main-Class': application.mainClass
        )
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    archiveClassifier.set('jar-with-dependencies')
}

빌드 명령:

  • Maven: mvn clean package
  • Gradle: ./gradlew jar

리더 선출 실행

주키퍼 서버 실행

# mac
zkServer start

# windows, linux
zkServer.sh start

 

 

터미널에서 주키퍼 CLI 실행:

# mac
zkCli

# windows, linux
zkCli.sh

 

 

/election Z노드 생성

create /election ""

 

 

 

각자 다른 터미널/노드에서 jar 파일 실행

java -jar leader-election-jar-with-dependencies.jar

 

실행 및 리더 선정 결과

 
반응형

댓글