주키퍼 기초개념

2020-03-13

.

Data_Engineering_studynote(20200313)

  • 학습한 컨텐츠

1) TOMO님의 ‘브런치 IT초짜의 개발일지 매거진 - Zookeeper’ 글 요약정리

2) URL : https://brunch.co.kr/@timevoyage/77

  • 개요

주키퍼는 분산시스템을 다루기 위한 서비스 중 하나로 분산처리 시스템을 이루는 다수의 프로세스들이 안전하고 원활하게 서비스 할 수 있도록 도와준다.

  • 분산 처리시스템을 다루기 어려운이유

같은 운영체제 하에서 자원을 공유하며 연산을 처리하는 일반적인 컴퓨팅 방식과 달리, 분산시스템은 각각의 어플리케이션들이 구동되는 환경도 다르고, 네트워크로 인해 발생하는 다양한 문제도 무시할 수 없다. 또한 장애가 발생했을때 정확히 진단을 내려줄 수 있는 운영체제도 따로 없기 때문에 카프카나 일라스틱 서치 같은 메세징 시스템의 도움없이는 문제의 원인이 무엇인지 파악하기 어렵다.

  • 주키퍼의 필요성

개발자가 어플리케이션이 구동되기 위한 데이터(서버들의 설정정보 또는 클러스터를 구성할 때 마스터가 어떤 서버인지, 각각의 작업에 할당되어 있는 서버가 무엇인지 등의 정보)를 주키퍼가 다룬다.

주키퍼가 직접 어플리케이션의 작업을 조율하는 것은 아님

분산시스템을 구성하는 요소들을 조율하는 것은 서버개발자의 책임이고, 주키퍼는 이런 작업을 쉽게 개발할 수 있도록 도와주는 도구일 뿐이다. 개발자는 주키퍼 API를 이용해 동기화나 마스터 선출 등 작업을 쉽게 구현할 수 있다.

주키퍼는 시스템의 일관성과 가용성을 최대화 시켜주고, 분할이 발생했을때도 데이터를 읽는 연산은 가능하다. 주키퍼가 만능은 아니지만 가용성과 일관성을 높여줄 수 있는 편의를 제공하여 좀 더 안정되고 일관성 있는 시스템을 구축할 수 있게 도와준다.

  • 주키퍼의 장점

1) 일관성, 순서, 지속성을 보장

2) 동기화를 위한 기초적인 요소를 구현가능

3) 분산시스템에서 동시성으로 인해 생기는 잘못된 동작을 차단

  • 주키퍼가 사용되는 서비스

1) HBase

클러스터 마스터를 선출하기 위해 주키퍼를 사용한다. 현재 이용가능한 서버가 어떤 것들이 있는지에 대한 정보를 저장하고, 클러스터의 메타데이터를 보관하는데 쓰인다.

2) Kafka

카프카 서버의 crash를 감지하기 위해 사용되며 새로운 토픽이 생성되었을때, 토픽의 생성과 소비에 대한 상태를 저장하기 위해 주키퍼를 이용

  • 주키퍼 API

주키퍼는 데이터를 트리로 관리한다. 트리의 노드는 데이터를 저장할 수 있는 노드이며, 주키퍼가 트리내에서 관리된다고하여 znode라고 불린다. znode를 생성하거나 삭제할때, znode의 데이터를 읽거나 쓰기 위해서는 file system과비슷한 주키퍼 API를 이용해야 한다.

주키퍼를 사용하는 어플리케이션은 주키퍼 서비스와 연결을 맺으면서 세션을 생성하며, 세션을 통해 API를 호출하고 필요한 연산을 수행한다.

  • znode의 모드

znode를 생성할때 모드를 아래와 같이 두가지를 지정할 수 있다.

  1. Persistent

1) delete를 통해 명시적으로 지우지 않는 한 znode는 없어지지 않는다.

2) znode를 생성한 세션이 시스템에 존재하지 않더라도 영구적으로 보관해야 할 데이터를 저장하는데 유용

  1. Ephemeral

1) 생성한 클라이언트가 crash가 발생하거나 주키퍼와 연결이 끊어진 경우에 znode가 삭제된다.

2) znode를 생성한 세션이 주키퍼 서비스와 연결이 유효할 경우에만 쓰일 정보를 저장하는데 사용됨

3) 세션이 만료되었을 경우에 자동으로 삭제되므로 자식노드를 찾지 못하는 경우가 있음

  1. persistent_sequential & emphemeral_sequential

