Zookeeper
주키퍼
분산 코디네이션 서비스. 설정 관리, 네이밍. Kafka와 함께.
주키퍼
분산 코디네이션 서비스. 설정 관리, 네이밍. Kafka와 함께.
Apache Zookeeper는 분산 시스템에서 설정 관리, 네이밍 서비스, 분산 동기화, 그룹 서비스를 제공하는 중앙 집중식 코디네이션 서비스입니다. 2008년 Yahoo!에서 개발되어 Apache 프로젝트로 기증되었으며, "동물원 관리인"이라는 이름처럼 Hadoop, Kafka, HBase 등 다양한 분산 시스템(동물 이름의 프로젝트들)을 조율합니다.
Zookeeper의 핵심 개념은 ZNode입니다. 파일 시스템과 유사한 계층적 네임스페이스를 제공하며, 각 ZNode는 데이터(최대 1MB)와 자식 노드를 가질 수 있습니다. Ephemeral Node(임시 노드)는 세션 종료 시 자동 삭제되어 장애 감지에 활용되고, Sequential Node는 자동 증가 번호가 붙어 분산 락과 Leader Election에 사용됩니다.
Zookeeper 앙상블은 보통 3대, 5대, 7대 등 홀수 개의 서버로 구성됩니다. ZAB(Zookeeper Atomic Broadcast) 프로토콜을 통해 과반수(Quorum) 동의를 얻어야 쓰기가 완료되므로, 3대 구성에서는 1대, 5대 구성에서는 2대까지 장애를 허용합니다. 읽기는 모든 노드에서 가능하지만, 쓰기는 반드시 Leader를 통해야 합니다.
Kafka 0.x~2.x 버전에서 Zookeeper는 필수 의존성이었습니다. 브로커 메타데이터, 토픽 설정, 파티션 리더 선출, 컨슈머 그룹 오프셋 관리 등을 담당했습니다. 그러나 Kafka 3.x부터는 KRaft(Kafka Raft) 모드가 도입되어 Zookeeper 없이도 운영이 가능해졌으며, 4.0에서는 완전히 제거될 예정입니다.
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
public class ZookeeperExample {
private static final String ZK_ADDRESS = "localhost:2181";
private static ZooKeeper zk;
public static void main(String[] args) throws Exception {
// Zookeeper 연결
zk = new ZooKeeper(ZK_ADDRESS, 3000, event -> {
System.out.println("연결 상태: " + event.getState());
});
// ZNode 생성 (Persistent)
String path = zk.create("/myapp/config",
"value".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
System.out.println("생성된 노드: " + path);
// Ephemeral Node 생성 (세션 종료 시 자동 삭제)
zk.create("/myapp/workers/worker-",
"host:port".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 데이터 읽기 (Watch 설정)
Stat stat = new Stat();
byte[] data = zk.getData("/myapp/config", event -> {
System.out.println("데이터 변경 감지!");
}, stat);
System.out.println("데이터: " + new String(data));
// 자식 노드 목록 조회
zk.getChildren("/myapp", false)
.forEach(child -> System.out.println("자식: " + child));
// Leader Election (간단한 예시)
String leaderPath = zk.create("/election/candidate-",
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 가장 작은 번호의 노드가 Leader
}
}
from kazoo.client import KazooClient
from kazoo.recipe.lock import Lock
from kazoo.recipe.election import Election
# Zookeeper 연결
zk = KazooClient(hosts='localhost:2181')
zk.start()
# 노드 생성 (부모 경로 자동 생성)
zk.ensure_path("/myapp/config")
zk.create("/myapp/config/db_host", b"localhost:5432")
# 데이터 읽기/쓰기
data, stat = zk.get("/myapp/config/db_host")
print(f"값: {data.decode()}, 버전: {stat.version}")
# 데이터 업데이트 (낙관적 락)
zk.set("/myapp/config/db_host", b"newhost:5432", version=stat.version)
# Watch 설정 (데이터 변경 감지)
@zk.DataWatch("/myapp/config/db_host")
def watch_config(data, stat):
if data:
print(f"설정 변경됨: {data.decode()}")
# 분산 락 구현
lock = Lock(zk, "/locks/my-resource")
with lock:
print("락 획득! 임계 영역 실행 중...")
# 중요한 작업 수행
# Leader Election
election = Election(zk, "/election", "worker-1")
def leader_func():
print("나는 리더다! 작업 분배 시작...")
election.run(leader_func) # 리더가 되면 함수 실행
# Ephemeral 노드로 서비스 등록 (서비스 디스커버리)
zk.create("/services/api-server/node-",
b"192.168.1.10:8080",
ephemeral=True,
sequence=True)
zk.stop()
# Zookeeper CLI 시작
bin/zkCli.sh -server localhost:2181
# 노드 생성
create /myapp ""
create /myapp/config "initial_value"
# Ephemeral 노드 생성 (세션 종료 시 삭제)
create -e /myapp/temp "session_data"
# Sequential 노드 생성 (자동 번호 부여)
create -s /myapp/seq- "data"
# 결과: /myapp/seq-0000000001
# 데이터 조회
get /myapp/config
# 출력: initial_value + stat 정보
# 데이터 수정
set /myapp/config "new_value"
# 자식 노드 목록
ls /myapp
# [config, temp, seq-0000000001]
# Watch 설정 후 조회
get -w /myapp/config # 변경 시 알림
# 노드 삭제
delete /myapp/temp
deleteall /myapp # 하위 포함 전체 삭제
# Zookeeper 4글자 명령어 (모니터링)
echo stat | nc localhost 2181 # 서버 상태
echo mntr | nc localhost 2181 # 상세 메트릭
echo cons | nc localhost 2181 # 연결된 클라이언트
echo srvr | nc localhost 2181 # 서버 정보
# Leader/Follower 확인
echo stat | nc localhost:2181 | grep Mode
# Mode: leader 또는 Mode: follower
"현재 Kafka 2.8을 쓰고 있어서 Zookeeper 3대가 필요합니다. Kafka 3.5로 업그레이드하면서 KRaft 모드로 전환하면 Zookeeper 클러스터를 제거할 수 있어서 운영 복잡도가 줄어듭니다. 마이그레이션은 Rolling Upgrade로 진행하면 다운타임 없이 가능합니다."
"마이크로서비스 간 분산 락이 필요하면 Zookeeper의 Ephemeral Sequential Node를 사용하면 됩니다. Redis 분산 락보다 일관성이 보장되고, 노드 장애 시 락이 자동 해제되어 데드락 위험이 없습니다. 다만 Redis보다 레이턴시가 높으니 초당 수천 건 이상이면 Redis Redlock을 고려해야 합니다."
"Zookeeper Leader Election은 ZAB 프로토콜 기반입니다. 각 서버가 자신의 zxid(트랜잭션 ID)를 비교해서 가장 높은 zxid를 가진 서버가 Leader가 됩니다. Quorum(과반수) 동의를 얻어야 Leader로 확정되고, 쓰기 요청은 모두 Leader를 통해 복제됩니다. 이로써 강한 일관성(Linearizability)을 보장합니다."
ZNode당 최대 1MB까지만 저장 가능하며, 설정 데이터나 메타데이터용으로 설계되었습니다. 대용량 데이터는 DB에 저장하고 Zookeeper에는 포인터만 저장하세요.
4대 구성은 3대와 동일하게 1대 장애만 허용합니다. Quorum이 과반수이기 때문입니다. 반드시 3, 5, 7처럼 홀수로 구성하세요. 프로덕션에서는 5대를 권장합니다.
Session Timeout이 너무 짧으면 네트워크 지연으로 Ephemeral 노드가 의도치 않게 삭제됩니다. 기본값 6초는 대부분 안전하며, GC가 긴 JVM 앱이면 30초 이상으로 늘리세요.
앙상블 서버들은 별도 물리 서버나 다른 AZ에 배치. dataDir과 dataLogDir은 SSD에 분리. JVM 힙은 4~8GB로 설정하고, maxClientCnxns로 연결 수 제한하세요.