@@ -108,7 +108,7 @@ GET /index.html
108108* ` Pipelining ` 이 적용되면 <BlueText >하나의 Connection 으로 다수의 요청과 응답을 처리할 수 있게 해서 Latency 를 줄일 수 있다.</BlueText >
109109
110110:::warning
111- 하지만, 완전한 멀티플렉싱이 아닌 응답 처리를 미루는 방식이여서 각 응답의 순서는 순차적으로 처리되며,
111+ 하지만, < b > 완전한 멀티플렉싱이 아닌</ b > 응답 처리를 미루는 방식이여서 각 응답의 순서는 순차적으로 처리되며,
112112결국, 앞선 응답이 지연이 생기면 자연스럽게 그 이후의 요청은 지연이 발생하게 된다.
113113:::
114114
@@ -158,8 +158,332 @@ GET /index.html
158158
159159### HTTP 2.0
160160
161- 이렇듯,
161+ ` HTTP 2.0 ` 은 ` HTTP 1.1 ` 의 문제점인, ** HOLB(Head-of-Line Blocking), Header Overhead, 연결 관리의 비효율성** 을 해결하기 위해 설계된 차세대 프로토콜이다.
162+ 2015년 5월 RFC-7540 으로 공식 표준화 되었다.
162163
163- #### 1)
164+ :::note
165+ <a href = " https://datatracker.ietf.org/doc/html/rfc7540" >RFC-7540: Hypertext Transfer Protocol Version 2 (HTTP/2)</a >
166+ :::
167+
168+ ` HTTP 2.0 ` 은 아래와 같은 주요 특징을 가지고 있다.
169+
170+ #### 1) Multiplexing
171+
172+ <div style = { { textAlign: ' center' }} >
173+ <img src = " /img/post/grpc/why/multiplexing.png" alt = " multiplexing" style = { { display: ' inline-block' }} />
174+ </div >
175+
176+ * ` HTTP 2.0 ` 은 요청와 응답을 ** 병렬로 처리** 할 수 있는 ** 멀티 플렉싱** 이다.
177+ * 여러 TCP 설정을 할 필요 없이 한 번의 TCP 연결을 통해서 병렬로 처리할 수 있어서 <BlueText >네트워크 오버헤드가 감소한다. </BlueText >
178+ * Client 가 여러 요청을 동시에 보내도, 각 요청이 독립적으로 처리되어서 <BlueText >Head-of-Line Blocking</BlueText > 문제를 해결한다.
179+ * 각 스트림에 <BlueText >우선 순위</BlueText >를 할당하여 중요한 리소스를 먼저 전송할 수 있다.
180+
181+ :::warning
182+ <b >Head-of-Line Blocking</b > 이 해결 되었다는 것은, Application Layer 에서 해결되었다는 것이다.
183+ HTTP/2 도 결국엔 TCP 위에서 동작하기 때문에 <b >Transport Layer</b > 에서의 <b >Head-of-Line Blocking</b > 은 존재한다.
184+
185+ TCP 는 순서 보장을 위한 프로토콜이어서 중간에 패킷 손실이 발생하면, 손실된 패킷을 다시 전송받을 때까지 나머지 패킷 처리가 지연된다.
186+ 하나의 TCP 연결에서 여러 요청이 stream 으로 요청이 된다고 해도 중간에 패킷 손실이 있으면 지연이 발생한다는 의미이다.
187+ <b >Head-of-Line Blocking</b > 문제는 UDP 기반의 QUIC 프로토콜(HTTP/3) 에서 해결가능하다.
188+ :::
189+
190+
191+ #### 2) Header 압축 (HPACK)
192+
193+ <div style = { { textAlign: ' center' }} >
194+ <img src = " /img/post/grpc/why/HPACK.png" alt = " HPACK" style = { { display: ' inline-block' }} />
195+ </div >
196+
197+ * ` HPACK ` 이라는 ** 헤더 압축** 방식을 통해 반복적으로 전송되는 헤더 정보를 효율적으로 관리하며, <BlueText >데이터 전송량</BlueText >을 줄이고, <BlueText >대역폭 사용</BlueText >을
198+ 최적화 한다.
199+
200+ :::note
201+ HPACK 은 <b >정적 테이블</b >로 자주 사용되는 헤더 필드를 미리 정의해서 참조하고, <b >동적 테이블</b >로 이전에 전송된 헤더 필드를 저장하고 참조하는 방식을 사용한다.
202+ 압축은 <b >허프만 인코딩</b >을 사용하여 헤더 값을 효율적으로 인코딩하여 크기를 줄인다.
203+ :::
204+
205+ #### 3) 서버 푸시 (Server Push)
206+
207+ <div style = { { textAlign: ' center' }} >
208+ <img src = " /img/post/grpc/why/server-push.png" alt = " server-push" style = { { display: ' inline-block' }} />
209+ </div >
210+
211+ * Client 가 요청하지 않은 리소스도 Server 가 미리 전송할 수 있게 페이지 로딩 속도를 개선한다.
212+ * 여러 요청/응답 사이클을 줄여 네트워크 사용을 최적화한다.
213+ * ** 우선 순위** 를 지정하여 중요한 리소스를 먼저 푸시하여 페이지 렌더링 속도를 향상 시킬 수 있다.
214+
215+ :::warning
216+ 과도한 서버 푸시는 불필요한 데이터 전송을 발생시킬 수 있다.
217+ :::
218+
219+ ---
220+
221+ ### HTTP 1.1 과 HTTP 2.0 의 차이점
222+
223+ <div style = { { textAlign: ' center' }} >
224+ <img src = " /img/post/grpc/why/http_1_1-vs2_0.png" alt = " http_1_1-vs2_0" style = { { display: ' inline-block' }} />
225+ </div >
226+
227+ | 특징| HTTP 1.1| HTTP 2.x|
228+ | --| --| --|
229+ | Connection| Persistent Connection (지속 연결) | Persistent Connection + Multiplexing|
230+ | Request Handling| 요청이 순차적으로 처리됨| 요청이 동시에 처리 가능 (Multiplexing)|
231+ | Header Compression| 없음| HPACK 으로 헤더 압축|
232+ | Protocol| 텍스트 기반| Binary 기반|
233+ | Prioritization| 없음| 스트림 우선 순위 설정 가능|
234+ | Server Push| 없음| 있음|
235+
236+
237+ ---
238+
239+ ### REST API 의 단점
240+
241+ MSA 구조에서 서버간 통신을 ` HTTP/2 ` 로만 변경해도 성능을 향상 시킬 수 있지만, ` REST ` 통신을 하게 되면 ` JSON ` 으로 인한 문제가 성능에 영향을 미칠 수 있다.
242+
243+ #### 1) Serialization / Deserialization
244+
245+ <div style = { { textAlign: ' center' }} >
246+ <img src = " /img/post/grpc/why/json-payload.png" alt = " json-payload" style = { { display: ' inline-block' }} />
247+ </div >
248+
249+ * ` JSON ` 은 <BlueText >텍스트 기반의 데이터 포맷</BlueText > 이므로, <RedText >네트워크 효율성이 떨어진다. </RedText >
250+ * 직렬화 / 역직렬화 과정에서 CPU 사용량이 많이 필요해서 MSA 간의 빈번한 데이터 교환이 있다면, 성능 저하가 될 수 있다.
251+
252+
253+ #### 2) 타입 제약
254+ * ` JSON ` 은 복잡한 데이터 구조나 날짜, 시간, 이진 데이터를 처리할 때 추가적인 파싱이나 인코딩이 필요하다.
255+
256+ #### 3) 데이터 중복
257+ * 필드를 key-value 쌍으로 표현하여 여러 개의 필드를 가진 객체에서 필드 이름이 반복되면 ** 데이트 크기가 증가한다.**
258+
259+
260+ ---
261+
262+ ### Q. gRPC 에서는 REST 의 단점을 어떻게 해결 했을까?
263+
264+ <BlueText ><span style = { { fontSize: ' 1.5rem' , }} >A. </span ></BlueText > gRPC 는 ` protobuf(Protocol Buffers) ` 로 ** JSON 의 성능 문제** 를 해결했다.
265+
266+ :::note
267+ <b >Protobuf</b >
268+ ✔ Google 이 개발한 <b > 언어 중립적 이고 플랫폼 중립적 </b > 인 데이터 직렬화 포맷이다.
269+ ✔ 다양한 프로그래밍 언어에서 동일한 스키마(proto 파일) 을 통해 데이터를 직렬화하고 역직렬화한다.
270+ ✔ gRPC 는 이 프로토콜을 활용해서 서버와 클라이언트 간의 효율적인 데이터 통신을 지원한다.
271+ :::
272+
273+
274+ #### 1) Serialization / Deserialization
275+
276+ <div style = { { textAlign: ' center' }} >
277+ <img src = " /img/post/grpc/why/json_vs_protobuf.png" alt = " json_vs_protobuf" style = { { display: ' inline-block' }} />
278+ </div >
279+
280+ * ` protobuf ` 는 바이너리 형식으로 인코딩 되어 더 작은 크기를 할당한다.
281+ * 바이너리 형식은 파싱 속도가 더 빠르고, CPU 사용량도 적다.
282+
283+ #### 2) 타입 제약
284+
285+ <div style = { { textAlign: ' center' }} >
286+ <img src = " /img/post/grpc/why/json_vs_protobuf2.png" alt = " json_vs_protobuf2" style = { { display: ' inline-block' }} />
287+ </div >
288+
289+ * ` protobuf ` 는 각 필드의 타입을 명확히 지정한다.
290+ * ` JSON ` 에서는 ` age ` 가 문자열로 되지만, ` protobuf ` 에서는 정수형으로 정의되어 타입 불일치를 방지한다.
291+ * ` Address ` 와 같은 중첩된 구조를 명확하게 정의할 수 있다.
292+
293+ #### 3) 데이터 중복
294+
295+ <div style = { { textAlign: ' center' }} >
296+ <img src = " /img/post/grpc/why/json_vs_protobuf3.png" alt = " json_vs_protobuf3" style = { { display: ' inline-block' }} />
297+ </div >
298+
299+ * ` JSON ` 에서는 각 객체마다 필드 이름이 반복되지만, ` protobuf ` 는 스키마에 한 번만 정의된다.
300+
301+ ---
302+
303+ ### JSON vs Protobuf 벤치마크
304+
305+ 간단하게 http 기반 벤치마크를 돌려보면, ` protobuf ` (protobufBenchmark) 가 ` JSON ` (jsonBenchmark) 에 비해서 초당 처리할 수 있는 수가 많은 것을 알 수 있다.
306+
307+ | Benchmark | Mode | Cnt | Score | Error | Units |
308+ | -----------------------------------------| ------| -----| ----------| ----------| -------|
309+ | SerializationBenchmark.jsonBenchmark | thrpt | 10 | 3550.345 | ± 46.399 | ops/s |
310+ | SerializationBenchmark.protobufBenchmark | thrpt | 10 | 4203.831 | ± 97.497 | ops/s |
311+
312+
313+ :::warning
314+ <b >벤치 마크 결과</b >
315+ 🏷️ <a href = " https://github.com/eottabom/lego-piece/tree/main/benchmark" >벤치 마크 결과 보기 (클릭)</a >
316+ 🏷️ <a href = " https://github.com/eottabom/lego-piece/tree/main/benchmark/src/main/java/lego/benchmark/serialization" >벤치 마크 코드 보러가기 (클릭)</a >
317+ :::
318+
319+
320+ ---
321+
322+ ### Tips) gRPC 서버 와 클라이언트 동작 원리
323+
324+ <div style = { { textAlign: ' center' }} >
325+ <img src = " /img/post/grpc/why/grpc.png" alt = " grpc" style = { { display: ' inline-block' }} />
326+ </div >
327+
328+ 출처 : https://grpc.io/docs/what-is-grpc/introduction/
329+
330+ #### gRPC 서버
331+ * Client 가 호출할 ` RPC(Remote Procedure Call) ` 메서드를 구현
332+ * 서버는 ` Protocol Buffers ` 로 정의된 ** 서비스 인터페이스(proto 파일)** 에 따라 서버측 비즈니스 로직을 구현
333+ * gRPC 서버는 클라이언트로부터 호출될 메서드를 구현체를 제공
334+
335+ #### gRPC 클라이언트
336+ * 서버와 통신하기 위한 ` stub ` 이라는 객체 사용
337+ * ` stub ` 은 클라이언트가 마치 서버에 직접 메서드를 호출하는 것처럼 RPC 메서드를 호출하는 인터페이스 제공
338+ * ` stub ` 객체가 RPC 호출을 네트워크 요청으로 변환하여 gRPC 서버로 전송
339+ * 서버는 요청을 처리한 후 응답을 클라이언트로 다시 반환
340+
341+
342+ #### 1) Protocol buffers 정의
343+ * ` .proto ` 파일에 gRPC 서비스를 정의한다. (` GetPerson ` )
344+
345+ ``` shell
346+ syntax = " proto3" ;
347+
348+ option java_package = " lego.example" ;
349+ option java_outer_classname = " PersonProto" ;
350+
351+ service PersonService {
352+ rpc GetPerson(PersonRequest) returns (PersonResponse);
353+ }
354+
355+ message PersonRequest {
356+ string name = 1;
357+ }
358+
359+ message PersonResponse {
360+ Person person = 1;
361+ }
362+
363+ message Person {
364+ string name = 1;
365+ int32 age = 2;
366+ string phoneNumber = 3;
367+ Address address = 4;
368+ }
369+
370+ message Address {
371+ string city = 1;
372+ string zipCode = 2;
373+ }
374+ ```
375+
376+ #### 2) gRPC 서버 구현
377+
378+ * 서버는 ` .proto ` 파일에 정의한 서비스를 메서드를 실제로 구현하는 역할을 한다.
379+ * ` PersonServiceImpl ` 은 ` .proto ` 파일에 정의된 ` PersonService ` 의 메서드를 실제로 구현하는 클래스
380+
381+ ``` java
382+ public final class PersonServer {
383+
384+ private static final Logger logger = LoggerFactory . getLogger(PersonServer . class);
385+
386+ private PersonServer () {
387+
388+ }
389+
390+ public static void main (String [] args ) throws IOException , InterruptedException {
391+ Server server = ServerBuilder . forPort(50051 ). addService(new PersonServiceImpl ()). build();
392+
393+ logger. info(" gRPC server start" );
394+ server. start();
395+
396+ server. awaitTermination();
397+ }
398+
399+ static class PersonServiceImpl extends PersonServiceGrpc .PersonServiceImplBase {
400+
401+ @Override
402+ public void getPerson (PersonProto .PersonRequest request ,
403+ StreamObserver<PersonProto . PersonResponse > responseObserver ) {
404+
405+ String name = request. getName();
406+ PersonProto . PersonResponse response;
407+
408+ PersonProto . Address address = PersonProto . Address . newBuilder(). setCity(" Seoul" ). setZipCode(" 1234" ). build();
409+
410+ PersonProto . Person person = PersonProto . Person . newBuilder()
411+ .setName(name)
412+ .setAge(35 )
413+ .setPhoneNumber(" 010-1234-1234" )
414+ .setAddress(address)
415+ .build();
416+
417+ response = PersonProto . PersonResponse . newBuilder(). setPerson(person). build();
418+
419+ responseObserver. onNext(response);
420+ responseObserver. onCompleted();
421+ }
422+ }
423+ }
424+ ```
425+
426+ #### 3) gRPC 클라이언트 구현
427+ * ` ManagedChannelBuilder ` 로 서버와 통신할 채널 생성
428+ * stub 생성 : ` PersonServiceGrpc.PersonServiceBlockingStub stub = PersonServiceGrpc.newBlockingStub(channel) `
429+ * ` stub.getPerson(request) ` 메서드를 호출하면 클라이언트는 gRPC 서버에 ` GetPerson ` 메서드를 원격 호출하게 된다.
430+
431+ ``` java
432+ public final class PersonClient {
433+
434+ private static final Logger logger = LoggerFactory . getLogger(PersonClient . class);
435+
436+ private PersonClient () {
437+
438+ }
439+
440+ public static void main (String [] args ) {
441+ // gRPC 채널 생성
442+ ManagedChannel channel = ManagedChannelBuilder . forAddress(" localhost" , 50051 )
443+ .usePlaintext()
444+ .build();
445+
446+ // gRPC 서비스를 호출할 stub 생성
447+ PersonServiceGrpc . PersonServiceBlockingStub stub = PersonServiceGrpc . newBlockingStub(channel);
448+
449+ PersonProto . PersonRequest request = PersonProto . PersonRequest . newBuilder()
450+ .setName(" eottabom" )
451+ .build();
452+
453+ // 서버에 RPC 호출을 보내고 응답을 받음
454+ PersonProto . PersonResponse response = stub. getPerson(request);
455+
456+ logger. info(" Server response : \n {}" , response. toString());
457+
458+ // 채널 종료
459+ channel. shutdown();
460+ }
461+
462+ }
463+ ```
464+
465+ :::warning
466+ <a href = " https://github.com/eottabom/lego-piece/tree/main/example/src/main/java/lego/example/grpc" >예제 코드 보러가기 (클릭)</a >
467+ :::
468+
469+ ---
470+
471+ :::success
472+ <b > 결론 </b >
473+ ✔ MSA 구조에서는 빈번한 네트워크 통신이 발생하여 Latency 가 발생할 수 있다.
474+ ✔ HTTP/2 는 Multiplexing 와 Header Compression 으로 HTTP 1.x 의 단점을 극복하여 지연 문제를 개선할 수 있다.
475+ ✔ gRPC 는 기본적으로 HTTP/2 기반이여서 HTTP/2 의 장점이 그대로 있다.
476+ ✔ MSA 구조에서 REST 통신을 하게 되면 JSON Payload 로 인해서 데이트 크기, 파싱 성능 등의 문제가 발생할 수 있는데, gRPC 가 protobuf 를 사용하여 데이터 전송을 더 효율적으로 할 수 있다.
477+ ✔ protobuf 를 통해 데이터 구조와 타입이 명확히 정의되어 있어서 타입 불일치나 파싱 오류를 방지할 수 있다.
478+ ✔ 언어와 플랫폼이 독립적이어서 다양한 언어/플랫폼에서 동일한 proto 파일을 이용해서 구현이 가능하다.
479+ :::
480+
481+
482+ ---
164483
484+ ### Reference
165485
486+ * [ 1. gRPC 개요] ( https://cla9.tistory.com/175 )
487+ * [ The basics of gRPC] ( https://medium.com/@josemiguelmelo/the-basics-of-grpc-b017bbedb303 )
488+ * [ What is gRPC?] ( https://blog.postman.com/what-is-grpc/ )
489+ * [ What is gRPC? Protocol Buffers, Streaming, and Architecture Explained] ( https://www.freecodecamp.org/news/what-is-grpc-protocol-buffers-stream-architecture/ )
0 commit comments