znode가 생성될때 znode의 이름에 정수가 순차적으로 더해진다. 유일한 이름의 znode를 생성할때 유용하며, 생성순서도 쉽게 파악할 수 있게 도와준다.

  • Ephemeral znode가 필요한 경우

마스터 슬레이브 구조의 클러스터 시스템에서 기존에 마스터가 어떤 이유로 클러스터에서 이탈되었을때 따른 백업 마스터가 이를 대체해야 할 경우

어떤 세션이 마스터인지에 대한 정보를 주키퍼의 /master znode에 보관하고 있다면, 세션이 끊어졌을때 해당 znode가 삭제되고 백업 마스터가 주키퍼에 새로운 znode를 생성하는 것이 바람직하다. 만약 znode가 삭제되지 않는다면 제 역할을 하지 못하는 마스터가 주키퍼의 /master znode에 설정되어 있을 것이고, 다른 백업 마스터가 이를 대체하지 못해 워커에 작업을 할당하지 못하는 경우가 발생할 수 있음

  • watch & notification

주키퍼에 znode가 생성되었는지 또는 znode의 데이터가 변경되었는지 확인하는 방법

가장 간단하고 쉬운방법은 특정 시간간격으로 계속 주키퍼에게 질의하는 폴링방식이 있다. 하지만 마스터가 작업을 할당하기 위해 주키퍼의 /tasks znode에 대해 getChildren을 반복적으로 호출하는 자원낭비의 여지도 있다.

주키퍼는 클라이언트가 폴링 없이도 znode의 변화를 감지할 수 있도록 알림을 주는 기능이 있다. 알림을 받기위해 클라이언트는 주키퍼의 znode에 대해 watch를 등록하기만하면 된다. watch가 알림을 받는 방식은 원샷이므로, 알림을 받은 후에는 다시 와치를 등록해야 이후에 다시 알림을 받을 수 있다.

알림을 사용할때 알림을 받고 새로운 watch를 설정하는 도중에 znode의 상태가 변할 수 있다는 것을 고려해야 한다. 하지만 새로운 watch를 설정할 때 주키퍼 API를 통해 znode의 상태를 확인하므로 알림을 받지 못하더라도 변화가 생겼다는 것을 알 수 있다. watch만 새로 등록하고 삭제할 수 없는 주키퍼 특성때문에 알림을 놓친것이 큰 문제가 되지는 않는다. 주키퍼 API는 종류에 따라 다양한 watch를 설정할 수 있다.

  • version

znode에는 데이터가 변경될때마다 버전이 변동된다. 주키퍼 API중 setData와 delete는 버전을 인자로 받으며, 입력으로 넣어준 버전이 실제버전과 일치하는 경우에만 동작을 한다. 버전에 관계없이 연산을 수행하고 싶으면 버전에 -1을 넣어주면 된다.

버전관리는 똑같은 znode를 여러 클라이언트가 사용할 경우에 유용하다. 만약 c1이 /config를 설정하고 (버전2) 이후에 c2가 /config를 바꿨을때 (버전 3), c1이 다음 setData를 호출하면 버전 2로 넣어줄 것이다. 하지만 /config znode의 실제버전은 c2에 의해 이미 3으로 변경되었으므로, c1의 setData는 호출에 실패할 것이다. 다시말해서 버전이 맞지 않아 연산이 실패하는 경우가 있을 수 있다는 점을 고려해야 한다.

  • 주키퍼 구조

주키퍼를 사용하는 어플리케이션은 주키퍼가 제공하는 라이브러리를 이용해 주키퍼 서버에 요청을 보낸다. 주키퍼 서버는 standalone 또는 quorum으로 구성된다. standalone 모드는 말 그대로 하나의 주키퍼 서버가 있는것이고, 주키퍼 서버의 상태를 복제하는 등의 부가적인 작업이 필요없다. 반면에 quorum 모드는 주키퍼 서버 여러대가 하나의 앙상블을 구성하고 있는 형태이며, 각각의 주키퍼가 가지고 있는 상태를 복제하여 일관성을 유지한다.

주키퍼가 클러스터가 아닌 quorum으로 구성된 이유는 주키퍼 앙상블이 데이터 복제를 통해 서버전체가 일관성 있는 정보를 가질 수 있도록 해야하기 때문이다. 클라이언트가 주키퍼에 요청을 보낸다음에 모든서버가 바뀐정보를 복사하게 되면 응답시간이 길어져 문제가 될수 있다. 주키퍼 서버는 quorum을 구성하여 응답시간을 최소화한다.

