Consistent Hashing과 Memecached를 이용한 테스트 샘플
07 Jul 2018 | docker softwareengineering dockercompose memcached springbootConsistent Hashing 이란?
웹 서버의 개수가 수시로 변경될 때 요청에 대해 분산하는 방법으로 Key의 집합을 K, 노드(또는 서버)의 크기를 N라고 했을 때, N의 갯수가 바뀌더라도 대부분의 키들이 노드를 그대로 사용할 수 있습니다.
예를 들어 10000개의 key값을 5개의 노드에 분산할 경우 2000개씩 나뉘는데 개중 하나의 노드가 죽더라도 전체를 재 분배하는 것이 아니라 2000개의 key에 대해서만 재분배 합니다.
예를 들어 일반 Hash를 이용하여 노드를 분산할 경우 아래 그림과 같은 방식으로 접근 할 수 있습니다.
3으로 나눈 나머지 값을 이용하여 3개 노드중 하나를 접근하도록 설정 할 수 있는데 이런 방식의 경우 하나의 노드가 죽었을 경우 모든 key값에 대해 다시 연산해야하는 문제가 생깁니다.
Consistent Hashing의 경우 각 노드를 링의 개념으로 연결하여 각 key가 포함되어야 하는 특정 구간을 결정하게 됩니다.
이 과정에서 하나의 노드가 죽는다 하여도 해당 노드에 속한 Key값은 다음 구간을 담당하는 노드에서 처리하게 되므로 나머지 노드의 key를 재분배 하지 않아도 됩니다.
Example: HashRing
위에서 설명한 링의 개념에 대해 실제 key에 따라 어떻게 노드를 결정하는지 아래와 같이 예시를 만들어보았습니다.
분배규칙 : A < 1, B < 3, C < 5, D < 7
다음과 같은 규칙일 때
맨 처음 ‘key=1’이 인입 될 경우 아래와 같이 위치 합니다.
그 다음 ‘key=4’가 인입되면 아래와 같이 위치 합니다.
그 외 여러 key값이 인입하게 되어 아래와 같은 상황이 되었습니다.
이 때 노드 B가 죽고 ‘key=1’이 유실됩니다.
노드 B가 죽은 상태에서 ‘key=1’이 다시 인입되면 B의 다음 구간인 C에 포함되게 됩니다.
위와 같이 특정 노드가 죽더라도 전체 key를 재분배 하지 않게 됩니다.
하지만 Hashring에도 문제가 있는데 아래와 같이 노드 B가 되살아나고
노드 B가 되살아난 상태에서 다시 ‘key=1’가 인입되면 아래 그림과 같이 중복된 key와 value가 존재하게 됩니다.
위 상황에서 다시 노드 B가 죽을 경우 노드 C에 있던 이전 값이 노출 될 수 있는 문제가 있는데 memcached의 경우 만료시간을 설정하여 해결 할 수 있습니다.
가상노드
특정 노드가 죽으면 해당 key가 모두 다른 노드에 분배되는데 위 예제에서 보면 노드 B가 죽을 경우 노드 C에 key가 몰리는 것처 보이지만 사실상 아래 그림과 같이 각 노드는 가상의 노드를 여러개 만들어 노드가 죽었을때도 균등하게 분배되도록 처리합니다.
Memcached 샘플파일
위 이론을 바탕으로 실제 Memcached를 이용하여 Consistent Hashing이 어떻게 동작하는지 확인 할 수 있는 샘플 프로젝트입니다.
Key에 대한 분배및 Proxy 역활은 소스에 포함되어 있는 Simple-Spring-Memcached(ssm)
에서 담당하게되며 git을 설치 후 clone 명령을 이용하여 다운받을 수 있습니다.
$ git clone https://github.com/jistol/docker-compose-memcached-multi-test-sample.git
구성
- Gradle
- Spring Boot
- Google Simple Spring Memcached (SSM)
Memcached 실행
-
Docker 설치
- Memcached Image 다운로드
- docker pull memcached
- docker-compose를 이용하여 실행
- ${PROJECT_HOME}/docker-compose up -d
- 위와 같이 실행하면 아래와 같이 3개의 포트로 memcached 서버가 기동됩니다.
$ docker-compose up -d
Creating network "memcached_default" with the default driver
Creating memcached_memcached3_1 ... done
Creating memcached_memcached1_1 ... done
Creating memcached_memcached2_1 ... done
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8158e741dacb memcached "docker-entrypoint.s…" 13 seconds ago Up 10 seconds 0.0.0.0:11212->11211/tcp memcached_memcached2_1
2627e168914e memcached "docker-entrypoint.s…" 13 seconds ago Up 9 seconds 0.0.0.0:11211->11211/tcp memcached_memcached1_1
4c6daf4fc275 memcached "docker-entrypoint.s…" 13 seconds ago Up 9 seconds 0.0.0.0:11213->11211/tcp memcached_memcached3_1
Test
아래와 같이 2가지 테스트를 할 수 있도록 구성되어 있습니다.
WAS 1 + Memcached 3 : Consistent Hashing 테스트
- gradle 명령을 이용하여 Tomcat 기동합니다.
$ gradle clean build -x bootRun
- 아래 URL을 통해 캐시를 주입합니다.
POST : http://localhost:8080/{key}/{value}
- telnet으로 Memcached에 접속, 값이 들어갔는지 확인합니다.
$ telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
get {key}
$ telnet localhost 11212
Trying ::1...
Connected to localhost.
Escape character is '^]'.
get {key}
$ telnet localhost 11213
Trying ::1...
Connected to localhost.
Escape character is '^]'.
get {key}
위 과정을 반복하면 입력한 KEY값이 동일한 노드의 Memcached에 들어가는것을 볼 수 있습니다.
WAS 3 + Memcached 3 : 서버등록 순서 오류에 의한 키배분 오류 테스트
resource/application.yml
파일을 보면 아래와 같이 각 profiles마다 다르게 서버 순서를 나열해 두었습니다.
server.port: 8080
mem-server: localhost:11211 localhost:11212 localhost:11213
---
spring.profiles: local1
server.port: 8081
mem-server: localhost:11212 localhost:11213 localhost:11211
---
spring.profiles: local2
server.port: 8082
mem-server: localhost:11213 localhost:11211 localhost:11212
이와 같이 설정시 인입된 서버 port에 따라 동일한 key에 대해 ssm이 다르게 분배하는 것을 확인 하는 테스트입니다.
- gradle 명령을 이용하여 각 profile별로 Tomcat 3대를 기동합니다.
$ gradle clean build -x bootRun
$ gradle clean build -x bootRun -Dspring.profiles.active=local1
$ gradle clean build -x bootRun -Dspring.profiles.active=local2
- 아래 URL을 통해 캐시를 주입합니다.
POST : http://localhost:8080/{key}/{value}
POST : http://localhost:8081/{key}/{value}
POST : http://localhost:8082/{key}/{value}
- telnet으로 Memcached에 접속, 값이 들어갔는지 확인합니다.
$ telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
get {key}
$ telnet localhost 11212
Trying ::1...
Connected to localhost.
Escape character is '^]'.
get {key}
$ telnet localhost 11213
Trying ::1...
Connected to localhost.
Escape character is '^]'.
get {key}
위 과정을 반복하면 동일한 key를 넣더라도 어느 port에 접근하여 넣었느냐에 따라 위치가 뒤 섞여있는 것을 확인할 수 있습니다.
Comments