Skip to content

Commit a2b8fad

Browse files
committed
feat: why using grpc step2
1 parent 243e579 commit a2b8fad

File tree

1 file changed

+327
-3
lines changed

1 file changed

+327
-3
lines changed

posts/grpc/why-using-grpc.mdx

Lines changed: 327 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)