4장 분산 코디네이터
1. 분산된 환경에서의 고려사항
네임 서비스/부하 분산
부하분산
- L4를 이용한 로드밸런싱 (P145참고)
- 여러대의 WAS 서버를 하나의 VIP로 묶어 L4에 할당
- L4에 할당된 VIP로 DNS 설정
- WAS 서버에서는 Loopback 으로 L4 바인딩 여부 확인(ifconfig로 확인가능)
- 장애 발생시 ifdown으로 L4에서 제거 (요즘 L4 서버는 L7체크 지원으로 자동 제거)
- DNS를 이용한 부하분산 (P145참고)
- 여러대의 WAS 서버 IP 모두 DNS에 등록
- 클라이언트는 도메인 룩업 과정을 통해 물리서버 IP를 찾아감
- 장애가 발생한 경우 DNS 서버에서 해당 서버를 제거해야 함
- DNS 캐시 문제로 클라이언트에 바로 반영되지 않을 수 있음
- L4가 출시되기 전가지 DNS RR이 일반적인 부하분산 방식이었다.
분산 락이나 동기화 문제
- 여러 서버에서 하나의 공유된 자원에 동시에 접근하려고 할 때 동기화를 구현하려면 어떻게 해야 할까?
- NFS(Network File System)와 같은 파일공유 시스템을 사용해야 할 것이다. (ex NAS)
- 서버대수가 많아지면 마운팅 포인트를 관리하는 문제와, NFS 서버 자체가 SPOF(Single Point Of Failure)가 된다.
- SPOF(단일고장점:Single Point Of Failure) : 시스템 구성 요소 중에서, 동작하지 않으면 전체 시스템이 중단되는 요소
- 다른 방법으로 DB를 이용할 수 있지만 서버 부하 및 데드락이 발생 할 수 있다.
장애상황 판단 문제
- 분산환경에서는 중요한 서버인 경우 고가용성을 지원하기 위해 이중화 구성을 한다. (P147참고)
- 엑티브-엑티브 구성 : RAC와 비슷
- 엑티브-스탠바이 구성 : MySQL의 Replication과 비슷
- 둘 다 조금의 문제가 있다.
환경 설정 값 관리
- 환경설정 파일을 .properties파일을 메모리에 로딩하거나, DB에 저장하여 관리하는 방식으로 개발된다.
- 파일에 저장하는 방식은 변경될 때 마다 모든 서버에 배포해야 하는 문제점이 있다.
- DB를 이용하는 방식은 매번 조회해야 하기 때문에 문제가 될수 있다.
- 위와 같은 문제를 해결하기 위해서 많은 오픈소스가 출시되는데 대표적인 오픈소스가 주키퍼 이다.
2. 주키퍼(Apache Zookeeper)
Zookeeper란?
- ZooKeeper는 분산환경의 상호 조정에 필요한 다양한 서비스를 제공하는 동기화가 보장되는 아파치 오픈소스이다.
- 분산 환경에서 락, 네이밍 서비스, 클러스터 멤버십 등을 쉽게 구현할 수 있는 분산 코디네이터 서비스(Distributed Coordinator Service)를 쉽게 만들수 있는 메커니즘을 제공한다.
- 아파치 하둡프로젝트의 서브프로젝트로 시작하여 메인프로젝트로 승격 됨.
- 구글의 처비(chubby)를 많이 참조
- 야후의 분산코디네이터로 사용되고 있으며, 주요 개발자와 프로젝트 스폰서는 야후이다.
- 주키퍼(동물사육사)라는 이름이 붙여진 것은 하둡의 서브프로젝트가 코끼리(hadoop), 거북이(chunkwa), 돼지(pig)등과 같이 동물 이름을 많이 따라, 주키퍼의 역할이 사육사처럼 다양한 서버로 구성된 분산 서버들을 잘 관리하고 조율하는 기능을 수행하기 때문이다.

- 주요 활용 사례
- 네임서비스, 환경설정, 그룹맴버쉽
- Double Barriers
- 우선순위 큐 (Priority Queues)
- 공유 락 제어
- 두 단계 커밋 (Two-phased Commits)
- 리더 선출 (Leader Election)
주키퍼 시스템 구성
- 주키퍼는 분산 환경을 쉽게 관리하는 기능을 제공하지만 주키퍼 역시 여러대의 서버로 구성된 분산 시스템이다.
- 주키퍼는 n개의 서버와 클라이언트 API로 구성되어 있다.
- 파일시스템과 유사하며 데이터를 계층적으로 저장 한다.
- 저장된 데이터는 주키퍼 서버들간에 복제 된다.