주키퍼에서 말하는 quorum은 주키퍼가 동작하는데 필요한 최소한의 서버개수다. 또한 클라이언트가 데이터를 안전하게 저장했음을 보장하는 서버개수를 의미하기도 한다.

주키퍼 앙상블이 다섯대의 서버로 구성되어 있다고 하자. 이때 quorum은 3이며 세대의 서버가 클라이언트의 요청을 반영한 znode tree를 가지고 있으면 클라이언트는 요청이 성공했다는 응답을 받는다. 나머지 두대는 클라이언트에 응답을 보낸 뒤 데이터 복제를 통해 다른 주키퍼 서버와 정합성을 유지한다.

quorum 크기를 정하는 것은 주키퍼가 서비스를 제대로 유지하는데 중요하다. 만약에 주키퍼 앙상블이 다섯대로 구성되어 있으며 quorum을 2로 정했다고 가정하자. 클라이언트가 /z라는 znode를 생성하는 요청을 보냈고, 두대의 서버에 요청이 반영되면 성공했다는 응답을 받는다. 그런데 데이터 복제가 일어나기 전에 파티션이 발생하여 최신데이터를 가지고 있는 두대의 서버가 클라이언트와 연결이 끊어질 수 있다. 서버 세대가 존재하며 quorum은 2이기 때문에 클라이언트는 서비스가 정상적으로 동작하고 있다고 판단하지만, /z znode는 어떤서버에도 존재하지 않는다.

이런문제로 quorum은 홀수단위로 정해주는게 일반적이다. 주키퍼 앙상블이 다섯대의 서버로 구성된다면 quorum은 3, 일곱대의 서버로 구성한다면 quorum은 5인 것이다. 주키퍼를 구성할때 항상 홀수의 서버가 쓰이는 이유는 짝수로 구성했을때 한대 적은 홀수와 차이가 없이 때문이다. 3대로 구성된 서비스는 한대에 크러쉬 값이 발생하더라도 정상적인 서비스를 제공할 수 있다. 주키퍼 4대를 구성해도 두대의 서버가 작동하지 않을 경우 서비스가 중단되기 때문에 굳이 한대를 추가할 필요가 없다.

  • session의 상태 및 수명

클라이언트는 주키퍼와 TCP 연결을 통해 session을 생성하며, session을 통해 주키퍼 API를 호출한다. session의 상테는 4가지로 나뉘어진다. session이 생성되면 NOT_CONNCETED 상태로 출발하며, 주키퍼 서버와 연결을 시도하다가 (CONNCETING) 연결이 성공하면 CONNCETED 상태로 변한다. CONNCETED 상태가 되면 주키퍼 서버에 요청을 보낼 수 있다. 만약에 주키퍼 서버와 연결이 끊어지면 앙상블 내 다른 서버와 연결을 맺으려고 시도하며 다시 CONNCETING 상태가 된다. CONNECTING상태에 있다가 연결을 맺지 못하고 타임아웃이 발생하면 session은 끊기게 되며 CLOSED로 바뀐다. CONNCETED 상테에 있는 session이 명시적으로 연결을 끊을 수도 있으며, 이때도 CLOSED라고 부른다.

session 타임아웃은 주키퍼 세션을 생성할 때 설정할 수 있다. 주키퍼 서비스는 설정된 시간만큼 세션이 유효하다는 것을 보장한다. 타임아웃으로 설정된 시간의 1/3이 되면 세션은 서버에 하트비트 메세지를 보내고 2/3이 되면 다른 주키퍼 서버를 찾기 시작한다.

주키퍼 클라이언트가 기존서버와 연결이 끊어지고 다른서버에 연결될 경우, 서버가 가지고 있는 상태를 확인한다. 클라이언트는 최신 데이터를 볼 수 있어야 하는데, 앙상블 내 일부 서버들이 몇몇 연산이 반영되지 않았을 가능성이 있기 때문이다. 주키퍼 트랜젝션 아이디(zxids)를 이용해 변경된 상태를 순서대로 정렬하며 가지고 있는 정보가 최신인지 확인 가능하도록 도와준다. 클라이언트는 새로운 서버와 접속할 때 서버가 가지고 있는 zxid와 자신이 가지고 있는 zxid를 비교한다. 만약 서버측이 가지고 있는 값이 클라이언트의 것보다 작을 경우 클라이언트는 해당서버 대신 최신 데이터를 가지고 있는 다른 서버를 찾는다.