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

[ZooKeeper] 주키퍼 클라이언트 스레딩 모델과 Java API 연결하기

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

이번 글에서는 Zookeeper의 클라이언트 스레딩 모델에 대해 자세히 알아보고, Java API를 활용하여 Zookeeper와 연결하는 방법까지 단계별로 소개하겠습니다. 이 글을 읽는 독자는 Gradle 기반 프로젝트를 기준으로 따라 할 수 있습니다.


Zookeeper 클라이언트 스레딩 모델

Zookeeper 클라이언트를 생성하면 내부적으로 2개의 추가 스레드가 자동 생성됩니다.

1. IO 스레드

  • 네트워크 통신 처리 담당
  • Zookeeper 서버와의 세션 유지, 요청 및 응답 처리, 세션 타임아웃 관리 등 수행
  • 사용자 코드와 직접 상호작용하지 않음

2. 이벤트 스레드

  • 서버와의 연결 및 해제 등 상태 변화 이벤트 처리
  • 사용자가 등록한 Watcher 트리거 관리
  • 이벤트는 발생 순서대로 하나씩 처리됨

즉, Zookeeper는 이벤트 기반 API로 동작하며, 이벤트 처리 흐름은 단일 스레드로 직렬 실행됩니다.


Maven 프로젝트 설정

pom.xml 파일은 다음과 같이 작성합니다:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>distributed.systems</groupId>
    <artifactId>leader.election</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>LeaderElection</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.12</version>
        </dependency>
    </dependencies>


</project>

 

Gradle 프로젝트 설정

build.gradle 파일은 다음과 같이 작성합니다:

plugins {
    id 'java'
    id 'application'
}

group = 'distributed.systems'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.apache.zookeeper:zookeeper:3.4.12'
}

application {
    mainClass = 'LeaderElection'
}

tasks.withType(JavaCompile) {
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
}

tasks.register('fatJar', Jar) {
    manifest {
        attributes 'Main-Class': application.mainClass
    }
    archiveClassifier.set('all')
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    with jar
}

LeaderElection.java 코드 작성

import org.apache.zookeeper.*;

import java.io.IOException;

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

    public static void main(String[] arg) throws IOException, InterruptedException, KeeperException {
        LeaderElection leaderElection = new LeaderElection();

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

    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();
                    }
                }
        }
    }
}

log4j 설정 파일 추가

리소스 디렉토리 안에 log4j.properties 파일을 만들어 아래 내용을 추가합니다:

log4j.rootLogger=DEBUG, zookeeper
log4j.appender.zookeeper=org.apache.log4j.ConsoleAppender
log4j.appender.zookeeper.Target=System.out
log4j.appender.zookeeper.layout=org.apache.log4j.PatternLayout
log4j.appender.zookeeper.layout.ConversionPattern=%d{HH:mm:ss} %-5p [%c] - %m%n

필요 시 DEBUG WARN으로 낮추어 불필요한 로그 출력을 줄일 수 있습니다.


실행과 디버깅

  1. 프로그램을 실행하면 이벤트 스레드에서 연결 메시지를 출력합니다.
  2. Zookeeper 서버를 종료하면 연결 해제 이벤트가 발생합니다.
  3. 이벤트 핸들러에서 메인 스레드를 깨우고 종료 처리를 합니다.

지속적으로 핑을 보내서 확인하는 모습이다.

 

 

주키퍼 서버를 정지했더니, 이벤트 핸들러가 실행되어 문구가 출력된것을 볼 수 있다.


마무리

이번 글에서는 Zookeeper 클라이언트의 스레딩 모델 구조를 이해하고, Java API를 통해 Zookeeper와 연결하는 간단한 애플리케이션을 구현해보았습니다.

이 흐름은 이후 구현할 리더 선출 알고리즘의 기초가 됩니다.

다음 글에서는 이 구조 위에 리더 선출 로직을 덧붙여보겠습니다.

계속해서 리더 선출 알고리즘 구현 편으로 이어집니다.

반응형

댓글