본문 바로가기

강의 정리/분산시스템

분산시스템 (3) 커뮤니케이션 모델과 로드 밸런싱

- 주제

1) 커뮤니케이션 모델: 메시지 교환을 통해 서버들이 '통신'을 어떻게 하는가.

2) 코드 밸런싱, 로드 밸런싱: 어떻게 중재하게 되는가.

 

 

 

- 커뮤니케이션 타입

- 프로세스: 실행중인 프로그램.

프로그램이라 하는것은 디스크에 파일 형태로 저장되어 있는 것.

프로세스는 메모리에 실행중인 상태로 돌아가고 있는 것.

프로세스는 한 서버에 여러개가 존재한다. (ex. 윈도우에서 애플리케이션 프로세스도 여럿, 백그라운드 프로세스는 그보다 더 많이 한꺼번에 돌아간다.)

 

1) 프로세스는 자기 자신과 통신한다. inter-thread communication = 쓰레드thread끼리 통신하는것.

2) 같은 장치 내부의 수많은 프로세스들이 통신한다.

3) 네트워크 너머에 있는 원격 머신에 있는 프로세스와 통신한다.

 

앞의 두개는 단일 서버의 이야기였고 후자는 물리적으로 다른 프로세스와 통신하는 것을 의미. 우리의 관심사는 마지막.

 

 

- 프로세스들끼리 어떻게 통신하는가?

사용되는 언어도 다르고, 주소 공간도 다르며(메모리의 몇 공간에 배치할 것인지), 접근 권한도 다르고, 하드웨어 자원도 다르고, 내부적인 인터페이스도 다르다. 

 

- 프로세스 커뮤니케이션은 expensive.

기능을 부여하기 위해 드는 노력이 크다. 왜?

버스, 네트워크, 메모리와 같은 커뮤니케이션 채널들이 기본적으로 제한적이다. 이 메세지들의 크기와 숫자가 문제가 된다.

매우 큰 메세지를 매우 많이 주고받으려면 많이 필요하다. 이때 분산시스템이라 하는것은 컴퓨터 자원에 대한 요구량이 계속 증가하고 있다고 가정하기 때문에 자원은 부족하다고 가정해야 한다.

 

- 커뮤니케이션 모델/프로토콜이 필요하다.

메시지라는 데이터는 네트워크 스택을 지나가면서 헤더를 붙여 전송하고, 리시버 단계에 도착하면 헤더를 떼고 내용물이 도착하게 된다. 즉, 전송할 때 헤더들이 모두 붙고 그 상태로 네트워크를 통과하며, 붙이고 떼어내는 과정도 더해져 최종적으로 절차를 늘리고 비용을 증가시킨다.

그럼에도 다른 머신에 있는 프로세스와 통신하기 위해서는 그러한 프로토콜이 필요하다.

 

이때 Abstraction 추상화가 사용되는데, 추상화는 개발자에게 필수적이다.

예를 들어보자. 프로그래머는 단일 서버 내에 있는 네트워크만이 아니라 다른 서버끼리 통신하는 상황을 사용해야 할 때 소켓 프로그래밍을 그냥 가져다가 쓴다. 그러나 내부적으로 무엇이 어떻게 돌아가는지 알지 못하고 사용한다. 이것이 추상화이다.

분산 시스템에서의 핵심은 단일 서버 내에서만 처리되는게 아니라 여러 서버에서 통신하며 처리된다는 것이다. 그것을 전공자가 라이브러리로 만들어두었다. send("안녕") 내부에는 길게 구현되어 있어도 우리는 함수만 사용하면 된다. 

 

- 커뮤니케이션 모델의 구성품

1) 메세지 포멧(형식): 앞부분의 8비트는 무엇, 다음 16비트는 무엇, 하는 형식. 서로 상호간에 동의가 되어있어야 한다. 

2) 프로토콜: 서로 어떻게 메세지를 주고받을 것인지에 대한 동의. 서버가 읽기 요청을 받았을 때 어떻게 할 것인가? 

 

결국 애플리케이션은 메세지 포맷을 해석하는 법을 알고 있어야 한다. 

 

 

 

- RPC (Remote Procedure Call)