- 모든 데이터는 주키퍼 서버들 사이에 동기화돼 관리되며, 클러스터는 항상 3대 이상의 서버로 운영된다.
- 특정 주키퍼 서버에서 장애가 발생하면, 다른 서버로 failover 됨
- 주키퍼에서는 1M 이하의 데이터만 다루도록 서버와 클라이언트 측에서 체크를 한다.
- 만약 평균적으로 이보다 데이터가 크게 되면 오버헤드를 발생시켜 다른 데이터를 처리하는데 지장을 주게 되고 처리 시간을 지연시킨다.
- 만약 많은 데이터를 저장해야 한다면 NFS나 HDFS에 저장하고 해당 저장소의 상세 위치만 주키퍼에 저장하는 방식을 사용하길 권장한다.
주키퍼 설치
wget http://ftp.daum.net/apache/zookeeper/zookeeper-3.4.3/zookeeper-3.4.3.tar.gz
tar -xvf zookeeper-3.4.3.tar.gz
#환경파일 복사
cp /cloud/env/zookeeper-3.4.3/conf/zoo_sample.cfg /cloud/env/zookeeper-3.4.3/conf/zoo.cfg
#환경파일 수정
vi zoo.cfg
dataDir=/cloud/data/zookeeper
clientPort=2181
#시작
/cloud/env/zookeeper-3.4.3/bin/zkServer.sh start
JMX enabled by default
Using config: /cloud/env/zookeeper-3.4.3/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
#중지
/cloud/env/zookeeper-3.4.3/bin/zkServer.sh stop
JMX enabled by default
Using config: /cloud/env/zookeeper-3.4.3/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
#클라이언트실행
/cloud/env/zookeeper-3.4.3/bin/zkCli.sh
데이터 모델
일반 파일 시스템 | 주키퍼 |
---|
디렉토리 구조 | 디렉토리 구조와 비슷한 계층적인 네임스페이스 제공 |
파일에만 데이터를 저장 | 모든 노드에 데이터를 저장 |
로컬에 저장되어 있거나 마운트된 파일과 디렉토리에 접근이 가능 | 클라이언트 라이브러리를 이용해 원격클라이언트 접근 가능 |

