-
ZooKeeper 와 Spring Cloud ZooKeeperDEV 2021. 9. 16. 22:53
회사에서 사용하는 기술 중 ZooKeeper 가 무엇인지 궁금하여 공부하면서 정리했습니다.
부족한 부분은 댓글로 남겨주세요.ZooKeeper 란?
ZooKeeper는 분산 애플리케이션을 위한 분산 오픈 소스 조정 서비스입니다.
주키퍼는 파일 시스템과 비슷한
namespace
를 제공합니다.파일 시스템처럼
/
부터 시작하며 주키퍼의 네임스페이스에 있는 모든 노드는 경로로 식별됩니다.실행
docker compose 를 이용하여 zookeeper 를 실행합니다
version: '3.1' services: zoo1: image: zookeeper restart: always ports: - "2181:2181"
실행이 완료되면
docker exec -it {CONTAINER ID} bin/zkCli.sh
명령어로 Zookeeper CLI 에 접속할 수 있습니다.CLI 명령어
처음에 접속 후
ls /
명령어를 입력하여 최상위에 어떤 노드가 있는지 확인할 수 있습니다.[zk: localhost:2181(CONNECTED) 0] ls / [services, zookeeper]
기본적으로
services
와zookeeper
노드가 존재합니다.노드 생성
create /test
명령어로 노드를 생성할 수 있습니다.[zk: localhost:2181(CONNECTED) 1] create /test Created /test [zk: localhost:2181(CONNECTED) 2] ls / [services, test, zookeeper]
데이터 쓰기, 조회
set [path] data
로 해당 경로에 데이터를 쓰고get [path]
로 해당 경로의 데이터를 조회할 수 있습니다.[zk: localhost:2181(CONNECTED) 9] set /test a,b,c [zk: localhost:2181(CONNECTED) 10] get /test a,b,c
get -w [path
를 이용하면 watcher 를 등록할 수 있습니다.해당 노드의 데이터가 변경되면 watcher 이벤트가 동작하여 변경을 알려줍니다.
[zk: localhost:2181(CONNECTED) 19] get -w /test a,b,c [zk: localhost:2181(CONNECTED) 20] set /test a,b WATCHER:: WatchedEvent state:SyncConnected type:NodeDataChanged path:/test
노드 삭제
delete [path]
로 노드를 삭제할 수 있습니다. 노드가 비어있지 않을 때는 삭제되지 않으며deleteall [path]
로 하위 노드까지 한번에 삭제할 수 있습니다.[zk: localhost:2181(CONNECTED) 41] delete /test Node not empty: /test [zk: localhost:2181(CONNECTED) 42] deleteall /test [zk: localhost:2181(CONNECTED) 43] ls / [services, zookeeper]
Spring Boot 에서 ZooKeeper 사용하기
설정
build.gradle.kts
implementation("org.springframework.cloud:spring-cloud-starter-zookeeper-discovery")
의존성을 추가합니다.spring-cloud-zookeeper-discovery 하위에는
org.apache.zookeeper
가 포함되어 있습니다../gradlew dependencies --configuration compileClasspath 명령어로 gradle 의존성을 트리 구조로 확인할 수 있습니다.
application.yml
zookeeper 의 설정 속성을 작성합니다.
spring: application: name: ZooKeeperTest cloud: zookeeper: connect-string: localhost:2181 discovery: enabled: true logging: level: org.apache.zookeeper.ClientCnxn: WARN
[spring.application.name](http://spring.application.name)
에 작성한 이름으로 주키퍼에/services
하위에 노드가 생성됩니다.@SpringBootApplication
이 위치한 곳에@EnableDiscoveryClient
를 추가합니다.@SpringBootApplication @EnableDiscoveryClient class ZookeeperStudyApplication
DiscoveryClient
DiscovertClient
를 사용하여ServiceInstance
의 정보를 조회할 수 있습니다.@RestController @RequestMapping("zoo-keeper") class ZooKeeperController( private val discoveryClient: DiscoveryClient ) { @GetMapping fun getServiceInstance(): ServiceInstance? { val instances = discoveryClient.getInstances("ZooKeeperTest") if (instances.isNullOrEmpty()) { return null } return instances[0] } }
테스트용 컨트롤러를 생성하여 ServiceInstance 를 반환하도록 했습니다.
{ "serviceId": "ZooKeeperTest", "host": "192.168.0.44", "port": 8080, "secure": false, "uri": "http://192.168.0.44:8080", "metadata": { "instance_status": "UP" }, "serviceInstance": { "name": "ZooKeeperTest", "id": "a0aaedf2-6c65-4c7b-9213-a9cfaa138809", "address": "192.168.0.44", "port": 8080, "sslPort": null, "payload": { "@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance", "id": "ZooKeeperTest", "name": "ZooKeeperTest", "metadata": { "instance_status": "UP" } }, "registrationTimeUTC": 1631535120395, "serviceType": "DYNAMIC", "uriSpec": { "parts": [ { "value": "scheme", "variable": true }, { "value": "://", "variable": false }, { "value": "address", "variable": true }, { "value": ":", "variable": false }, { "value": "port", "variable": true } ] }, "enabled": true }, "instanceId": "a0aaedf2-6c65-4c7b-9213-a9cfaa138809", "scheme": null }
서비스의 이름, uri, 포트 등의 정보가 조회됩니다.
ServiceInstance 등록
ZookeeperServiceRegistry
로 새로운 ServiceInstance 를 추가할 수 있습니다.@PostMapping fun register() { ServiceInstanceRegistration.builder().defaultUriSpec().address("/test/url").port(9000) .name("/test/anotherService").build().let { serviceRegistry.register(it) } }
post 호출을 하여 새로운 ServiceInstance 를 추가해보겠습니다.
curl -X POST 'http://localhost:8080/zoo-keeper'
호출 후 주키퍼 CLI 에서
/services
하위 경로를 확인해보면 등록할 때 작성한name
으로 노드가 생성되어 있습니다.[zk: localhost:2181(CONNECTED) 64] ls /services/test/anotherService [630bca1f-44d0-4207-8ccc-9b7dd32f875a]
instanceId 로 조회해 보면 입력한 이름, address, 포트가 잘 등록된 것을 확인할 수 있습니다.
[zk: localhost:2181(CONNECTED) 66] get /services/test/anotherService/630bca1f-44d0-4207-8ccc-9b7dd32f875a { "name": "/test/anotherService", "id": "630bca1f-44d0-4207-8ccc-9b7dd32f875a", "address": "/test/url", "port": 9000, "sslPort": null, "payload": null, "registrationTimeUTC": 1631536229270, "serviceType": "DYNAMIC", "uriSpec": { "parts": [ { "value": "scheme", "variable": true }, { "value": "://", "variable": false }, { "value": "address", "variable": true }, { "value": ":", "variable": false }, { "value": "port", "variable": true } ] } }
한번더 post 호출을 하여 등록하게 되면
[zk: localhost:2181(CONNECTED) 68] ls /services/test/anotherService [3d2bacec-b99d-4fdf-9b2f-c4f3adac3e08, 630bca1f-44d0-4207-8ccc-9b7dd32f875a]
같은 노드 안에 2개의 serviceId가 생성된 것을 확인할 수 있습니다.
@GetMapping("anotherService") fun getAnotherServiceInstance(): ServiceInstance? { val instances = discoveryClient.getInstances("/test/anotherService") if (instances.isNullOrEmpty()) { return null } return instances[0] }
anotherService 의 ServiceInstance 를 조회하는 함수를 생성하여 확인할 수 있습니다.
LoadBalancerClient
Spring Cloud 에서 제공하는
LoadBalancerClient
이용하여 로드 밸런서 기능을 사용할 수 있습니다.class ZooKeeperController( ... private val loadBalancer: LoadBalancerClient, ){ ... @GetMapping("lb") fun getServiceInstanceUsingLB(): ServiceInstance { return loadBalancer.choose("ZooKeeperTest") } }
LoadBalanverClient
에서choose
메소드를 이용하여 가져올 서비스 Id 를 입력합니다.서버를 다시 실행하고
http://localhost:8080/zoo-keeper/lb
로 접속하면 처음에 접속했던 것 과 동일하게 ServiceInstance 정보를 확인할 수 있습니다.이때 같은 서버를 포트만 9090으로 변경하여 하나 더 실행시켜보겠습니다.
포트를 변경하여 실행할 때는 인텔리제이 기준으로
VM options
에-Dserver.port=9090
를 넣어주면 됩니다.주키퍼 cli 에서 확인해보면 같은 노드에 2개의 서비스가 있습니다.
[zk: localhost:2181(CONNECTED) 75] ls /services/ZooKeeperTest [af31d159-fd04-4042-93ea-42a811fa70b5, c4bcc464-1a39-46cf-9d53-e478318470be]
다시 위의 주소로 접속을 반복해서 해보면 8080 포트와 9090 포트로 번갈아가며 표시됩니다.
로드 밸런서 타입이 기본적으로 라운드 로빈 방식이기 때문에 번갈아가며 선택됩니다.
DependencyWatcherListener
spring: cloud: zookeeper: dependencies: dependencyOne: path: ZooKeeperTest
dependencies
설정을 추가하여 등록한 dependency 의 변경을 감시하는 Watcher 를 생성할 수 있습니다.@Component class ZookeeperListener: DependencyWatcherListener { companion object{ val logger = LoggerFactory.getLogger(this::class.java) } override fun stateChanged(dependencyName: String?, newState: DependencyState?){ logger.info(dependencyName) logger.info(newState.toString()) } }
DependencyWatcherListener
를 구현하는ZookeeperListener
를 생성하고stateChanged
메소드를 override 하여 구현하여 dependency 의 state 가 변경되면 dependencyName 과 새로운 state 를 로그로 찍어주도록 했습니다.8080 으로 서버를 실행한 후 9090으로 하나를 더 실행해보면
INFO 30663 --- [NotifyService-0] .w.DependencyStateChangeListenerRegistry : Service cache state change for '/ZooKeeperTest' instances, current service state: CONNECTED INFO 30663 --- [NotifyService-0] c.m.z.ZookeeperListener$Companion : /ZooKeeperTest INFO 30663 --- [NotifyService-0] c.m.z.ZookeeperListener$Companion : CONNECTED
설정에서 추가한 path 의 인스턴스의 state 가 변경되었을 때 로그가 출력됩니다.
예시로 사용한 소스는 이곳에 있습니다.
https://github.com/sinna94/zookeeper-study참고
반응형'DEV' 카테고리의 다른 글
나에게 필요한 클린 코드 (0) 2022.03.09 heroku 에 spring boot 프로젝트 배포시 발생한 문제들 해결 방법 (0) 2021.08.16 docker-compose : default network subnet 설정 (0) 2021.07.21 MSSQL(SQL Server) - IN 구문 사용시 매개변수 최대 값 에러 (0) 2021.07.05 iterm2 단축키 정리 (0) 2021.06.29