: 프로세스가 원격 프로세스에 직접적으로 콜할 수 있도록 허용하는 프로토콜.

inta a = 'remote' rand() 할때 랜드 함수는 어디에서 실행되는가?

그냥 rand()는 컴퓨터 내부에서 실행되지만 리모트는 멀리 떨어진 다른 서버에서 실행된다.

이때 개발자의 입장에서는 랜덤한 수를 원하고 있으니, 컴퓨터 내부에서 실행되든 외부 서버에서 실행되든 결과값만 얻으면 된다. 또한 메세지를 어떻게 교환했는지도 알 필요가 없다.

-> 이 명령어는 물리적으로 떨어진 다른 서버에서 결과값을 받아오지만, 개발자는 알 필요 없으니까 최대한 숨기자!

결과적으로 일반 함수처럼 사용된다.

=> 즉, RPC는 원격 호출을 할 수 있게 하는 프로토콜이다. 추상화를 굉장히 많이 해둬서 사용이 편리하다.

 

- 그렇다면 왜 많이 사용되는가?

요즘 서비스들은 microservice를 한다.

서비스를 통째로 만들지 않고, 각 어플 안에 각각의 기능들이 있으며 서로 상호 소통하여 기능을 공유한다.

예를 들어 백화점 프로그램은 옛날에는 로그인 - 장바구니 - 결제 = 과거에는 모든 기능이 하나에 들어갔다. 그러나 현재 프로그램은 소규모로 뜯어내고 조각맞춘다. 인증을 전문적으로 하는 서비스가 따로 있고, 장바구니의 데이터베이스를 따로 담아주는 데베 서버가 있고, 결제를 전문적으로 처리하는 서버가 따로 있다.

결국에 사용자 입장에는 변함이 없지만 시스템적으로는 작은 기능들을 하나씩 담당하여 모두가 모인 결과물이다. 더 직관적으로는 한 서비스를 만들때 데베는 mySQL에 요청 보내고 하는 것들도 마이크로서비스이다.

 

- RPC를 사용하면 로컬 프로세스를 사용하는 것처럼 호출된다.

서버 한대를 사용하는 것보다 RPC를 사용하는 것이 좋다.

+ 인공지능에 있어서도. AI에 사용되는 리소스가 말도 안 되게 크기 때문에.

 

 

- RPC를 사용하기 위해서는 서비스 인터페이스가 필요한데, 이는 서버와 클라이언트 둘 다에게 적용된다.

 

- RPC framework는 두가지 프록시를 자동적으로 생성해주는 인터페이스를 사용한다.

1) Stub: 클라이언트에서 사용되는 프록시.

함수 호출 구조를 호출당하기 위한 형태로 가공해서(=RPC를 위한 데이터 포멧으로 가공해서) 원격 서버에 전달해준다.

2) Skeleton: 서버 쪽에서의 프록시.

받은 요청을 뜯어서 기존의 구조로 돌려놓고 - 해석하고, 함수를 실행하고, 결과를 클라이언트에게 전달해준다. 

 

인터페이스가 사용되는 이유는: 만들어 놓으면 그 규격에만 맞는다면 전부 사용할 수 있다.

그것을 클라이언트에서는 스텁, 서버에서는 스켈레톤.

 

 

RPC 프레임에 맞는 프레임워크로 변환해서 네트워크로 향했다가 결과값을 받아 원래대로 변환해준다.

 

 

- RPC 구현은 프레임워크에 의존적일수도, 의존적이지 않을수도 있다.

 

- RPC 프로토콜은 두개의 커뮤니케이션을 제공한다.

1) 블로킹blocking 커뮤니케이션은 0번 요청을 보내면 0번 값을 받는다. 이때 0번에 대한 답이 올 때까지 보내지 않고, 받고 나서 다음 요청을 보낸다.

2) non-blocking 커뮤니케이션은 닥치는대로 보내고 서버가 결과를 보내주는 대로 받는다.

전자는 reliable하다면 후자는 상대적으로 덜 신뢰적이지만 속도가 더 빠르다.

 