- 주키퍼의 모든 데이터는 서버 메모리에 존재하기 때문에 저장 할 수 있는 전체 데이터 용량은 힙 메모리 사이즈를 초과하지 못한다.
- 주키퍼에는 시스템관리, 모니터링, 락 관리 등에 필요한 메타 정보만 저장 하는 것이 일반적이다.
패스
- 주키퍼에서 노드를 구분하는 식별자로, 파일 시스템의 디렉토리 구조와 동이랗게 '/'로 구분되는 문자열이다.
시간
- 하나이상의 서버에서 수행되기 때문에 클라이언트 처리 요청에 따른 버전정보나 시간정보에 대해서 모든 서버가 공유해야 한다.
- 주키퍼는 아래의 방법으로 시간과 버전 정보를 관리한다.
- 트랜잭션 아이디(ZXID:ZooKeeper Transaction Id) : 모든 요청에대해서 순서적으로 부여
- 버전번호 : 노드의 데이터가 변경될 때 마다 버전값이 증가한다.
- 경과시간 : 세션 타임아웃, 커넥션 타임아웃과 같은 이벤트 경과시간
- 시스템시간 : 대부분 사용하지 않지만 노드의 생성,수정 일시는 시스템시간을 사용한다.
z노드
- 계층적인 네임스페이스에서의 각 노드를 z노드라고 한다.
- 위 그림의 /, /app1, /app1/p_1...
- 특징
- 부모 노드에도 데이터 저장할 수 있다.
- 크기가 작은 데이터 위주로 저장
- 버전 : z노드에 저장되는 모든 데이터는 버전을 갖고 있다.
- 접근권한 : z노드 단위로 ACL을 관리 할 수 있다.
- 자바, 파이썬, 펄, C 등의 언어 제공
- z노드 연산과 관련된 JAVA의 Client Library
기능 | 설명 |
---|
exists | 특정 위치에 node가 존재하는지 확인 |
create | 특정 위치에 node를 생성, 1M까지만 가능 |
delete | 특정 node를 삭제 |
getData | node의 데이터를 가져온다. |
setData | node에 데이터를 설정한다. |
getChildren | node의 자식 목록을 가져온다. |
sync | 주키퍼 모든 서버에 복제본이 저장될 때까지 기다린다. |
package com.gurubee.zoo.sample;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
/**
* Zookeeper ZNode JAVA API Sample
*
* @author : oramaster
*
*/
public class ZNodeTest implements Watcher {
private static final String ZK_HOSTS = "10.25.186.1:2181,10.25.186.2:2181,10.25.186.3:2181";
private static final int ZK_SESSION_TIMEOUT = 100 * 1000;
private ZooKeeper zk;
private ZNodeTest() throws Exception {
// Watcher를 impelemnts하고 있기 때문에 this 전달
zk = new ZooKeeper(ZK_HOSTS, ZK_SESSION_TIMEOUT, this);
}
private void execute() throws Exception {
String zNode = "/zk2";
if (zk.exists(zNode, false) == null) {
zk.create(zNode, "zksample".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("Create Node Data : " + new String(zk.getData(zNode, false, null).toString()));
} else {
System.out.println("Node Exists : " + new String(zk.getData(zNode, false, null)));
}
zk.close();
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
ZNodeTest t = new ZNodeTest();
t.execute();
}
@Override
public void process(WatchedEvent event) {
}
}
import org.apache.zookeeper.AsyncCallback.StringCallback;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
/**
* Zookeeper ZNode ASync JAVA API Sample
*
* @author : oramaster
*
*/
public class ZNodeASyncTest implements Watcher {
private ZooKeeper zk;
private Object monitor = new Object();
/**
*
* 콜백 수신
*
*/
class CreateCallBack implements StringCallback {
@Override
public void processResult(int rc, String path, Object ctx, String name) {
if (rc == 0) {
System.out.println(" ### 콜백 수신 성공");
} else {
System.out.println(" ### 콜백 수신 실패");
}
// callback 받으면 테스트 프로그램을 종료시키기 위해 main 흐름으로 알려준다.
synchronized (monitor) {
monitor.notifyAll();
}
}
}
public void start() {
try {
zk = new ZooKeeper("127.0.0.1:2181", 10000, this);
// CreateCallBack 생성
zk.create("/gurubee/test1", "www.gurubee.com".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT,
new CreateCallBack(), null);
System.out.println("Create Node Data : " + new String(zk.getData("/gurubee", false, null).toString()));
System.out.println(" ### 서버로 부터 응답이 오지 않아도 실행된다. !! ");
// callback이 실행되기 전에 종료되지 않게한다.
synchronized (monitor) {
monitor.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
(new ZNodeASyncTest()).start();
}
@Override
public void process(WatchedEvent event) {
}
}
- exists(), create()등 호출하는 기능에 따라 만들어야 하는 콜백 클래스는 조금씩 틀리다.
- create : StringCallback
- delete : VoidCallback
- exists : StatCallback
- getACL : ACLCallback
- getChildren : ChildrenCallback
- getData : DataCallback
- 비동기 호출을 하더라도 처리되는 순서는 주키퍼 서버에 의해 보장된다.
z노드 속성
- Stat : z노드의 상태 정보를 저장한다.
- 부가정보를 Stat객체로 생성한다. 아래 표 참고
속성 | 설명 |
---|
czxid | znode의 생성 트랜잭션에 사용된 zxid |
mzxid | znode가 가장 마지막 수정 트랜잭션에 사용된 zxid |
ctime | znode가 생성된 시스템시간 |
mtime | zonde가 가장 마지막 수정된 시스템시간 |
version | znode의 데이터가 수정된 횟수 |
cversion | znode의 자식 노드가 수정된 횟수 |
aversion | znode의 ACL의 수정된 횟수 |
ephemeralOwner | znode가 임시순번이면 그 zonde를 만든 sessionid |
dataLength | znode의 데이터 길이 |
numChildren | znode의 자식의 수 |
- 와처(Watcher)
- 와처는 세션의 상태가 변경됐거나 관심 있는 노드의 상태 변경(생성,데이터수정,자식노드의 추가/제거, ACL변경)이 발생했을 때 클라이언트가 이벤트를 받아 처리할 수 있게하는 기능을 제공한다.
- 원자적(automic) 데이터 처리
- z노드에 대한 데이터의 조회와 저장은 원자성을 가진다.
- 데이터의 일부가 저장되거나 일부만 조회되거나 하지 않는다.
- 영속성 노드 (Persistent Node)
- 'Persistent' 옵션으로 생성된 z노드는 주키퍼 서버의 로컬디스크에 저장이되고, 클라이언트의 삭제 요청에 의해서만 삭제가 된다.
- 모든 z노드는 생성 옵션에 상관없이 메모리에 존재한다.
- 주키퍼 서버에 저장할 수 있는 용량은 주키퍼 서버의 메모리 용량에 제한된다.
- 임시 노드 (Ephemeral Node)
- 임시노드는 클라이언트의 세션이 끊기면 자동으로 삭제되는 노드다
- 임시노드는 자식을 가질 수 없으며, 임시노드의 부모노드는 반드시 영속성 노드여야 한다.
- 시퀀스 노드 (Sequence Node)
- 분산된 노드에서 유일한 값을 쉽게 만들 수 있다.
접근제한 (ACL, Access Control List)
- 주키퍼의 패스와 노드에 대한 권한 관리는 재귀적이지 않다.
- '/test/sub01' 과 같은경우 '/test'와 '/test/sub01'의 권한은 별도로 설정해야 한다.
- 아무런 설정을 하지 않으면 누구나 접근이 가능하다.
- 접근 권한은 (schema:id, permission) 같은 형태로 설정한다.
- schema : 인증방법
- id : 인증을 허용 할 값
- permission : 처리할 수 있는 권한
- (ip:10.1.1.2, READ) : 10.1.1.2 아이피에 읽기 권한 부여
- 스키마와 권한은 교재 162Page 참고
- 권한목록
권한 | 설명 |
---|
CREATE | 자식 노드를 생성 할 수 있는 권한 |
READ | 데이터를 읽거나 자식 노드를 조회 할 수 있는 권한 |
WRITE | 데이터를 수정할 수 있는 권한 |
DELETE | 자식 노드를 삭제할 수 있는 권한 |
ADMIN | 권한을 설정 할 수 있는 권한 |
권한 | 설명 |
---|
world | 모든 사용자가 접근 가능한 스키마 |
auth | 접근 권한을 설정하기 위한 스키마 |
digest | 패스워드 방식으로 접근 권한을 설정하기 위한 스키마 |
host | 호스트명을 이용해 접근 권한을 설정하기 위한 스키마 |
ip | 클라이언트 아이피를 이용해 접근 권한을 설정 |
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
public class ZNodeACLTest implements Watcher {
private static final String ZK_HOSTS = "10.25.186.1:2181,10.25.186.2:2181,10.25.186.3:2181";
private static final int ZK_SESSION_TIMEOUT = 100 * 1000;
private ZooKeeper zk;
public ZNodeACLTest() throws Exception {
zk = new ZooKeeper(ZK_HOSTS, ZK_SESSION_TIMEOUT, this);
}
private void execute() throws Exception {
List<ACL> acls = new ArrayList<ACL>();
String zNode = "/zk2/02";
// Perms.ALL : 모든 가능한 권한, 아이디/비밀번호로 ACL 설정
acls.add(new ACL(Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("gurubee:1234"))));
// READ, WRITE권한, 아이피로 ACL설정
acls.add(new ACL(Perms.READ | Perms.WRITE, new Id("ip", "10.64.53.179")));
zk.create(zNode, "acl_Data".getBytes(), acls, CreateMode.PERSISTENT);
List<ACL> aclList = zk.getACL(zNode, new Stat());
for (ACL acl : aclList) {
System.out.println(acl.toString());
}
// digest 인증
zk.addAuthInfo("digest", "gurubee:1234".getBytes());
System.out.println(zk.getData(zNode, false, new Stat()));
zk.close();
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
ZNodeACLTest t = new ZNodeACLTest();
t.execute();
}
@Override
public void process(WatchedEvent event) {
}
}
- digest 인증이면 서버에 아이디,패스워드 정보를 전송해야 하는데 아래와 같이 ZooKeeper 클래스의 addAuthInfo 메소드를 이용한다.
zk = new ZooKeeper(ZK_HOSTS, ZK_SESSION_TIMEOUT, this);
zk.addAuthInfo("digest", "gurubee:1234".getBytes());
System.out.println(zk.getData("/zk2/02", false, new Stat()));
세션
- 세션은 클라이언트와 주키퍼 서버와의 연결을 의미한다.
- 세션의 개념은 주키퍼를 이용한 클러스터 멤버십이나 락 제어 같은 활용에서 중요한 개념으로 사용된다.
- 아래는 주키퍼의 세션상태이다.
- CONNECTNG : 클라이언트가 주키퍼 서버 중 하나와 연결을 시도하고 있는 상태
- CONNECTED : 클라이언트가 서버중 하나와 연결된 상태로, z노드 연산을 수행할 수 있다.
- CLOSE : close()메소드 호출에 의해 명시적으로 연결을 종료한상태, 세션 타임아웃, 인증실패도 해당
- DISCONNECTED : 특정 서버와 접속이 끊긴 상태 (서버장애,네트워크장애등) 주키퍼는 세션 클러스터링 해줌
- SESSION EXPIRED : 세션타임아웃 시간을 초과한 상태
이벤트 처리
- 주키퍼는 세션 연결과 읽기 연산시 이벤트를 받을 수 있는 와처(Watcher)를 설정하는 기능을 제공한다.
- 와처는 세션의 상태가 변경됐거나 관심 있는 노드의 상태 변경(생성,데이터수정,자식노드의 추가/제거, ACL변경)이 발생했을 때 클라이언트가 이벤트를 받아 처리할 수 있게하는 기능을 제공한다.
- 아래는 와처의 간단 예제이다.
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
/**
* Class 내용 기술
*
* @author : oramaster
*
*/
public class WatcherTest implements Watcher {
private static final String ZK_HOSTS = "127.0.0.1:2181,127.0.0.1:2181,127.0.0.1:2181";
private static final int ZK_SESSION_TIMEOUT = 20 * 1000;
private ZooKeeper zk;
private WatcherTest() throws Exception {
//Watcher를 impelemnts하고 있기 때문에 this 전달
zk = new ZooKeeper(ZK_HOSTS, ZK_SESSION_TIMEOUT, this);
SampleNodeWatcher nodeWatcher = new SampleNodeWatcher();
zk.exists("/gurubee/test02", nodeWatcher);
}
/**
* {@inheritDoc}
*/
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.None) {
switch (event.getState()) {
case SyncConnected:
System.out.println(" ## ZooKeeper SyncConnected ");
break;
case Disconnected:
System.out.println(" ## ZooKeeper Disconnected ");
break;
case Expired:
System.out.println(" ## ZooKeeper Expired ");
break;
}
}
}
private void exec() {
while (true) {
try {
Thread.sleep(5 * 1000);
long sid = zk.getSessionId();
byte[] passwd = zk.getSessionPasswd();
zk.close();
zk = new ZooKeeper(ZK_HOSTS, ZK_SESSION_TIMEOUT, this, sid, passwd);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
WatcherTest test = new WatcherTest();
test.exec();
}
class SampleNodeWatcher implements Watcher {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeCreated) {
System.out.println(" ######### Node Create : " + event.getPath());
} else if (event.getType() == Event.EventType.NodeDeleted) {
System.out.println(" ######### Node Delete : " + event.getPath());
} else if (event.getType() == Event.EventType.NodeDataChanged) {
System.out.println(" ######### Node Data Change : " + event.getPath());
}
}
}
}
- 주키퍼 이벤트 처리 흐름 (Page 167)
- 클라이언트가 주키퍼 서버에 연결되면 P2P FIFO 네트워크 채널을 생성한다.
- 이 채널은 세션이 종료될 때 까지 유지한다.
- z노드에 와처를 등록하면 클라이언트 주키퍼 메모리에 와처 객체를 등록한다.
- 클라이언트는 서버에 z노드 패스 정보와 클라이언트 호스트정보를 전달한다.
- 서버는 z노드와 와처를 등록한 클라이언트 목록을 가지고 있다.
- 서버의 z노드가 변경되면 와처의 클라이언트를 조회하여 클라이언트 전용채널로 변경 내용을 전달한다.
- 와처 이벤트 타임
- None (세션 연결상태), NodeCreate, NodeDeleted, NodeDataChanged, NodeChildrenChanged
- WatcherEvent객체의 getStat()
- SyncConnected(접속 완료 상태), DisConnected(접속이 끊긴 상태), Expired(세션이 종료된 상태)
- 접속확인 기능 추가
- await()을 호출해 CountDownLatch값이 0이 될 때까지 기다린다.
- SyncConnected 이벤트를 받으면 countDown()을 호출하여 0으로 만든다.
- await()이 깨어나면서 다음 로직을 수행한다.
private CountDownLatch connMonitor = new CountDownLatch(1);
...
connMonitor.await();
...
case SyncConnected:
connMonitor.countDown();
...
- 와처 이벤트 데모
- 와처를 실해한 상태에서 노드 생성 자바를 실행해 보자
## ZooKeeper SyncConnected
######### Node Create : /gurubee/test03
## ZooKeeper Expired
## ZooKeeper Expired
데이터 관리 정책
- DB는 ACID
- Atomic (원자성) : 하나의 트랜잭션은 하나의 단위로 처리가 되어야 한다. 완전히 수행되거나, 아무것도 수행되지 않거나
- Consistency (정합성) : 트랜잭션이 종료된 후에 저장되는 데이터는 오류가 없어야 한다
- Isolated (격리성) : 트랜잭션에 의해서 변경되는 내용은 해당 트랜잭션이 완료되기 전까지 다른 트랜잭션에 영향을 주어서는 안된다
- Durability (영속성) : 트랜잭션이 완료된 후의 값이 영구 저장소에 정상적으로 기록이 되어야 한다.
- 주키퍼는
- 순차적 정합성(Sequential Consistency) : 일정 시간이 지나면 정합성이 맞쳐진다.
- 원자성(Atomic)
- 단일 이미지 뷰 제공(Single System Image) : 어느 주키퍼 서버에 접속하더라도 동일한 데이터뷰를 제공 받는다.
- 안정성(Reliability) : 주키퍼에 저장된 데이터는 영속성 있게 저장된다.
멀티서버 구성과 운영
- 주키퍼는 반드시 멀티서버로 운영해야 한다.
- 서버들 사이에도 네트워크 단절, 트랜잭션 타임아웃, 세션처리, 장애 복구 후 동기화등을 고려해야 한다.
리더 선출
- 주키퍼의 모든 서버는 동일한 데이터를 가지고 있다.
- 마스터 역할을 하는 서버를 리더라고 한다.
- 리더를 미리 정하는게 아니라 클러스터에서 자동으로 리더를 선출한다.
멀티서버 설치
- conf/zoo.cfg
- 첫 번째 포트는 리더에 접속하기 위한 포트이고 두번째 포트는 리더를 선출하기 위한 포트이다.
- dataDir에 myid 파일을 생성해야 한다.
- myid 파일 내용은 각 서버별로 server.x에 해당하는 1, 2, 3을 작성해주면 된다.
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/cloud/data/zookeeper
clientPort=2181
server.1=10.1.1.1:2888:3888
server.2=10.1.1.2:2888:3888
server.3=10.1.1.3:2888:3888
환경 설정 값
- 환경설정 값은 필요할 때 교재 176Page를 참고하면 된다.
- JVM에 대한 설정도 고려해야된다.
클라이언트 요청처리
- 클라이언트:쓰기요청 → 서버1:리더로 포워딩 → 리더:zxid 발급 → 서버3:쓰기작업 → 서버3:ACK 전송 → 리더:커밋요청 → 서버3:처리결과 전송

