https://www.youtube.com/watch?v=iOoquUhKT5g
※ 영상을 보며 아래 노션 페이지에 내용을 정리하였고, 포스트에는 노션을 한 번 더 요약한 내용을 담았습니다.
내용 요약
배경 설명
1명당 기본적으로 3~4개의 과제를 병렬적으로 진행하고 있었다.
병렬적으로 진행되는 과제가 많다는 것이 단순히 이 프로젝트를 관리하는 개발자들의 어려움은 물론이고 함께 협업하는 기획팀이나 유관부서에게도 이런 어려움들이 전파되었었다.
이런 상황을 타파하고자 시스템을 작은 단위로 분리하게 되었다.
검토 과정
외부 시스템으로부터 데이터를 연동하는 서버와, 해당 데이터를 가지고 비즈니스 로직을 수행하는 서버는 잘 분리가 되어 있던 상황이었다.
비즈니스 로직을 수행하는 거대한 서버 하나를 다음과 같이, 여러 개의 서비스로 분리하는 과정을 진행하였다.
이 중 고민했던 부분은, 공통된 코드를 어떻게 할 것인가 였다. 하나의 큰 서비스를 작은 단위로 나뉘다 보니, 공통적으로 사용하는 코드들이 분명히 있었고, 이것들을 각각의 서비스에서 어떻게 받아갈 수 있는가에 대한 고민을 주로 하였다.
1. 공통된 코드를 lib로 제공하기
해당 방법은 각각의 서비스에서 필요한 코드들을 빌드해서 제공하면 되므로, 빠르게 도입할 수 있다는 장점이 있었다.
하지만 코드들이 각각의 서비스에 실행되다보니까 어플리케이션 러닝 타임이 길어지고, 저장소에 접근하기 위한 커넥션이나 저장소와 관련된 자원 관리 등을 어디서 할 것인가가 명확하지 않다는 단점이 있었다.
2. REST API 방식으로 제공하기
저장소에서 데이터를 가져가는 서버를 앞에 한 개 두고, 각각의 서비스에서 REST API 방식으로 통신하는 것이다.
코드들 때문에 어플리케이션 러닝 타임이 길어지거나 저장소에 대한 자원 관리 등이 명확하지 않았던 단점을 해소할 수 있었다.
하지만 통신 레이어가 증가하면서 약간의 latency가 증가할 수 있다. 이는 대용량 트래픽을 받는 시스템에서는 위험한 요소이다.
또한, 이 데이터를 받아가기 위한 클라이언트 코드들이 각각의 서비스에서 독립적으로 작성돼야하는 것이 약간 비효율적이었으며, 데이터에 대한 버전 관리가 명확하지 않다는 단점 또한 있었다.
3. RPC 방식으로 제공하기
아까의 두 번째 구조는 유지하되, RPC 방식으로 제공할 시 클라이언트 코드를 각각 구현해야한다는 단점이나 버전관리가 용이하지 않다는 단점을 보완할 수 있었다.
당시 개발자들이 RPC에 익숙하지 않다는 것이 문제였으나, 그래도 RPC 통신하는 부분을 잘 추상화해서 제공할 수 있다면 각각의 서비스 개발자들은 로컬의 메소드를 호출하는 것처럼 쉽게 사용할 수 있을 것이라 기대하였다.
RPC 사전 지식
RPC
- Remote Prodedure Call
- 원격 프로시저 호출
- 별도의 원격 제어를 위한 코딩 없이 다른 주소 공간(다른 컴퓨터)에서 함수나 프로시저를 실행(호출)할 수 있게하는 프로세스 간 통신 기술
- RPC는 하나의 개념으로써, 이에 대한 구현체에는 gRPC(구글), Apache Thrift(페이스북), Finagle(트위터) 등이 있다.
IDL
- Interface Definition Language
- 함수나 프로시저가 클라이언트와 서버가 갖게되는 일종의 약속
- 일반적으로 RPC를 구현하는 과정은 IDL 파일로 먼저 명세를 작성하고, 이것을 각각의 언어로 컴파일해서 클라이언트와 서버를 구현하는 방식으로 진행된다.
RPC 구현 예시
thrift(IDL 파일)와 java spring을 이용한 예시
1. IDL 파일로 명세를 작성한다.
- ecoRequest와 ecoResponce가 명세되어 있다.
- SampleService는 메서드 echo를 제공한다.
2. 이 파일을 java언어로 컴파일한다.
thrift --gen java SampleService.thrift
그럼 다음과 같은 파일들이 생성된다.
3. 서버 구현
서버는 SampleService 안에 있는 Iface라고 하는 인터페이스를 구현, 메서드 echo의 내용을 채워넣게된다.
4. 클라이언트 구현
- 클라이언트의 경우 SampleService 안에 있는 클라이언트를 잘 초기화해서, 위와 같이 echo라는 메서드를 사용할 수 있게 된다.
- 초기화 시 서버에 대한 접속 정보가 필요하다.
요구 사항
- IDL을 만들지 않고 기존 자바 클래스를 최대한 활용하여 유지보수가 편하도록 할 것
IDL을 하나하나 만들다보니 800개가 넘는 IDL 파일이 생성됐다.
이렇게 파일을 만드는 것도 힘들뿐더러 유지보수도 힘들다.
따라서 기존 클래스와 인터페이스를 잘 활용하는 방안을 고안하게 됐다. - RPC 구현체를 선택하여 사용할 수 있도록 할 것
코드를 변경하지 않고 gRPC나 thrift 원하는대로 개발자가 변경하여 사용할 수 있다. - 기존 Spring의 사용성과 크게 차이가 없도록 할 것
RPC에 대한 기술적인 허들을 낮추기 위함
위 요구사항을 기반으로 Woowaboot라는 RPC 라이브러리를 만들었다.
최종 산출물
RPC 라이브러리 'Woowaboot' 사용법
Http Controller에 적용하기
기존 Spring 컨트롤러에 @WoowaBootController 어노테이션을 붙이고, SampleService 인터페이스를 입혀주면 해당 bean은 RPC로 동작하게 된다.
우아부트 서버를 올리는 과정에서 스프링 부트는 REST 컨트롤러를 파싱해서 http에 매핑한다.
또 우아부트 서버는 @WoowaBootController 어노테이션을 매핑해서 RPC 핸들러로 만든다.
하나의 빈이 싱글톤으로 초기화되면서 2개의 영역에서 동작할 수 있는 형태이다.
Http가 없는 스프링 Bean에 적용하기
싱글톤으로 동작하는 코드에 @WoowaBootController 어노테이션을 붙여주면 이 스프링 빈은 RPC 핸들러가 된다.
서버 시작 코드 수정하기
원래 스프링은 @SpringBootApplication 이 붙은 클래스를 실행하여 스프링을 실행한다. 해당 이 어노테이션을 @WoowoBootApplication 으로 바꿔주면 Woowaboot 라이브러리를 사용할 수 있다.
RPC 구현체를 Thrift로 할지, gRPC로 할지는 위 코드와 같 스프링 실행 시점에서 결정한다.
IDL 만들기
순수 자바 인터페이스가 IDL 역할을 한다.
Client : IDL을 호출해서 사용하기
- WoowaRpcFactory에 DNS나 IP를 세팅한다.
- 우아한형제 프로젝트 고유 string을 DNS로 처리한다.
- 생성된 팩토리에 SampleService 인터페이스를 전달하여 client를 생성한다.
- getClient 메서드는 실제로 수행 가능한 SampleService 인스턴스를 return한다.
클라이언트를 호출하는 과정을 살펴보자.
- SampleService 인터페이스를 통해서 만들어진 인스턴스의 echo라는 메서드를 호출한다.
- 그러면 이 메서드는 local에서 동작하는 것이 아닌 remote 서버에 있는 echo 메서드를 호출한다.
- 거기서 모든 처리가 다 끝난 후 return된 값을 다시 클라이언트에 돌려준다.
만일 서버에서 에러가 발생하면 에러 난 디스크립션을 직렬화해서 클라이언트로 가져오고, 실제 로컬에서 에러난 것과 똑같은 효과를 볼 수 있다. 따라서 디버깅도 가능하다.
이렇듯 remote 서버에 있는 메서드들을 local에서 자연스럽게 이용할 수 있다.
정리
- IDL 역할을 하는 프로토콜(자바 인터페이스)이 존재한다.
- 그 인터페이스를 구현한 서버가 있고, 이것을 호출할 수 있는 클라이언트가 있다.
- 클라이언트는 서버를 호출하고, 서버는 구현된 방식으로 동작한다.
서비스 메쉬
같은 역할을 하는 rpc 서버를 묶어서 rpc 클러스터, 즉 서비스 메쉬를 만들어 사용해야한다.
고전적으로 서비스 메쉬는 dns나 vip, ip를 이용할 수 있다.
dns를 이용해서 주소를 call하면 그 중에 하나의 rpc 서버가 응답한다.
vip, ip를 이용해 주소를 call하면 마찬가지로 하나의 rpc 서버가 응답한다.
하지만 이 방법은 커넥션이 끊어지고 다시 맺어지는 과정에서 특정 rpc 서버로 커넥션이 몰릴수 있고 로드밸런싱이 제대로 되지 않기 때문에 장기간 안정적으로 운영이 되지 않을 수 있다는 단점이 있다. 따라서 이 방법은 채택하지 않았다.
micro service에서 서비스 메쉬를 지원하는 라이브러리에는 Istio, proxyHa, Eureka, Zookeeper 등이 있고, 이 중 zookeeper를 채택하였다.
zookeeper를 통해 서버를 등록하는 방법은 다음과 같다.
- rpc서버가 등록되거나 해지될때 주키퍼에 ip나 프로토콜 정보를 등록한다.
- 클라이언트는 주키퍼에 있는 서버 목록(ip나 프로토콜 정보)를 조회한다. 이를 통해 서버를 호출할 수 있게 된다.
- 혹시나 운영 도중 rpc 서버가 죽거나 새로 등록되면 주키퍼에 다시 등록을 하거나 해지하게 된다.
- 주키퍼는 클라이언트에게 변경사항을 알려주고, 알림을 받은 클라이언트는 서버 목록을 다시 조회한다.
Woowa하게 RPC 적용하기
실제 서비스에 RPC를 적용해나간 과정은 다음과 같다.
1. RPC로 만들 Bean을 추출하여 라이브러리로 변경한다.
서버에 있는 코드 중에서 RPC로 만들 bean을 추출하고 이를 라이브러리화 한다.
추출된 라이브러리를 include 하여 서버를 구동한다.
기존과 같은 클래스 코드이기 때문에 동일하게 동작한다.
2. 이 라이브러리를 rpc 서버로 만든다.
해당 라이브러리를 잘 주고 받을 수 있는 rpc 서버를 추가로 등록하는 과정이다.
3. 라이브러리와 RPC의 결과가 같음을 테스트하고, 두 가지를 모두 가진 서버 배포하기
앞단의 서버가 standalone 방식으로 배포된 라이브러리를 통해 동작하는 방식과 rpc 서버를 통해 리모트로 동작하는 방식 2가지 모두 호환될 수 있도록 만든다.
라이브러리와 rpc간의 데이터 같음이나 성능을 테스트해가며 특정 빈을 조금씩 이동시킨다.
이 과정에서 문제가 생기면 다시 라이브러리로 돌아간다.
4. 이전에 쓰던 라이브러리 제거하기
모든 메서드가 테스트되고 데이터에 문제가 없음이 확인되면, 그 전에 쓰던 라이브러리는 제거한다.
이후부터는 rpc 라이브러리만 가지고 통신하게 된다.
※ 샘플 코드
※ 구동 방식
1. 주키퍼가 있을 때
2. 주키퍼가 없을 때
새롭게 알게된 점 / 느낀 점
- RPC에 대해 전혀 몰랐는데 이번 기회에 새롭게 알 수 있었다. '여러 서버에서 공통적으로 사용하는 코드를 어떻게 제공할 것인가?'에 대한 하나의 해답인 듯 하다.
- 만일 RPC를 쓰지 않고 라이브러리로 제공한다면 각 서버에서는 라이브러리를 직접 include할 것이고, 그럼 코드들이 각각의 서버에서 실행되게 되므로 다소 비효율적인 측면이 있다. 하지만 RPC 서버를 두고, 각각의 서버들이 RPC 서버의 메서드를 호출하는 방식을 채택한다면 이러한 문제를 해결할 수 있을 뿐더러 저장소에 대한 자원 관리 또한 명확히 할 수 있다.
- RPC를 구현하는 방법에 대해 얼추 알게 되었다. IDL 이라는 명세 파일을 thrift 등을 이용해 작성하고, 이를 java 언어로 컴파일한다. 이렇게 생성된 java 인터페이스 파일을 사용 용도에 알맞게 구현하여 사용하면 되는 듯하다. 이 과정을 완전히 이해하진 못했고, RPC를 직접 사용해봐야 확실하게 알 수 있을 것 같다.
- 배민처럼 개발 실력이 뛰어난 회사에서는 직접 필요한 라이브러리를 만들어 사용한다는 것을 알 수 있었다. RPC 라이브러리를 직접 만들고, 이를 오픈소스화 할 계획도 가지고 있다고 해서 배민 팀이 대단하다고 느꼈다.
- 당연하게도 RPC 서버는 1대만 두지 않는다. 여러대를 두어 서비스 메쉬를 이룬다. 이 서비스 메쉬를 관리하는 라이브러리로써 배민팀은 zookeeper를 사용하였다. zookeeper를 기존에 kafka 클러스터를 관리할 때만 사용해봐서, kafka에 종속적인 기술인 줄로만 알았는데, 그게 아니라 클러스터를 관리할 때 범용적으로 쓰일 수 있다는 사실을 이번 기회를 통해 알 수 있었다.
- 강의 도중에 빠르게 'standalone'이라는 용어가 쓰였는데, 뭔지 몰라서 찾아봤더니 '독립형' 이라는 뜻이었다. 코드를 독립적으로 배포한다는 의미로 사용한 것 같은데 정확하진 않다.
- 언젠가 RPC를 공부 차원에서 토이프로젝트에 도입해보고 싶다.
'IT 일상 > 세미나 및 컨퍼런스' 카테고리의 다른 글
[우아콘2023] Kafka Streams를 활용한 이벤트 스트림 처리 삽질기 (1) | 2024.01.26 |
---|---|
[우아콘2023] Kafka를 활용한 이벤트 기반 아키텍처 구축 (0) | 2024.01.21 |
[우아콘2023] 대규모 트랜잭션을 처리하는 배민 주문시스템 규모에 따른 진화 (0) | 2024.01.17 |
AWS Summit Korea 2022 (22.05.10 - 22.05.11) (0) | 2022.05.11 |
디지털 대전환 엑스포 (21.11.27) (0) | 2021.12.15 |