- 프로그래밍 언어 안에서 커뮤니케이션 인터페이스를 제공하고, 실행중에 커뮤니케이션 프로토콜을 숨기는 컨셉은 모든 메세지 프레임워크 안에서 사용된다.

RPC를 이해하는 것은 메세지 시스템을 이해하는 것에 도움을 준다.

 

 

- 장점: 1) 상호소통에 적합하고, 2) 쉽고, 3) 광범위하고, 4) 성능이 좋다.

- 단점:

1) RPC와 관련된 코드를 사용하기 위해서는 tight coupling이 필요하다.

그러나 장점을 누리기 위해 필요한 것이라서 단점은 굳이 아니다.

2) Local call과 Remote call은 사실, 아주 다르다.

(1) 후자는 아무리 RPC로 편리하게 만들었어도 외부 서버에 다녀오기 때문에 레이턴시가 길어질수밖에 없다.

(2) 또한 unpredictable, 패킷 로스가 발생할 수 있다. 이는 네크워크 채널이라는 것의 특징 때문이다.

(3) 또한 어느 주소에 액세스를 하고 싶다는 조작이 불가능하다.

 

 

 

- 메세지 브로커

 

파란색 프로세스들은 작업을 요청만 하고 갈색 프로세스들은 수행만 한다. 요청을 하는건 클라이언트, 요청을 받고 수행하는건 서버. 

 

1. 메세지 브로커의 정의

- 메세지 브로커라는 건 결국 메세지 큐이며, 메세지 중심의 미들웨어이다.

- sender로부터 적합한 큐, 마지막으로 receiver까지 메세지를 전달하는, 메세지 전달 프레임워크의 일부이다.

- 보낸 사람과 받는 사람의 주소를 확인한다.

- 프로세스들 사이에서 전달될때 binary encoding을 적용할 수 있다.

 

 

비동기 방식으로 작동non-blocking.

일단 메세지 큐에다가 전부 다 던져놓는다. 클라이언트에게 작업 완료는 알 바가 아니다.

이때 비동기 방식은 덜 신뢰적이기 때문에, 메세지 브로커의 역할은 더 안정적으로 돌아갈 수 있게 하거나, 다양한 일을 바르게 할당해주는 일을 맡는다.

 

RPC라는 응용 라이브워크에서 제공하는 Exception을 사용. 

 

분산시스템에서 서버가 통신할때 기본적으로 애플리케이션 개발자들은 RPC를 사용한다.

그냥 함수 가져다가 꺼내 쓰면 되니까 편리하다. 그러나 내부적으로는 많은 일들이 발생한다.

 

 

 

- Coordination and Load balancing

 

- Coordination이란 무엇인가?

1) 여러개의 서버는 리더를 필요로 한다.

2) 공유된 리소스 싱크로제이션: 공유된 자원의 값의 싱크를 맞춰줘야 한다.

3) 포트 번호 등의 Configuration 등을 관리한다.

4) 그룹 멤버쉽: 하나의 그룹에서 관리하는 것.

5) Lock management: 락이 어떤 자원의 어떤 무엇이 걸려있는지 관리.

6) 로드 밸런싱: 어떤 요청이 들어왔을때 누가 무엇을 처리하는가.

7) 주키퍼, 카프카, 처비 등.

 

* 한쪽에 클라이언트들, 다른 한쪽에 서버들이 있을 때, 클라이언트의 요청이 중간의 코디네이터를 거쳐서 간다.

코디네이터들이 요청들을 보고 처리해주면서 각 서버들에 작업을 분배해주는 것을 로드 밸런싱이라고 한다.

모든 요청이 코디네이터에게 몰린다면: 코디네이터의 성능이 과부하 되기 좋다 -> 그래서 코디네이터는 여러개 존재한다. 그들이 전부 복제된다면 클라이언트의 수가 많아도 과부하 되는 것을 막아준다. 클라이언트와 서버 사이에 코디네이터 여러개가 존재하기 때문에 큰 비중을 차지한다.

 

 

- 용어에 대해서: througthput vs latency

 

- 클라이언트의 요청: 1초에 n번의 요청 = Arrival rate(서버 입장에서 도착한다는 관점), sending rate(클라이언트의 입장), tx rate(Transmit, 얼마나 보내고 있는지)의 3가지가 있다.

 

