Distributed Process Coordination
서버 개발자들에게 주키퍼는 분산 시스템을 다루기 위해 사용되는 중요한 서비스 중 하나다. 하지만 개발자들은 왜 주키퍼라는 이름이 붙었는지에 대해 관심을 가지지는 않는다. (프로그래밍 언어인 자바, 코틀린이 의미하는 바에 관심이 없는 것과 비슷하다. 자바가 인도네시아의 섬이며, 코틀린이 러시아 상트페테르부르크와 인접한 섬이라는 사실을 알고 있었는가!) 주키퍼는 야후 연구개발팀에서 개발된 서비스로, 분산 시스템이 안전하게 작동할 수 있는 간편한 API를 제공한다. 주키퍼가 개발될 당시 야후 개발자들은 마치 자신들이 동물원에 사는 것처럼 보인다고 말했다. (때마침 아파치 피그를 비롯한 다양한 동물들의 이름이 프로젝트에 사용되었기 때문이다.) 이 발언에 착안해 분산 시스템은 다양한 동물들이 살고 있는 동물원이며, 동물원이 제대로 운영되려면 이를 통제하기 위한 사육사, 즉 주키퍼 (Zookeeper)가 필요하다는 생각을 하게 되었다. 주키퍼는 그 이름대로 현재 수많은 분산 처리 시스템을 이루는 수많은 프로세스들이 안전하고 원활하게 서비스할 수 있도록 도와주고 있다.
'주키퍼'라는 이름이 어떻게 탄생되었는지 알았지만, 복잡한 분산 처리 시스템에서 또 하나의 클러스터를 구성해가며 주키퍼를 도입할 필요가 있나 싶다. 하지만 분산 처리 시스템은 한 개의 머신에서 여러 프로세스가 돌아가는 것과 엄청난 차이가 있다. 같은 운영체제 하에서 자원을 공유하며 연산을 처리하는 것과 달리, 분산 시스템은 각각의 어플리케이션이 돌아가는 환경도 다르며 네트워크로 인해 발생하는 다양한 문제도 무시할 수 없다. 또한 장애가 발생했을 때 정확히 진단을 내려줄 수 있는 운영 체제가 없기 때문에 카프카나 엘라스틱 서치 같은 메시징 시스템의 도움 없이는 문제의 원인이 무엇인지 파악하기도 어렵다.
물론 주키퍼를 사용하지 않고 분산 시스템을 설계할 수 있다. 하지만 주키퍼는 개발자들이 동기화를 비롯한 부수적인 요소 대신 서비스가 제공하는 기능과 그 구현 방법에 더 집중할 수 있도록 도와준다.
하지만 주키퍼를 만능으로 생각해서는 안 된다. 주키퍼가 트리 구조를 통해 데이터를 보관할 수 있기는 하지만, 방대한 양의 어플리케이션 데이터를 보관하는 데는 적합하지 않다. 개발자가 주키퍼에 저장해야 할 정보는 제어 또는 조율을 위한 데이터다. 서버들의 설정 정보 또는 클러스터를 구성할 때 마스터가 어떤 서버인지, 각각의 작업에 할당되어 있는 서버가 무엇인지 등의 정보가 바로 주키퍼가 다루는 것들이다.
또한 주키퍼가 직접 어플리케이션의 작업을 조율하는 것은 아니라는 것을 알아야 한다. 분산 시스템을 구성하는 요소들을 조율하는 것은 서버 개발자의 책임이며, 주키퍼는 이를 쉽게 개발할 수 있도록 도와주는 도구일 뿐이다. 개발자는 주키퍼 API를 이용해 동기화나 마스터 선출 등 몇몇 골치 아픈 작업을 쉽게 구현할 수 있다.
CAP 정리라는 것이 있다. CAP는 일관성 (Consistency), 가용성 (Availability), 분할 내성 (Partition-tolerance)을 의미한다. 일관성은 모든 노드가 같은 순간에 같은 데이터를 볼 수 있다는 의미다. 가용성은 성공‧실패 여부와 상관없이 요청에 대한 결과를 반환할 수 있다는 것이다. 분할 내성은 메시지 전달이 실패하거나 일부 시스템에 문제가 발생하더라도 전체 시스템이 원활하게 동작할 수 있음을 의미한다. CAP 정리는 이 세 가지 조건을 모두 만족하는 분산 시스템은 존재하지 않음을 증명한 정리다.
주키퍼를 사용하더라도 분산 시스템에 중요한 위 세 가지 요건을 모두 충족할 수는 없다. (즉, CAP 정리를 뒤엎을 수는 없다는 이야기다.) 하지만 주키퍼를 사용하면 시스템의 일관성과 가용성을 극대화하고, 분할이 발생했을 때도 데이터를 읽는 연산은 가능하다. 주키퍼가 분산 시스템에 발생하는 모든 문제에 대한 완벽한 해결 방법은 될 수 없지만 개발자들에게 편의를 제공함으로써 좀 더 안정되고 일관성 있는 시스템을 구축할 수 있게 도와주는 것이다.
주키퍼의 장점을 요약하면 다음과 같다.
1. 일관성, 순서, 지속성을 보장함
2. 동기화를 위한 프리미티브 (primitive, 기초적인 요소)를 구현 가능
3. 분산 시스템에서 동시성으로 인해 생기는 잘못된 동작 차단
Apache HBase - Hadoop에서 사용되는 HBase는 클러스터 마스터 (cluster master)를 선출하기 위해 주키퍼를 사용한다. 현재 이용 가능한 서버가 어떤 것들이 있는지에 대한 정보를 저장하고, 클러스터의 메타 데이터를 보관하는데 쓰인다.
Apache Kafka - 메시징 시스템으로 널리 쓰이는 카프카 (Kafka)는 카프카 서버의 크래시를 감지하기 위해 사용되며 새로운 토픽이 생성되었을 때, 토픽의 생성과 소비에 대한 상태를 저장하기 위해 주키퍼를 도입했다.
Facebook Messages - 이메일, SMS, 페이스북 챗 등을 통합한 서비스인 페이스북 메시지는 샤딩 (sharding)과 페일오버 (failover) 컨트롤러의 구현을 위해 주키퍼를 사용했다.
주키퍼는 데이터를 트리로 관리한다. 트리의 노드는 데이터를 저장할 수 있는 노드이며, 주키퍼가 트리 내에서 관리된다고 하여 znode라 불린다. znode를 생성 또는 삭제하거나, znode의 data를 읽거나 쓰기 위해선 file system과 비슷한 주키퍼 API를 이용해야 한다. 주키퍼 API가 제공하는 연산은 다음과 같다.
create /path data
/path라는 이름의 znode를 생성하고, znode에 data를 저장한다.
delete /path
/path znode를 삭제한다
exists /path
/path znode가 있는지 확인한다
setData /path data
/path znode에 data를 설정한다
getData /path
/path znode의 data를 얻는다
getChildren /path
/path znode의 자식 node들의 리스트를 얻는다
주키퍼의 특징 중 하나는 znode data의 일부를 쓰거나 읽는 건 불가능하다는 것이다. znode의 data에 접근하는 건 원자성을 가지는 연산, 즉 atomic operation이다. 주키퍼를 사용하는 어플리케이션은 주키퍼 서비스와 연결을 맺으면서 세션 (session)을 생성하며, session을 통해 API를 호출 및 필요한 연산을 수행한다.
Znode를 생성할 때 znode의 모드를 지정할 수 있다. 주키퍼는 Persistent, Ephemral 두 가지 모드를 지원한다. Persistent로 생성할 경우 delete를 통해 명시적으로 지우지 않는 한 znode는 없어지지 않는다. 반면 Ephemeral znode는 생성한 클라이언트가 crash가 발생하거나 주키퍼와 연결이 끊어질 경우에도 삭제된다.
znode의 사용성에 따라 persisten와 ephemeral을 반드시 지정해서 create를 호출해야 한다.
1. Persistent 모드 - znode를 생성한 세션이 시스템에 존재하지 않더라도 영속적으로 보관해야 할 데이터를 저장하는데 유용하다.
2. Ephemeral 모드 - znode를 생성한 세션이 주키퍼 서비스와 연결이 유효할 경우에만 쓰일 정보를 저장하는 데 사용된다. ephemeral znode는 세션이 만료되었을 경우에 자동으로 삭제되므로 자식 노드를 갖지 못하는 특징이 있다.
Ephemeral znode가 필요한 경우에 대해 생각해 보자. master-worker로 구성된 시스템에서 기존의 master가 어떤 이유로 시스템에서 제외되었을 때 다른 백업 master가 이를 대체해야 할 경우를 예로 들 수 있다. 어떤 세션이 마스터인지에 대한 정보를 주키퍼의 /master znode에 보관하고 있다면, 세션이 끊어졌을 때 해당 znode가 삭제되고 백업 마스터가 주키퍼에 새로운 znode를 생성하는 것이 바람직하다. 만약 znode가 삭제되지 않는다면 제 역할을 하지 못 하는 마스터가 주키퍼의 /master znode에 설정되어 있을 것이고, 다른 백업 마스터가 이를 대체하지 못 해 worker에 작업을 할당하지 못 하는 사태가 벌어질 것이다.
이 두 가지 모드에 더해 주키퍼는 sequential이라는 모드도 지원한다. Sequential znode는 생성될 때 znode의 이름에 정수가 순차적으로 더해진다. /task/task-라는 znode를 sequential 모드로 처음 생성할 경우 /tasks/task-1이라는 이름이 붙으며, 똑같은 path의 znode에 대해 create를 호출하면 /tasks/task-2, tasks/task-3과 같이 새로운 노드들이 생성된다. Sequential znode는 유일한 이름의 znode를 생성할 때 유용하며, znode의 생성 순서를 쉽게 확인할 수 있게 도와준다.
즉, 주키퍼에서 persistent, ephemeral, persistent_sequential, ephemeral_sequential 네 가지 모드의 znode를 생성할 수 있다.
주키퍼에 znode가 생성되었는지 또는 znode의 data가 변경되었는지 확인하는 방법을 알아보자.
우선 가장 간단하고 쉬운 방법이 특정 시간 간격으로 계속 주키퍼에 질의하는 폴링 방식을 사용할 수 있다. 하지만 마스터가 작업을 할당하기 위해 주키퍼의 /tasks znode에 대해 getChildren을 반복적으로 호출하는 것은 폴링 방식의 문제점을 극명하게 드러내고 있다. 주키퍼는 원격 서비스로 작동하기 때문에 주키퍼 API를 의미없이 호출하는 것은 자원 및 시간의 낭비다. (물론 주키퍼를 하나의 머신에서 작동하더라도 폴링 방식에는 문제가 많다.) 주키퍼는 클라이언트가 폴링 없이도 znode의 변화를 감지할 수 있도록 알림을 주고 있다. 알림을 받기 위해 클라이언트는 주키퍼의 znode에 대해 와치 (watch)를 등록하기만 하면 된다. 와치가 알림을 받는 방식은 원샷 (one-shot)이므로, 알림을 받은 후에는 다시 와치를 등록해야 이후에 다시 알림을 받을 수 있다.
알림을 사용할 때 고려해야 할 점이 하나 있다면, 알림을 받고 새로운 와치를 설정하는 도중에 znode의 상태가 변할 수 있다는 것이다. 하지만 새로운 와치를 설정할 때 주키퍼 API를 통해 znode의 상태를 확인하므로 알림을 받지 못 하더라도 변화가 생겼다는 것을 알 수 있다. 와쳐만 새로 등록하고 삭제할 수 없는 주키퍼의 특성때문에 알림을 놓친 것이 큰 문제가 되지는 않는다.
주키퍼 API는 그 종류에 따라 다양한 와쳐를 설정할 수 있다. 와쳐를 설정할 수 있는 주키퍼 API는 getData, getChildren, exists가 있으며, API의 종류에 따라 감지할 수 있는 이벤트 종류도 다르다.
znode에는 데이터가 변경될 때마다 증가하는 version이 있다. 주키퍼 API 중 setData와 delete는 버전을 인자로 받으며, 입력으로 넣어준 버전이 실제 버전과 일치할 경우에만 동작을 실행시킨다. 버전에 관계없이 연산을 수행하고 싶으면 버전에 -1을 넣어주면 된다.
버전 관리는 똑같은 znode를 여러 클라이언트가 사용할 경우에 유용하다. 만약 c1이 /config를 설정하고 (버전 2) 이후에 c2가 /config를 바꿨을 경우 (버전 3), c1이 다음 setData를 호출하면 버전을 2로 넣어줄 것이다. 하지만 /config znode의 실제 버전은 c2에 의해 이미 3으로 변경되었으므로, c1의 setData 호출은 실패할 것이다.
주키퍼를 사용하는 어플리케이션은 주키퍼가 제공하는 라이브러리를 이용해 주키퍼 서버에 요청을 보낸다. 주키퍼 서버는 스탠드어론 (standalone) 또는 쿼럼 (quorum)으로 구성된다. 스탠드어론 모드는 말 그대로 하나의 주키퍼 서버가 있는 것이고, 주키퍼 서버의 상태를 복제하는 등의 부가적인 작업이 필요없다. 쿼럼 모드는 주키퍼 서버 여러 대가 하나의 앙상블을 구성하고 있는 형태이며, 각각의 주키퍼가 가지고 있는 상태를 복제하여 일관성을 유지한다.
쿼럼 모드로 주키퍼를 구성할 경우의 장점은 두 가지가 있다. 주키퍼 앙상블은 클라이언트 요청을 각기 다른 서버가 받을 수 있도록 구성하고 있기 때문에 처리할 수 있는 주키퍼 요청의 수도 늘어난다. 따라서 서버 한 대로 주키퍼 트래픽을 감당하는 것이 여의치 않을 경우 쿼럼 모드로 주키퍼를 구성하는 것이 바람직하다.
또한 쿼럼 모드는 주키퍼를 구성하고 있는 서버가 네트워크를 비롯한 일련의 사건으로 인해 클라이언트와 연결이 끊어졌을 경우 이를 유연하게 처리할 수 있는 능력을 갖추고 있다. 클라이언트가 세션을 생성한 서버와 연결이 끊어질 경우, 앙상블 내 다른 서버와 연결을 자동으로 맺어준다. 이로 인해 클라이언트는 연결이 끊어졌을 경우에 다른 서버로 접속하는 등의 예외 처리를 할 필요가 없으며, 원활하게 주어진 작업을 수행할 수 있다.
주키퍼가 클러스터가 아닌 쿼럼으로 구성된 가장 큰 이유는 주키퍼 앙상블이 데이터 복제를 통해 서버 전체가 일관성 있는 정보를 가질 수 있도록 해야 하기 때문이다. 클라이언트가 주키퍼에 요청을 보낸 뒤 모든 서버가 바뀐 정보를 복사하게 되면 응답 시간이 길어져 문제가 될 수 있다. 주키퍼 서버는 쿼럼을 구성하여 응답 시간을 최소화한다. 주키퍼에서 말하는 쿼럼은 주키퍼가 동작하는데 필요한 최소한의 서버 개수다. 또한 클라이언트가 데이터를 안전하게 저장했음을 보장하는 서버 개수를 의미하기도 한다. 주키퍼 앙상블이 다섯 대의 서버로 구성되어 있다고 하자. 이 때 쿼럼은 3이며 세 대의 서버가 클라이언트의 요청을 반영한 znode tree를 가지고 있으면 클라이언트는 요청이 성공했다는 응답을 받는다. 나머지 두 대는 클라이언트에 응답을 보낸 뒤 데이터 복제를 통해 다른 주키퍼 서버와 정합성을 유지한다.
쿼럼 크기를 정하는 것은 주키퍼가 서비스를 제대로 유지하는 데 매우 중요하다. 만약 주키퍼 앙상블이 다섯 대로 구성되어 있으며 쿼럼을 2로 정했다고 가정하자. 클라이언트가 /z라는 znode를 생성하는 요청을 보냈고, 두 대의 서버에 요청이 반영되면 성공했다는 응답을 받는다. 그런데 데이터 복제가 일어나기 전에 파티션이 발생하여 최신 데이터를 가지고 있는 두 대의 서버가 클라이언트와 연결이 끊어질 수 있다. 서버 세 대가 존재하며 쿼럼은 2이기 때문에 클라이언트는 서비스가 정상적으로 동작하고 있다고 판단하지만, /z znode는 어떤 서버에도 존재하지 않는다. 즉, 쿼럼을 잘못 정하면 주키퍼에 저장된 데이터의 영속성을 보장하지 못 한다.
위와 같은 문제로 쿼럼은 n/2 + 1 (n = odd number)로 정해진다. 주키퍼 앙상블이 다섯 대의 서버로 구성되었다면 쿼럼은 3, 일곱 대의 서버로 구성되었다면 쿼럼은 4인 것이다. 주키퍼를 구성할 때 항상 홀수의 서버가 쓰이는 이유는 짝수로 구성했을 때 한 대 적은 홀수와 차이가 없기 때문이다. 3대로 구성된 서비스는 한 대에 크러쉬가 발생하더라도 정상적인 서비스를 제공할 수 있다. 주키퍼를 4대로 구성해도 두 대의 서버가 작동하지 않을 경우 서비스가 중단되기 때문에 굳이 한 대를 추가할 필요가 없다.
클라이언트는 주키퍼와 TCP 연결을 통해 세션을 생성하며, 세션을 통해 주키퍼 API를 호출한다. 세션의 상태는 네 가지로 나뉜다. 세션이 생성되면 NOT_CONNECTED 상태로 출발하며, 주키퍼 서버와 연결을 시도하다가 (CONNECTING) 연결이 성공하면 CONNECTED 상태로 변한다. CONNECTED 상태가 되면 주키퍼 서버에 요청을 보낼 수 있다. 만약 주키퍼 서버와 연결이 끊어지면 앙상블 내 다른 서버와 연결을 맺으려고 시도하며 다시 CONNECTING 상태가 된다. CONNECTING 상태에 있다가 연결을 맺지 못하고 타임아웃이 발생하면 세션은 닫히게 되며 CLOSED로 바뀐다. CONNECTED 상태에 있는 세션이 명시적으로 연결을 끊을 수도 있으며, 이 때도 세션은 CLOSED가 된다.
세션 타임아웃은 주키퍼 세션을 생성할 때 설정할 수 있다. 주키퍼 서비스는 설정된 시간만큼 세션이 유효하다는 것을 보장한다. 타임아웃으로 설정된 시간의 1/3이 되면 세션은 서버에 하트비트 메시지를 보내고, 2/3이 되면 다른 주키퍼 서버를 찾기 시작한다.
주키퍼 클라이언트가 기존 서버와 연결이 끊어지고 다른 서버에 연결될 경우, 서버가 가지고 있는 상태를 확인한다. 클라이언트는 최신 데이터를 볼 수 있어야 하는데, 앙상블 내 일부 서버들에 몇몇 연산이 반영되지 않았을 가능성이 있기 때문이다. 주키퍼는 트랜잭션 아이디 (zxids)를 이용해 변경된 상태를 순서대로 정렬하며 가지고 있는 정보가 최신인지 확인 가능하도록 도와준다. 클라이언트는 새로운 서버와 접속할 때 서버가 가지고 있는 zxid와 자신이 가지고 있는 zxid를 비교한다. 만약 서버 측이 가지고 있는 값이 클라이언트의 것보다 작을 경우 클라이언트는 해당 서버 대신 최신 데이터를 가지고 있는 다른 서버를 찾는다.
주키퍼가 어떤 식으로 작동하고 구성되어있는지 확인했으므로, 주키퍼를 다루기 위해 사용하는 클라이언트 라이브러리에 대해 알아보자.
우선 주키퍼 서버에 요청을 하기 위해선 서버와 연결을 맺을 세션을 생성해야 한다. 주키퍼 라이브러리는 ZooKeeper 클래스를 이용해 서버와 통신할 수 있도록 도와준다. Zookeeper 클래스는 접속할 서버 정보와 세션이 만료될 타임아웃, 이벤트를 받을 와쳐를 인자로 받으며, 세션을 만드는 데 성공하면 이후 클래스의 메소드를 통해 주키퍼의 다양한 API를 이용할 수 있다. 세션은 주키퍼 서버와 연결이 끊어졌을 경우 다른 주키퍼 서버와 붙으려고 시도한다. 이마저도 실패하고 타임아웃이 발생하게 되면 세션은 만료되며, 만료된 세션으로 호출하는 모든 요청은 실패하게 된다.
connectString - 접속할 주키퍼 서비스의 hostname과 port를 문자열 형식으로 넣어준다.
ex. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
sessionTimeout - 주키퍼가 클라이언트로부터 응답이 없을 때 세션이 끊어졌음을 결정하는 시간이다. sessionTimeout이 지나면 주키퍼 서비스는 세션을 만료시킨다.
watcher - 세션에서 발생한 이벤트를 받을 때 사용하는 객체다. Watcher는 인터페이스이며 void process(WatchedEvent e) 함수를 구현한 객체를 넣어주면 된다.
주키퍼는 모든 API에 대해 synchronous, asynchronous 두 가지 형태를 제공한다. synchronous API는 주키퍼 서비스의 동작이 끝날 때까지 기다리며, asynchronous API는 바로 리턴되는 대신 인자로 넘긴 콜백 객체를 이용해 요청이 끝난 뒤 작업을 수행한다. asynchronous 형태의 API를 호출할 경우 클라이언트는 주키퍼 서버에 보낼 요청을 우선 큐에 저장한다. 주키퍼 라이브러리에는 큐에서 요청을 꺼내서 서버로 요청하는 트랜스미션 스레드와 콜백 함수를 호출해 응답을 받는 콜백 스레드가 따로 있다. 요청 순서를 보장하기 위해 콜백 스레드는 싱글 스레드로 작동한다.
주키퍼가 알림을 받을 watcher는 Watcher 인터페이스이며, 이 인터페이스를 구현한 객체는 반드시 void process(WatchedEvent event)를 구현해야 한다.
주키퍼는 세션의 상태가 변경되었을 때 또는 znode에 이벤트가 발생했을 때 와쳐에게 알림을 주며, WatchedEvent 객체를 통해 세션의 상태, 발생한 이벤트, 이벤트가 발생한 znode의 path를 알 수 있다.
KeeperState
Disconnected - 세션의 연결이 끊어짐
SyncConnected - 세션이 주키퍼 서버와 연결이 성공함
AuthFailed - 인증이 실패함
ConnectedReadOnly - Read 연산만 가능한 연결이 맺어짐
SaslAuthenticated - Sasl 인증이 성공함
Expired - 세션이 만료됨
EventType는 다음과 같으며, 이벤트를 감지할 수 있는 주키퍼 API를 함께 표시했다.
NodeCreated - exists
NodeDeleted - exists, getData
NodeDataChanged - exists, getData
NodeChildrenChanged - getChildren
path는 znode에 대한 이벤트가 발생했을 때 znode의 경로를 알려주며, 세션의 상태가 바뀐 경우에는 None이 된다.
주키퍼에 새로운 znode를 생성하기 위해서는 create를 호출해야 한다.
path - znode의 path. 해당 경로에 이미 znode가 있다면 요청은 실패한다.
data - znode에 저장할 data. 반드시 byte array로 변환 후 넣어줘야 한다.
acl - access control list (ACL). znode에 접근할 수 있는 ACL list를 설정한다.
createMode - znode의 모드를 persistent 또는 ephemeral로 설정한다.
cb - 스트링 콜백 오브젝트.
ctx - 콜백 함수에 인자로 들어가는 데이터
asynchronous create를 호출할 때 넘겨주는 인자는 StringCallback 인터페이스이며 인터페이스를 구현한 객체는 반드시 processResult라는 함수를 구현해야 한다.
rc - 콜백함수의 리턴값. 성공했을 경우 OK이며, 실패할 경우 KeeperException 중 하나를 반환한다.
path - 주키퍼 API를 호출했을 때 사용한 znode 경로
ctx - 콜백함수로 넘겨준 데이터
name - 생성된 znode의 이름
znode에 데이터를 설정하기 위해서는 setData를 호출해야 한다.
path - znode의 path.
date - znode에 들어갈 데이터 값
version - znode의 버전
cb - 스탯 콜백 오브젝트
ctx - 콜백 함수에 들어갈 데이터
asynchronous setData를 호출할 때 넘겨주는 인자는 StatCallback 인터페이스이며 인터페이스를 구현한 객체는 반드시 processResult라는 함수를 구현해야 한다.
stat - 값을 설정한 znode가 가진 메타 데이터
znode의 데이터 값을 읽어올 때 사용하는 API
path - znode의 path.
watcher - znode의 데이터가 변경되거나 znode가 삭제되었을 때 알림을 받을 와쳐
cb - 데이터 콜백 오브젝트
ctx - 콜백 함수에 들어갈 데이터
asynchronous setData를 호출할 때 넘겨주는 인자는 DataCallback 인터페이스이며 반드시 processResult라는 함수를 구현해야 한다.
data - znode가 저장하고 있는 데이터
주키퍼가 path로 지정된 znode를 가지고 있는지 확인한다.
path - znode의 path.
watcher - znode의 데이터가 변경되거나 znode가 생성 또는 삭제되었을 때 알림을 받을 와쳐. 즉 exists 요청이 실패하더라도 와쳐가 등록되어 znode의 생성을 감지할 수 있다.
cb - 스탯 콜백 오브젝트
ctx - 콜백 함수에 들어갈 데이터
znode의 자식 노드를 리스트로 얻는다.
path - znode의 path.
watcher - znode가 생성 또는 삭제되었을 때 또는 자식 노드가 생성되거나 삭제되었을 때 알림을 받을 와쳐.
cb - 자식 콜백 오브젝트
ctx - 콜백 함수에 들어갈 데이터
asynchronous getChildren을 호출할 때 넘겨주는 인자는 ChildrenCallback 인터페이스이며 인터페이스를 구현하는 객체는 반드시 processResult라는 함수를 구현해야 한다.
children - znode의 자식 노드를 저장할 리스트
주키퍼가 가지고 있는 znode의 메타 정보는 주키퍼 스탯으로 관리되고 있다. znode의 Stat을 확인하고 싶으면 getData에 Stat 객체를 인자로 넣어주면 된다. 스탯을 구성하고 있는 정보들은 위 이미지에서 확인할 수 있다.
주키퍼는 분산 시스템을 구성할 때 개발자들을 골치 아프게 만드는 동기화 문제를 해결할 수 있도록 도와준다는 것을 알 수 있다. 뿐만 아니라 주키퍼는 개발자가 원하는 프리미티브를 구현할 수 있는 프레임워크기 때문에 다양한 용도로 사용될 수 있다. 예를 들면 Master-Worker 서버를 구성하면서 클라이언트가 요청한 작업을 할당할 때 주키퍼가 사용될 수 있다. 마스터로 지정된 서버 정보를 주키퍼가 가지고 있고, 워커가 생성되면 워커의 주소를 주키퍼에 등록한다. 클라이언트는 요청할 작업을 주키퍼에 등록하기만 하면 마스터가 작업을 워커에 할당하고, 할당한 내역을 주키퍼에 기록하면서 요청을 처리한다. 서버 간 직접적인 통신 없이 주키퍼를 사용하기 때문에 개발자는 동기화나 데이터 복제 등 골치아픈 문제에 대해 생각하지 않아도 된다.
단순히 서버 리스트나 설정 정보를 관리하는 데도 사용되는 등 주키퍼의 역할은 다양하다. 주키퍼를 반드시 사용하라는 법은 없지만 분산 시스템을 구성할 때 주키퍼를 하나의 요소로 등록하는 것도 고려해보자. 사용하기 편한 API, 이벤트를 받을 수 있는 와쳐 등은 개발자들이 주키퍼를 사용하기 쉽게 도와준다. 지금 당장 적용하지 않더라도 주키퍼의 구조와 사용법에 대해 익히면 새로운 시스템을 설계할 때 도움이 되지 않을까?