IPC ( 별도의 수단을 이용하여 프로세스 간 통신을 하는 방법론) Inter Process Communication
Socket
Application Layer에서 Transport Port의 TCP 또는 UDP를 이용하기 위한 수단
실질적으로 로컬 컴퓨터의 프로세스와 원격지 컴퓨터의 프로세스가 IPC 통신을 하는 것.
소켓은 대부분의 언어에서 API 형태로 제공하는 편리함 때문에 지금도 많이 사용됨.
다만 일련의 통신 과정을 직접 구현하므로 통신 관련 장애를 처리하는 것은 개발자 몫.
서비스가 고도화될 수록 수백 수천 가지 데이터를 formatting 하는 작업도 점점 어려워짐.
RPC( Remote Procedure Call)
Socket의 단점과 한계에서 RPC라는 기술이 등장한다.
네트워크로 연결된 서버 상의 프로시저를 원격으로 호출할 수 있는 기능입니다.
네트워크 통신이나 Call 방법을 신경쓰지 않고 원격지의 자원을 내 것처럼 사용할 수 있습니다.
IDL(Interface Definication Language) 기반으로 다양한 언어를 가진 환경에서도 쉽게 확장이 가능하며, 인터페이스 협업에도 용이하다.
지원언어는 다음과 같다.
- C++
- Java
- Python
- Ruby
- Node.js
- C#
- Go
- PHP
- Objective-C…
RPC의 핵심 개념은 Stub이다.
서버와 클라이언트는 서로 다른 주소 공간을 사용한다. 그렇기 때문에 함수 호출에 사용된 매개 변수를 꼭 변환해주어야 한다.
그렇지 않으면 메모리 매개 변수에 대한 포인터가 다른 데이터를 가리키게 되기 때문인데, 이 변환을 담당하는 것이 Stub이다.
프로세스는 상호독립적입니다. 각각 별도의 메모리 공간을 할당받아 동작하고 있습니다.
이 과정에서 매개변수의 주소값이 변환되지 않으면 호출한 메모리 포인터를 기준으로 한 매개변수에 접근하게 됩니다.
Client Stub은 함수 호출에 사용된 파라미터의 변환(Marshalling) 및 함수 실행 후 서버에서 전달된 결과의 변환을, server stub은 클라이언트가 전달한 매개 변수의 역변환(Unmarshalling) 및 함수 실행 결과 변환을 담당하게 됩니다.
기본적인 RPC 통신과정은 다음과 같습니다.
1. IDL을 사용해 호출하는 규약을 정의.
함수명, 인자, 반환값에 대한 데이터형이 정의된 IDL 파일을 rpcgen으로 컴파일하면 자동으로 stub code가 생성된다.
2.Stub Code에 명시된 함수는 원시코드 형태로, 상세 기능은 server에서 구현됩니다.
만들어진 stub 코드는 클라이언트/서버에 함께 빌드합니다.
3.client에서 stub에 정의된 함수를 사용할 때,
4. client stub은 RPC runtime을 통해 함수를 호출하고
5. server는 수신된 procedure 호출에 대한 처리 후 결과 값을 반환합니다.
6. 최종적으로 Client는 Server의 결과 값을 반환받고, 함수를 Local에 있는 것처럼 사용할 수 있다.
RPC는 상당히 획기적인 방법론이였으며, 분산 환경의 등장에 따라 함께 발전해 온 오래된 기술이고 이에 대한 구현체로 CORBA, RMI 등 여러 가지가 있었는데, 정작 구현의 어려움/지원 기능의 한계 등으로 제대로 활용할 수 없었다.
따라서 데이터 통신을 익숙한 Web을 활용하려는 시도로 이어지고, 이 자리를 REST가 차지하게 됩니다.
REST ( REpresentiational State Trasfer )
REST는 HTTP/1.1 기반으로 URI를 통해 모든 자원을 명시하고 HTTP METHOD를 통해 처리하는 아키텍쳐입니다.
HTTP를 그대로 계승하였다는 점에서 별도 작업 없이도 쉽게 사용할 수 있다는 장점으로 매우 보편적으로 사용됩니다.
다만 REST는 일종의 스타일이지 표준이 아니기 때문에 parameter와 응답 값이 명시적이지 않습니다.
이는 구현하는 개발자에 따라 형태가 매번 달라진다는 것을 의미합니다.
또한 HTTP 메서드는 매우 제한적이기 때문에 세부 기능 구현에는 제약이 있습니다.
HTTP/1.1
HTTP/1.1에서는 매 요청마다 연결/해제 과정이 발생하였습니다. HTTP 프로토콜은 무상태성과 비연결성 특징을 통해 연결 상태를 서버에 저장하지 않으면서 더 많은 연결을 처리하기 위함이었습니다.
따라서 같은 클라이언트의 반복적인 요청을 해결해야 했습니다.
이를 위해 두 개의 연결방식이 추가됩니다.
Persistent connection Model과 HTTP Pipelining이 있습니다.
Persistent connection은 연결의 지속 시간을 설정합니다.
설정한 연결 지속 시간에서는 연결/해제 과정을 하지 않습니다. 대신 TCP 특성상 요청 후에 응답을 기다려야 했고 이 시간 동안 특정 동작을 수행할 수 없습니다.
이를 통해 클라이언트와 서버 간의 네트워크 오버헤드를 줄일 수 있어 전체적인 성능을 개선할 수 있습니다.
Pipelining은 이를 더 개선했습니다.
클라이언트에서 요청을 응답에 상관없이 한 번에 보내고 Server에서는 응답을 요청이 들어온 순서대로 처리한 후 한 번에 보내는 방식입니다. 만약 오류가 발생하면 누락이 발생한 요청을 다시 보내도록 요청합니다.
이를 Persistent connection과 함께 사용할 수 있습니다.
이 경우 클라이언트와 연결을 맺은 상태로 여러 개의 요청과 응답을 주고받을 수 있습니다.
그렇기 때문에 클라이언트는 요청을 대기하지 않고 non-blocking 하게 동작할 수 있어 클라이언트 성능이 향상될 수 있습니다.
이러한 Pipelining도 HOL( Head Of Line) Blocking 문제를 가지고 있습니다.
Pipelining 특성상 클라이언트의 요청을 순서대로 받고 순서대로 처리합니다.
이는 앞선 요청의 대기시간이 긴 경우 짧은 응답을 해줄 수 있는 뒷부분의 요청에 대한 응답이 blocking 되는 것을 의미합니다. 혹은 요청 중 하나가 오류가 발생되어 처리되지 않는다면 그다음 요청도 실패할 수 있습니다.
xml, json
XML은 html과 같이 tag 기반이지만 no pre-defined tags로 높은 확장성을 인정받아 이기종간 데이터 전송의 표준이었지만, 다소 복잡하고 비효율적인 데이터 구조 탓에 속도가 느리다는 단점이 있습니다.
이러한 효율 문제를 JSON이 간결한 Key-Value 구조 기반으로 해결하는 듯하였으나 결국 자료형의 한계로 파싱 후 추가 형변환이 필요한 경우가 많아졌습니다.
두 타입 모두 String 기반이라 사람이 읽기 편하지만 데이터 전송 및 처리를 위해선 별도의 Serialization이 필요합니다.
gRPC ( google Remote Procedure Call)
google 사에서 개발한 오픈소스 RPC 프레임워크입니다. 과거 Protocol Buffer만을 제공해 왔는데, PB 기반 Serializer에 HTTP/2를 결합하여 RPC 프레임워크를 만들었습니다.
REST와의 가장 큰 차이점은 HTTP/2를 사용한다는 것과 프로토콜 버퍼로 데이터를 전달한다는 점입니다. Proto File만 배포하면 환경과 프로그램 언어에 구애받지 않고 서로 간의 데이터 통신이 가능합니다.
HTTP/2
http2는 하나의 connection으로 동시에 여러 개 메시지를 주고받으며, header를 압축하여 중복을 제거한 후 전달하기에 더욱 효율적입니다.
또한 필요시 클라이언트 요청 없이도 서버가 리소스를 전달할 수도 있기 때문에 클라이언트 요청을 최소화할 수 있습니다.
HTTP/2는 binary protocol을 사용해 데이터를 전송함으로써 더욱 compact 하게 data를 전송합니다. 반변 HTTP/1.1은 textual protocol입니다.
compressed header
HTTP/2는 header를 압축하기 때문에 데이터 전송시간을 줄일 수 있습니다.
persistent connection & mulitplex streaming
다중 요청에 대한 응답을 하나의 binary data로 묶어 전송할 수 있는 기술입니다.
Server Push
HTTP/2는 client가 요청한 resource에 연관된 다른 resource들을 임의로 client 초기 요청의 결괏값과 함께 push 할 수 있습니다. 이를 통해 클라이언트 요청을 최소화할 수 있습니다.
* HTTP/2를 사용하기 위해선 HTTPS를 반드시 사용해야 합니다.
ProtoBuf
Protocol Buffer는 google 사에서 개발한 구조화된 데이터를 직렬화하는 기법입니다.
보통 직렬화(Serialization)는 데이터를 바이트 단위로 변환하는 작업을 의미합니다.
JSON의 데이터를 공백을 모두 제거하면 82바이트가 사용되는 반면 PB는 33바이트만 충분하게 됩니다.
이는 필드 번호, 필드 유형, 등을 1byte로 받아서 식별하고, 주어진 length 만큼만 읽도록 합니다.
Protocol Buffers의 각 필드는 태그 번호와 필드 타입을 나타내는 바이트로 시작합니다.
만약 첫 번째 바이트가 해당 필드가 문자열이라면 문자열의 바이트 수와 문자열의 UTF-8 인코딩이 이어집니다
첫 번째 바이트가 정수인 경우 숫자의 가변 길이에 대한 인코딩이 나오게 됩니다.
또한 배열 유형은 존재하지 않지만 다중 필드 값을 나타내기 위해 태그 번호가 여러 번 표시됩니다.
Proto File
- Message and Field
( syntax = “proto3”;라는 코드를 넣지 않으면 프로토콜 버퍼 컴파일러가 사용자가 proto2를 사용하고 있다고 가정합니다.)
- proto2 vs proto3
- 위 예제에서는 첫 줄에 syntax = “proto3”을 지정해 줌으로써 proto version 3의 규약을 따르겠다고 선언했습니다. 이를 명시하지 않으면 default로 version2 문법을 따르게 됩니다. 아래와 같이 지원 언어도 다르지만, message 작성 시 field rule 지정 등 문법에도 차이가 나타납니다.
- 지원 언어 및 새로운 기능 지원을 위해 proto3을 사용할 것은 권장합니다.
- Proto2 지원 언어 : C++, Java, Python, Go
- Proto3 지원 언어 : C++, Java, Python, Go, Ruby, Objectice-C, C#, JavaScript, PHP, Dart
Proto File에서는 주고받는 data들을 message라는 것으로 정의합니다.
메시지는 여러 가지 타입의 필드로 구성됩니다.
위 예시에는 name, id, email이라는 필드를 가지는 Person이라는 메시지를 구성한 예제입니다.
추가적으로 Enumerations 및 기타 메시지 유형을 포함할 수 있습니다.
enum Corpus{
CORPUS_UNSPECIFIED=0;
CORPUS_UNIVERSAL=1;
CORPUS_WEB=2;
CORPUS_IMAGES=3;
CORPUS_LOCAL=4;
CORPUS_NEWS=5;
CORPUS_PRODUCTS=6;
CORPUS_VIDEO=7;
}
message SearchRequest{
string query=1;
int32 page_number=2;
int32 result_per_page=3;
Corpus corpus=4;
}
(생성된 코드는 특정 언어의 제약사항을 따릅니다. 따라서 enum의 지연여부를 확인해야 할 수 있습니다.)
2. Naming
message 이름은 CamelCase 형태, field 이름은 under_bar 형태를 사용할 것을 권장합니다.
유의해야 하는 점은 field 이름은 숫자로 시작할 수 없다는 점입니다.
3. Field Number
각 메시지의 정의에는 고유 번호가 존재합니다. 이 field 번호는 메시지 바이너리 형식에서 각 필드를 식별하는 데 사용되며 메시지 유형이 사용 중인 경우 변경하면 안 됩니다.
최소 1, 최대 2^29-1까지 지성할 수 있습니다.
FieldDescriptor::kFirstReservedNumber 값인 19000부터 FieldDescriptor::kLastReservedNumber값인 19999까지의 숫자는 프로토콜 버터 구현을 위해 예약된 숫자이므로 사용할 수 없습니다.
혹은 다음과 같이 사용자가 예약한 필드 번호는 사용할 수 없습니다.
message Foo{
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
4.Proto File Field Rule
proto2의 경우 required, optional를 필드 별로 명시해주어야 합니다.
proto3에서는 required, optional은 사라지고, repeated 만 사용됩니다.
repeated rule을 주게 되면 Field를 배열의 형태로도 사용할 수 있습니다.
필드는 Key-value 구조로 저장되어 repeated field를 사용해도 key가 계속 붙게 되는데 packed 옵션을 주면 value만 반복하게끔 할 수 있습니다.
중첩 타입도 규정할 수 있습니다.
message SearchResponse{
message Result{
string url=1;
string title=2;
repeated string snippets=3;
}
repeated Result results=1;
}
다음 예제와 같이 다른 메시지 유형 내부에서 메시지 유형을 정의하고 사용할 수 있습니다.
5. Importing Definitions
. proto 파일에 정의된 다른 메시지를 임포트할 수 있습니다.
.proto 파일을 새로운 위치로 변경해야 하는 경우 import public을 통해 이전의 import문의 변경 없이 새로운 import 구문을 사용하도록 지정할 수 있습니다.
다만 이 기능은 java에서 사용할 수 없습니다.
6.Package
package는 message type 이름을 중첩 없이 구분할 때 사용합니다.
7.Service
Servie는 RPC를 통해 서버가 클라이언트에게 제공할 함수의 형태를 정의합니다.
서비스명과 RPC 메서드명 모두 CamelCase 형태를 권장합니다.
옵션을 주지 않으면 단일 요청/응답으로 동작하고 stream 옵션을 주면 RPC를 구현할 수 있습니다.
. proto 파일 예시
참고 - https://grpc.io/docs/what-is-grpc/
참고 - https://ko.wikipedia.org/wiki/REST
참고 - https://ko.wikipedia.org/wiki/원격_프로시저_호출
참고 - https://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html
참고 - https://protobuf.dev/programming-guides/proto3/
'느리게 변하는 지식 > Network' 카테고리의 다른 글
Load Balancer... (0) | 2023.02.15 |
---|---|
DNS 주요 레코드 (0) | 2023.02.12 |
NAT 그리고 PAT (0) | 2023.02.11 |
FTP Active, Passive (0) | 2023.02.11 |
라우팅, 스위칭 (0) | 2023.02.09 |
댓글