* 클라이언트가 1초에 백만번의 요청을 보내고 있으면: 1MRPS.

* Arrival rate와 Sending rate, 둘은 완전히 다를 수 있다.

클라이언트가 1MRPS를 보냈다면 서버들이 답장을 보냈을 때 클라이언트는 답장을 받는데, 1초에 얼마나 답장이 오는가는 서버들이 결정한다.

클라이언트가 성능이 너무 좋아서 백만번의 요청을 보낼 수 있어도, 서버 입장에서는 그만큼 감당할 수 없을수도 있다. 1초에 0.5RPS를 받게 되는 것.

보내는 건 클라이언트가 결정, 받는 건 서버가 결정.

이런 차이가 나게 되는 건 서버의 최대 throughput의 한계가 결정되어 있기 때문이다. 서버가 아무리 많이 받아도 처리하는 것에 한계가 있을 수 있다.

 

- Goodput: 처리량의 맥시멈에 닿는 것 = application-level throughout = receive.

 

 

(a) 0.8 이상을 서버가 처리할 수 없다. -> 처리하기 위해서는: 서버를 늘리면 된다.

보내는 양 > 처리하는 양.

 

 

maximum throughput에 접근할수록 서버의 tail latency에 접근한다. 

1M(ex)을 초과하는 요청이 들어오면 큐에 쌓아두게 된다. 이때 큐에 쌓이는 요청이 많아질수록 줄서서 기다리는 시간 - 큐잉 딜레이가 증가한다. 앞쪽은 큐잉 딜레이를 경험하지 않았지만 뒤는 경험했다는 것.

 

- 테일 레이턴시 = 꼬리 지연.

100개의 요청들을 줄을 세워보고 99등일때 테일 레이턴시라고 한다.

왜 거기에 관심을 가지느냐?

사용자의 경험수준이라는 것: 100번중에 좋은 경험이 99개여도 단 한 번의 나쁜 경험이 구축되면 사용자는 견디지 못한다. 즉 평균값과 중의값에 대해서 결정되는게 아니라 최악의 케이스에 의해 결정되기 때문에, 오늘날은 테일 레이턴시에 관심을 가진다. 이때 100등은 너무 예외적인 케이스가 존재할 수 있어서 제외.

 

(b) 0.8 정도가 되었을 때 SLO(테일 레이턴시)를 벗어나는 것. 

(c) 드랍 레이트도 안 늘어나다가 0.8 이상부터 증가하는 것 = 큐에 더이상 들어갈 곳이 없어서 증가. 

 

 

 

- 로드 밸런싱 이야기로 돌아가서

 

- 여러대의 서버의 이상적인 결과를 위한다.

- 높은 이용률을 위한다.

 

여러대의 서버가 있으면 그 서버들 중에 누가 이 작업을 처리할지 누군가가 정해야 한다.

예를 들어 3M이 들어오면 1M씩 할당해서 가장 바쁘게 돌려야 좋다. 한쪽에 1M을 몰아주면 작업은 느려지고 드랍은 발생한다.

그렇다면 어떻게 분배해야 하는가?

이때 사용되는 것이 요청을 분배하는 알고리즘 = 로드 밸런싱 알고리즘이다.

 

- 요청을 어느 서버에 넣을지를 결정하는 기능은 어디에 구현되어 있는가?

1) 클라이언트에서도 구현할 수 있다.

'어떤 서버들이 존재하는지'에 대한 정보를 얻고 어떤 서버에 할당해달라 명령할 수 있다.

2) 또는 로드 밸런서라는 별도의 코디네이터에서 구현된다.

후자가 주로 사용되는 이유: 전자는 클라이언트가 모든 서버에 대한 정보를 알고 있다는 전제가 있어야 하는데, 서버는 계속해서 변화하며 그 정보를 일일히 전달하기에는 힘들고 비효율적이기 때문에.

 

- 로드 밸런싱 기능을 로드 밸런서에 어떻게 구현하느냐: 소프트웨어적/하드웨어적.

1) 하드웨어는 성능이 정말로 좋다!