주키퍼 서버 장애 처리
- 서버 장애가 발생하면 클라이언트에서 Disconnected 이벤트가 발생한다.
- 클라이언트 라이브러리는 자동으로 다른 서버에 접속을 지도한다.
- 서버는 자신의 트랜잭션 아이디(zxid)보다 큰 트랜잭션 아이디를 가진 클라이언트의 접속을 거부한다.
- 178Page 그림참고
- 클라이언트 1은 2, 3번 서버 접속가능
- 클라이언트 2, 3은 3번 서버만 접속가능
커맨드라인 쉘
- z노드의 조회 삭제를 위해 커맨드라인 쉘을 제공함
- bin/zkCli.sh
- 명령어는 교재 179Page 표4.5 참고
C언어 API
분산 락 구현
락
- 하나의 자원에 여러 스레드나 프로세스가 접근을 시할 때 한번에 하나씩만 접근이 필요한 경우 사용한다.
- 락을 획득한 경우만 자원을 사용할 수 있다.
- 자바에서 synchronized 메소드 이용
주키퍼 분산 락 구현
분산 시스템 구성 : 주키퍼 활용
클러스터 멤버십과 네이밍 서비스
- 클러스터 멤버십이란 동일한 기능을 수행하는 서버군(클라스터)에서 현재 실행 중인 서버 목록을 유지하고 새로 추가되거나 장애나 점검 등으로 제거되는 서버를 제거하는 등의 서비스를 의미
중앙 집중형 클러스터 멤버십
- 각 서버에 설치된 에이전트가 정보를 마스터에게 전송한다.
- P192그럼 4.10 마스터 서버가 SPOF(Single Point Of Failure)가 되고, 문제가 발생하면 장애로 연결된다.
- 클라이언트 수가 많아지면 마스터 서버에 부하가 집중될 수 있다.
주키퍼를 이용한 클러스터 멤버십
- P193 그림 4.11
- 주키퍼에 노드명을 서버의 호스트명으로 생성하고 데이터에 성능 등의 정보를 저장한다.
- 서버 신규 추가시 노드를 추가하고, 장애 발생시 노드삭제 이벤트를 받도록 구성한다.
- 데이터 가중치나 CPU/메모리 사용량 같은 성능관련 정보도 저장할 수 있다.
- 데몬중지, 장애 발생시 자동으로 노드를 삭제할 수 있다.
- 클러스터 멤버십 기능을 이용하면 네이밍 서비스를 쉽게 만들 수 있다.
- P193 코드 4.9 HelloServer.java 참고
- P195 코드 4.10 HelloClient.java 참고
이중화 구성
- P199 그림 참고
- 락을 획득하면 엑티브 서버가 됨
- 주키퍼 서버에 엑티브 서버 IP를 set
- 주키퍼 클라이언트에 엑티브 서버 IP 캐시 저장
- 데이터 변경 와처 설정
- 장애발생
- 주키퍼 서버 세션타임아웃으로 락 해제
- 다른 서버(마스터2)에 락획득 이벤트 발생, 엑티브 서버 역할 수행
- 클라이언트는 와처를 통해서 이벤트 전달받고, 엑티브 서버 변경
애플리케이션 환경 설정 관리
- 환경설정을 DB에 저장하면 아래와같은 문제가 있다.
- 매번 조회해야 한다. (?)
- 캐시를 하면 설정값이 변경되면 어플리케이션을 재시작해야 한다.(?)
주키퍼를 이용한 환경설정 저장
- P200 그림참고
- 환경 설정 변수명 : 노드명으로 저장
- 환경 설정 값 : 노드 데이터로 저장
- 서버 처음 시작할 때 환경설정 정보를 HashMap에 저장
- 주키퍼에서 노드가 변경되면 각 서버들은 와처를 통해 변경사항을 수신하여 환경 설정을 변경 할 수 있다.
- P201 소스 참고
생성자/소비자
- 비동기 데이터 전달을 위해 FIFO 큐를 사용
- 주키퍼를 이용한 로직
- 1. 소비자는 시작과 동시에 '/queue' 디렉토리에 와처등록
- 2. 생성자는 '/queue' 노드 아래에 시퀀스 노드 생성, 노드의 내용에 필요한 정보를 저장
- 3. 소비자는 와처를 통해 노드 생성이 됐다는 정보를 받음
- 4. 소비자는 '/queue'의 모든 자식을 가져온다.
- 5. 모든 자식 노드에 대해 순서적으로 삭제 요청
- 6. 삭제가 성공하면 다른 소비자에 의해 처리된 것이 아니므로 자신이 해당 데이터를 처리한다.
즉, 삭제가 되면 자신이 해당 데이터를 소비한게 된다.
- P207 소스 참고
Reference