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를 삭제
getDatanode의 데이터를 가져온다.
setDatanode에 데이터를 설정한다.
getChildrennode의 자식 목록을 가져온다.
sync주키퍼 모든 서버에 복제본이 저장될 때까지 기다린다.
  • zNode JAVA Sync Sample

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) {
        
    }

}

  • zNode JAVA ASync Sample


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객체로 생성한다. 아래 표 참고
속성설명
czxidznode의 생성 트랜잭션에 사용된 zxid
mzxidznode가 가장 마지막 수정 트랜잭션에 사용된 zxid
ctimeznode가 생성된 시스템시간
mtimezonde가 가장 마지막 수정된 시스템시간
versionznode의 데이터가 수정된 횟수
cversionznode의 자식 노드가 수정된 횟수
aversionznode의 ACL의 수정된 횟수
ephemeralOwnerznode가 임시순번이면 그 zonde를 만든 sessionid
dataLengthznode의 데이터 길이
numChildrenznode의 자식의 수
  • 와처(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클라이언트 아이피를 이용해 접근 권한을 설정
  • zNode ACL JAVA 예

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

  • c언어 사용하시는 분 P181~P184 참고

분산 락 구현

  • 하나의 자원에 여러 스레드나 프로세스가 접근을 시할 때 한번에 하나씩만 접근이 필요한 경우 사용한다.
  • 락을 획득한 경우만 자원을 사용할 수 있다.
  • 자바에서 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