단점은: 생산하는 시점에서 로직이 고정되어 나오기 때문에 내부 로직 변경이 불가능하다. 미리 생산할때 제공해주는 옵션이 아니라면 사용 불가능.

2) 소프트웨어는: 일반 서버에다가 구현해놓고 사용한다.

장점은 유연Flexibility하다. 대표적인 것은 nginx.

 

- 업계에서는 하드웨어 로드 밸런서스위치라고 부른다.

L4 스위치, L4 장비: 애플리케이션 레이어에서 로드 밸런싱을 한다는 것의 의미는 요청을 받아 할당을 정해야 한다는 것이고, 이를 위해서는 기준이 있어야 하며, TCP와 UDP등의 L4정보를 확인하고 할당한다는 것이다. L7은 어떤 데이터를 요구하고 있는지를 기준으로 분배한다.

 

- 멀티코어 시스템에서도 어느 코어에 할당하느냐에서도 로드 밸런싱을 사용한다. 

 

 

- RR(Round-robin)

1 - 2 - 3 - 1 - 2- 3 . . .

균등하게 순서대로 분배하는 간단한 알고리즘. 순회.

 

- WRR(Weighted Round-Robin)

서버들마다 성능이 조금씩 달라지는 걸 감안하지 않고 순회적으로 분배하는게 RR이었다면, (즉 서버들의 성능이 동일하다고 가정한다) 그를 보완하기 위해 등장한 개념이다.

 

가중치라는 개념을 사용한다.

각각의 서버에 가중치를 부여하고, 더 높은 가중치를 가진 서버는 더 많이 노동한다. 

 

- Random

진짜 랜덤하게 분배한다.

RR과 공통점은 둘이 단순하고, 구현이 손쉬우며, 서버의 처리량에 대해 전혀 고려하지 않는다는 것이다.

차이점은 Deterministic결정적인게 RR이라면 Stochastic확률적인게 Random이다. 또한 랜덤은 단기적으로 unfairness될 수 있다.

 

- Least connections

커넥션의 수가 적다면 - 서버가 처리하고 있는 작업의 양이 적다고 생각하고 적은 커넥션에 할당해준다.

일반적으로는 납득이 가능하지만, 사실 커넥션의 수를 통해 얼마나 많은 작업량을 처리하는지를 알 수는 없다. 왜냐하면 커넥션 1개가 5시간을 소요하고 100개가 짧을수도 있기 때문이다. 그럼에도 불구하고 일반적으로는 그런 경향성이 있기 때문에 사용된다.

 

커넥션의 수라는 것은 실시간으로 변동한다는 특징을 보유하고 있다. 

 

- Hash-based

말 그대로 해쉬 값을 계산해서 결정하겠다는 것이다.

내가 기준으로 삼고 싶은 어떤 정보에다가 모듈러 연산을 돌려서(ex. %3) 해쉬값이 0, 1, 2가 나온다고 했을때 그에 맞게 할당한다.

장점은 굉장히 빠르고 단순하다는 것이다.

해쉬함수를 사용하는 것은 알겠는데, 그 기준으로는 무엇을 사용하는가? 이때 가장 많이 사용하는 것은 5-tuple이다. source IP, Port, destination IP, Port, protocol을 기준으로.

 

사용해서 좋은점은 무엇인가?

여러개로 분할된 패킷이 사실 하나의 요청일 때 - 세개의 패킷이 하나의 서버로 가야할 수 있다. 이때 서버의 중간통로의 한계량 때문에 여러개로 분할되어도, 해쉬값을 사용한다면 모두 같은 서버로 향하게 된다.

 

 

 

- Case study: NGINX

 

점유율 33.66%.

단순히 로드 밸런서를 위해서만 존재하는 것은 아니고 제공하는 기능이 굉장히 많다.

웹서버도 돌릴 수 있고, L7의 reverse proxy도 사용 가능하다. 단순하게 성능이 좋으면서 가볍다. 제공해주는 알고리즘은 다양(위에서 설명한 것들 + a)하다.

 

 

위는 여러개의 기능을 하면서 + 전체적으로 마이크로서비스 아키텍쳐라는걸 볼 수 있다.