gRPC는 Google에서 개발한 고성능 원격 프로시저 호출(Remote Procedure Call, RPC) 프레임워크로, 마이크로서비스 아키텍처에서 빠르고 안정적인 통신을 가능하게 한다.
HTTP/2 기반의 비동기 통신과 Protocol Buffers를 사용해 성능과 확장성을 극대화하는 것이 특징이다.
이 글에서는 gRPC의 서비스 요청 방식에 대해 설명하고, 각각의 요청 방식에 대해 다룬다.
🔍 gRPC 서비스 요청 방식이란?
gRPC에서는 클라이언트-서버 아키텍처를 기반으로 요청(Request)과 응답(Response)을 주고받는다.
다른 RPC와 달리, gRPC는 HTTP/2의 스트리밍 기능을 활용하여 다양한 요청/응답 방식을 지원한다.
gRPC의 네 가지 서비스 요청 방식
- Unary RPC ➡️ 클라이언트(요청 1회) -> 서버 (응답 1회)
- Server Streaming RPC ➡️ 클라이언트(요청 1회) -> 서버(여러 응답 스트리밍)
- Client Streaming RPC ➡️ 클라이언트(여러 요청 스트리밍) -> 서버(응답 1회)
- Bidirectional Streaming RPC ➡️ 클라이언트 <-> 서버 -> 양방향 스트리밍 통신
1️⃣ Unary RPC (단일 요청-응답)

Unary RPC는 가장 기본적인 요청-응답 방식이다.
- 클라이언트(요청): 1번 전송
- 서버(응답): 1번 반환
✔ 마치 REST API의 POST 요청처럼 동작하며, 가장 일반적인 형태이다.
예시: 사용자 정보 요청
🔹 ProtoBuf 정의 (user_service.proto)
syntax = "proto3";
service UserService {
rpc GetUser(UserRequest) returns (UserResponse);
}
message UserRequest {
int32 user_id = 1;
}
message UserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
}
🔹 서버 측 구현 (Java)
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
// 사용자 정보 조회
UserResponse response = UserResponse.newBuilder()
.setUserId(request.getUserId())
.setName("Alice")
.setEmail("alice@example.com")
.build();
// 응답 전송
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
🔹 클라이언트 요청 (Java)
UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel);
UserRequest request = UserRequest.newBuilder().setUserId(1).build();
UserResponse response = stub.getUser(request);
System.out.println("User Name: " + response.getName());
✔️ 결과: 단일 요청에 대해 단일 응답을 받아 출력한다.
2️⃣ Server Streaming RPC (서버 스트리밍 요청)

Server Streaming RPC는 클라이언트가 요청을 한 번 보내고, 서버에서 여러 번 데이터를 스트리밍 형태로 응답한다.
💭 주로 대용량 데이터 전송이나 실시간 로그 처리 등에 사용된다.
예시: 사용자 활동 로그 요청
🔹 ProtoBuf 정의
service ActivityService {
rpc GetUserActivities(UserRequest) returns (stream ActivityResponse);
}
message ActivityResponse {
string activity = 1;
string timestamp = 2;
}
🔹 서버 측 구현 (Java)
public class ActivityServiceImpl extends ActivityServiceGrpc.ActivityServiceImplBase {
@Override
public void getUserActivities(UserRequest request, StreamObserver<ActivityResponse> responseObserver) {
// 예시: 사용자 활동 스트리밍
for (int i = 0; i < 5; i++) {
ActivityResponse response = ActivityResponse.newBuilder()
.setActivity("Login Event " + i)
.setTimestamp(Instant.now().toString())
.build();
responseObserver.onNext(response); // 스트리밍으로 응답 전송
}
responseObserver.onCompleted(); // 스트리밍 종료
}
}
🔹 클라이언트 요청 (Java)
ActivityServiceGrpc.ActivityServiceBlockingStub stub = ActivityServiceGrpc.newBlockingStub(channel);
UserRequest request = UserRequest.newBuilder().setUserId(1).build();
stub.getUserActivities(request).forEachRemaining(activity -> {
System.out.println("Activity: " + activity.getActivity() + ", Time: " + activity.getTimestamp());
});
✔ 결과: 여러 번 응답을 받으며 실시간으로 출력됩니다.
3️⃣ Client Streaming RPC (클라이언트 스트리밍 요청)

Client Streaming RPC는 클라이언트에서 여러 요청을 스트리밍으로 전송하고, 서버에서 요청 수신이 끝난 후 한 번의 응답을 반환한다.
💭 주로 데이터 업로드나 배치 작업에 유용하게 사용됩니다.
예시: 파일 업로드
🔹 ProtoBuf 정의
service FileService {
rpc UploadFile(stream FileChunk) returns (UploadStatus);
}
message FileChunk {
bytes data = 1;
}
message UploadStatus {
string message = 1;
}
🔹 서버 측 구현 (Java)
public class FileServiceImpl extends FileServiceGrpc.FileServiceImplBase {
@Override
public StreamObserver<FileChunk> uploadFile(StreamObserver<UploadStatus> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(FileChunk chunk) {
// 파일 데이터를 처리
System.out.println("Received chunk of size: " + chunk.getData().size());
}
@Override
public void onCompleted() {
UploadStatus status = UploadStatus.newBuilder().setMessage("Upload Completed!").build();
responseObserver.onNext(status);
responseObserver.onCompleted();
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
};
}
}
🔹 클라이언트 요청 (Java)
StreamObserver<FileChunk> requestObserver = fileServiceStub.uploadFile(new StreamObserver<UploadStatus>() {
@Override
public void onNext(UploadStatus status) {
System.out.println(status.getMessage());
}
@Override
public void onError(Throwable t) {}
@Override
public void onCompleted() {}
});
// 파일 데이터를 스트리밍 전송
requestObserver.onNext(FileChunk.newBuilder().setData(ByteString.copyFrom(bytesChunk)).build());
requestObserver.onCompleted();
✔ 결과: 클라이언트에서 파일 데이터를 여러 번 보내고, 전송이 끝나면 응답을 받는다.
4️⃣ Bidirectional Streaming RPC (양방향 스트리밍 요청)

Bidirectional Streaming RPC는 클라이언트와 서버가 동시에 데이터를 스트리밍하는 방식이다.
- 클라이언트 -> 요청 스트리밍 전송
- 서버 -> 응답 스트리밍 반환
💭 실시간 채팅, 스트리밍 게임 데이터 처리 등에 사용된다.
예시: 실시간 채팅 서비스
🔹 ProtoBuf 정의
service ChatService {
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string user = 1;
string message = 2;
}
🔹 서버 측 구현 (Java)
public class ChatServiceImpl extends ChatServiceGrpc.ChatServiceImplBase {
@Override
public StreamObserver<ChatMessage> chat(StreamObserver<ChatMessage> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(ChatMessage message) {
// 클라이언트 메시지 수신
System.out.println("Received message from " + message.getUser());
responseObserver.onNext(ChatMessage.newBuilder()
.setUser("Server")
.setMessage("Echo: " + message.getMessage())
.build());
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
};
}
}
🔹 클라이언트 요청 (Java)
StreamObserver<ChatMessage> requestObserver = chatStub.chat(new StreamObserver<ChatMessage>() {
@Override
public void onNext(ChatMessage message) {
System.out.println(message.getUser() + ": " + message.getMessage());
}
@Override
public void onError(Throwable t) {}
@Override
public void onCompleted() {}
});
// 메시지 전송
requestObserver.onNext(ChatMessage.newBuilder().setUser("Client").setMessage("Hello!").build());
✔ 결과: 클라이언트와 서버가 동시에 메시지를 주고받으며 실시간 소통 가능
🔍 gRPC와 Protobuf의 상호작용
Protocol Buffers(Protobuf)는 gRPC에서 사용하는 기본 데이터 직렬화 포맷이다.
gRPC의 빠른 처리 속도와 네트워크 효율성은 대부분 이 Protobuf와의 결합에서 나온다.
1️⃣ 주요 인터페이스 정의 언어 (IDL)
gRPC는 Protobuf를 통해 서비스와 메시지의 구조를 정의한다. 이때 사용되는 언어가 바로 IDL(Interface Definition Language)이다.
예시: Protobuf를 사용한 IDL 정의
syntax = "proto3";
service UserService {
rpc GetUser(UserRequest) returns (UserResponse);
}
message UserRequest {
int32 user_id = 1;
}
message UserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
}
- service ➡️ 서비스의 인터페이스 정의
- rpc ➡️ 원격 호출 메서드 정의
- message ➡️ 데이터 구조 정의
2️⃣ 효율적인 데이터 직렬화
Protobuf는 텍스트 기반 JSON에 비해 훨씬 작고 빠른 바이너리 포맷을 사용한다.
- 직렬화(Serialization) ➡️ 데이터를 바이너리 형태로 변환해 전송
- 역직렬화(Deserialization) ➡️ 수신한 데이터를 원래 객체로 복원
3️⃣ 네트워크 효율성 향상
- 작은 메시지 크기 ➡️ 대역폭 절감
- 빠른 직렬화/역직렬화 ➡️ 빠른 요청 및 응답 처리
🔗 gRPC와 HTTP/2: HTTP/2 사용의 이점 및 주의 사항
gRPC는 HTTP/2 프로토콜을 기반으로 동작한다.
HTTP/1.1의 한계를 극복하며 다음과 같은 기능적 이점을 제공한다.
✅ 스트리밍 (Streaming)
- 양방향으로 데이터를 실시간 주고받을 수 있는 기능
- 실시간 채팅, 비디오 스트리밍 서비스에 활용 가능
✅ 헤더 압축 (Header Compression)
- HTTP/2는 HPACK 압축 알고리즘을 사용해 헤더 크기를 줄여 네트워크 오버헤드 감소
- 중복된 헤더 데이터를 줄여 빠른 요청-응답 처리 가능
✅ 멀티플렉싱 (Multiplexing)
- 하나의 연결로 여러 요청/응답이 동시에 처리 가능
- REST API의 HTTP/1.1에서는 동시 요청 시 헤드 오브 라인(Head-of-Line) 블로킹 문제가 발생하지만, gRPC는 이 문제를 해결하여 빠른 병렬 처리 지원
🚨 gRPC 스크리밍 사용 시 주의 사항
gRPC 스트리밍은 클라이언트와 서버 간에 데이터를 실시간으로 주고받을 수 있는 강력한 기능을 제공한다.
그러나 스트리밍을 올바르게 사용하지 않으면 리소스 낭비, 오류 처리 문제, 성능 저하 등의 문제가 발생할 수 있다.
1️⃣ 리소스 관리에 유의
- gRPC 스트리밍은 연결을 지속적으로 유지하기 때문에, 불필요한 연결이 많아지면 서버 및 클라이언트 리소스를 과도하게 사용할 수 있다.
- 스트리밍을 사용할 때는 불필요한 연결을 즉시 닫고, 적절한 타임아웃을 설정해야 한다.
예제 (Java - 스트리밍 종료)
responseObserver.onCompleted(); // 스트리밍 종료 후 리소스 해제
- 연결 유지 시간이 너무 길어지지 않도록 관리해야 한다.
2️⃣ 다양한 오류 처리 필요성
- 스트리밍 환경에서는 네트워크 장애, 서버 다운, 클라이언트 중단 등 다양한 오류가 발생할 수 있다.
- 재시도(retry) 로직과 적절한 오류 코드 처리가 필요하다.
- 특히, gRPC의 상태 코드(Status Code)를 활용하여 오류 원인을 파악하는 것이 중요하다.
예제 (Java - 오류 처리)
@Override
public void onError(Throwable t) {
Status status = Status.fromThrowable(t);
log.info("Error occurred: {}", status);
}
- 예상치 못한 네트워크 문제나 서버 다운을 고려한 예외 처리가 필요하다.
3️⃣ Backpressure 관리의 필요성
- Backpressure란 생산자(서버)와 소비자(클라이언트) 간의 처리 속도 차이로 인해 발생하는 문제를 의미한다.
- 서버가 너무 빠르게 데이터를 보내면, 클라이언트가 이를 제대로 처리하지 못할 수 있다.
- 이를 방지하려면 클라이언트에서 데이터를 수신할 수 있는 속도를 서버가 고려하여 조절해야 한다.
예제 (Java - Flow Control 적용)
responseObserver.request(10); // 클라이언트가 처리할 수 있는 개수만큼 요청
- 클라이언트가 감당할 수 있는 만큼만 데이터를 수신하도록 조절해야 한다.
gRPC 서비스 요청 방식: HTTP/2와의 상호작용 예시
gRPC 요청은 HTTP/2의 스트림(Stream)을 통해 이루어진다.
각 RPC 호출은 고유의 스트림 ID를 가지며, 여러 스트림이 하나의 TCP 연결을 공유한다.
예시: 단일 요청-응답 흐름 (Unary RPC)
- 클라이언트 -> 서버로 요청 (HTTP/2 프레임 전송)
- 서버 -> 클라이언트로 응답 (헤더 + 데이터 프레임 전송)
- 스트림 종료 (서버와 클라이언트 간 통신 종료)
gRPC 서비스 요청 방식: 실제 요청 및 응답 프로토콜 분석
gRPC에서 요청과 응답은 HTTP/2 프레임을 통해 전송된다.
1️⃣ gRPC HTTP/2 요청 헤더
:method: POST
:scheme: http 또는 https
:path: 호출할 서비스 경로 (ex: /UserService/GetUser)
content-type: application/grpc
2️⃣ gRPC HTTP/2 응답 헤더
content-type: application/grpc
grpc-status: 요청 처리 상태 (0 → 성공, 1 → 에러 발생)
grpc-message: 에러 메시지 (에러 발생 시)
3️⃣ gRPC 요청 메시지 (Protobuf 직렬화)
\x00\x00\x00\x00\x10\x08\x01\x12\x05Alice\x1A\x11alice@example.com
- 첫 번째 바이트 -> 메시지 압축 여부 (0 = 비압축)
- 다음 4바이트 -> 메시지 길이 정보
4️⃣ gRPC 응답 메시지 (Protobuf 직렬화)
- 바이너리 형식으로 응답 데이터 전송
- 클라이언트는 이 데이터를 역직렬화해 객체로 복원
gRPC 서비스 요청 방식: 양방향 스트리밍 예시
1️⃣ 스트리밍 시작 요청 및 응답
- 클라이언트와 서버가 스트림을 설정하고 연결
2️⃣ 헤더 프레임 전송
- 클라이언트와 서버가 각각 필요한 정보를 HTTP/2 헤더에 담아 전송
3️⃣ 데이터 프레임 전송 (스트리밍 중)
- 클라이언트 -> 여러 데이터 전송
- 서버 -> 실시간으로 응답
4️⃣ 추가 헤더 전송 옵션
- 스트리밍 중 메타데이터나 새로운 설정 정보를 실시간으로 전달 가능
5️⃣ 스트리밍 종료 요청 및 응답
- 클라이언트나 서버에서 스트림 종료 신호 전송
gRPC 스트리밍 모범 사례
- 스트림 관련 전략 수립: 타임아웃, 하트비트 또는 기타 커넥션 관리 구현 필요
- 메시지 크기 최적화: 메시지 정의를 최적화하여 필드를 효율적으로 사용
- 스트림 처리 로직 분리: 스트림 처리를 위한 별도의 서비스 또는 모듈 구성
- 동시성과 병렬 처리 활용: 양방향 스트리밍에서는 서버가 동시에 여러 클라이언트부터 스트림을 처리 해야함
- 오류 및 상태 모니터링: 스트리밍 서비스의 상태와 성능을 지속적으로 모니터링하고, 오류 발생 시 알림을 받을 수 있는 로깅 및 모니터링 시스템 구축
🔥 gRPC의 Stream과 Repeated의 차이
특징StreamRepeated
특징 | Stream | Repeated |
데이터 처리 방식 | 스트리밍 데이터를 실시간으로 전송 | 모든 데이터를 한 번에 전송 |
네트워크 효율성 | 네트워크 효율적, 필요한 순간에 전송 | 데이터 양이 많을수록 비효율적 |
메모리 사용량 | 작은 메모리 사용 | 전체 데이터를 한 번에 저장해야 함 |
언제 사용해야 할까? | 데이터가 실시간으로 전송되거나, 양이 많을 때 | 데이터가 비교적 작고 일괄 전송이 가능할 때 |
예시 | 실시간 채팅, 로그 스트리밍 | 사용자 목록 전체 반환 |
'기술(Tech) > Network & System' 카테고리의 다른 글
[gRPC] gRPC 상태 코드 (0) | 2025.03.03 |
---|---|
[gRPC] RESTful API와 gRPC 간 변환 방법 (with. gRPC-Web) (1) | 2025.03.02 |
[gRPC] RPC란? (0) | 2025.02.23 |
[gRPC] Protocol Buffers(ProtoBuf): 고성능 데이터 직렬화 포맷 (0) | 2025.02.15 |
[gRPC] gRPC에 대해 알아보자 (0) | 2025.02.15 |
gRPC는 Google에서 개발한 고성능 원격 프로시저 호출(Remote Procedure Call, RPC) 프레임워크로, 마이크로서비스 아키텍처에서 빠르고 안정적인 통신을 가능하게 한다.
HTTP/2 기반의 비동기 통신과 Protocol Buffers를 사용해 성능과 확장성을 극대화하는 것이 특징이다.
이 글에서는 gRPC의 서비스 요청 방식에 대해 설명하고, 각각의 요청 방식에 대해 다룬다.
🔍 gRPC 서비스 요청 방식이란?
gRPC에서는 클라이언트-서버 아키텍처를 기반으로 요청(Request)과 응답(Response)을 주고받는다.
다른 RPC와 달리, gRPC는 HTTP/2의 스트리밍 기능을 활용하여 다양한 요청/응답 방식을 지원한다.
gRPC의 네 가지 서비스 요청 방식
- Unary RPC ➡️ 클라이언트(요청 1회) -> 서버 (응답 1회)
- Server Streaming RPC ➡️ 클라이언트(요청 1회) -> 서버(여러 응답 스트리밍)
- Client Streaming RPC ➡️ 클라이언트(여러 요청 스트리밍) -> 서버(응답 1회)
- Bidirectional Streaming RPC ➡️ 클라이언트 <-> 서버 -> 양방향 스트리밍 통신
1️⃣ Unary RPC (단일 요청-응답)

Unary RPC는 가장 기본적인 요청-응답 방식이다.
- 클라이언트(요청): 1번 전송
- 서버(응답): 1번 반환
✔ 마치 REST API의 POST 요청처럼 동작하며, 가장 일반적인 형태이다.
예시: 사용자 정보 요청
🔹 ProtoBuf 정의 (user_service.proto)
syntax = "proto3";
service UserService {
rpc GetUser(UserRequest) returns (UserResponse);
}
message UserRequest {
int32 user_id = 1;
}
message UserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
}
🔹 서버 측 구현 (Java)
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
// 사용자 정보 조회
UserResponse response = UserResponse.newBuilder()
.setUserId(request.getUserId())
.setName("Alice")
.setEmail("alice@example.com")
.build();
// 응답 전송
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
🔹 클라이언트 요청 (Java)
UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel);
UserRequest request = UserRequest.newBuilder().setUserId(1).build();
UserResponse response = stub.getUser(request);
System.out.println("User Name: " + response.getName());
✔️ 결과: 단일 요청에 대해 단일 응답을 받아 출력한다.
2️⃣ Server Streaming RPC (서버 스트리밍 요청)

Server Streaming RPC는 클라이언트가 요청을 한 번 보내고, 서버에서 여러 번 데이터를 스트리밍 형태로 응답한다.
💭 주로 대용량 데이터 전송이나 실시간 로그 처리 등에 사용된다.
예시: 사용자 활동 로그 요청
🔹 ProtoBuf 정의
service ActivityService {
rpc GetUserActivities(UserRequest) returns (stream ActivityResponse);
}
message ActivityResponse {
string activity = 1;
string timestamp = 2;
}
🔹 서버 측 구현 (Java)
public class ActivityServiceImpl extends ActivityServiceGrpc.ActivityServiceImplBase {
@Override
public void getUserActivities(UserRequest request, StreamObserver<ActivityResponse> responseObserver) {
// 예시: 사용자 활동 스트리밍
for (int i = 0; i < 5; i++) {
ActivityResponse response = ActivityResponse.newBuilder()
.setActivity("Login Event " + i)
.setTimestamp(Instant.now().toString())
.build();
responseObserver.onNext(response); // 스트리밍으로 응답 전송
}
responseObserver.onCompleted(); // 스트리밍 종료
}
}
🔹 클라이언트 요청 (Java)
ActivityServiceGrpc.ActivityServiceBlockingStub stub = ActivityServiceGrpc.newBlockingStub(channel);
UserRequest request = UserRequest.newBuilder().setUserId(1).build();
stub.getUserActivities(request).forEachRemaining(activity -> {
System.out.println("Activity: " + activity.getActivity() + ", Time: " + activity.getTimestamp());
});
✔ 결과: 여러 번 응답을 받으며 실시간으로 출력됩니다.
3️⃣ Client Streaming RPC (클라이언트 스트리밍 요청)

Client Streaming RPC는 클라이언트에서 여러 요청을 스트리밍으로 전송하고, 서버에서 요청 수신이 끝난 후 한 번의 응답을 반환한다.
💭 주로 데이터 업로드나 배치 작업에 유용하게 사용됩니다.
예시: 파일 업로드
🔹 ProtoBuf 정의
service FileService {
rpc UploadFile(stream FileChunk) returns (UploadStatus);
}
message FileChunk {
bytes data = 1;
}
message UploadStatus {
string message = 1;
}
🔹 서버 측 구현 (Java)
public class FileServiceImpl extends FileServiceGrpc.FileServiceImplBase {
@Override
public StreamObserver<FileChunk> uploadFile(StreamObserver<UploadStatus> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(FileChunk chunk) {
// 파일 데이터를 처리
System.out.println("Received chunk of size: " + chunk.getData().size());
}
@Override
public void onCompleted() {
UploadStatus status = UploadStatus.newBuilder().setMessage("Upload Completed!").build();
responseObserver.onNext(status);
responseObserver.onCompleted();
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
};
}
}
🔹 클라이언트 요청 (Java)
StreamObserver<FileChunk> requestObserver = fileServiceStub.uploadFile(new StreamObserver<UploadStatus>() {
@Override
public void onNext(UploadStatus status) {
System.out.println(status.getMessage());
}
@Override
public void onError(Throwable t) {}
@Override
public void onCompleted() {}
});
// 파일 데이터를 스트리밍 전송
requestObserver.onNext(FileChunk.newBuilder().setData(ByteString.copyFrom(bytesChunk)).build());
requestObserver.onCompleted();
✔ 결과: 클라이언트에서 파일 데이터를 여러 번 보내고, 전송이 끝나면 응답을 받는다.
4️⃣ Bidirectional Streaming RPC (양방향 스트리밍 요청)

Bidirectional Streaming RPC는 클라이언트와 서버가 동시에 데이터를 스트리밍하는 방식이다.
- 클라이언트 -> 요청 스트리밍 전송
- 서버 -> 응답 스트리밍 반환
💭 실시간 채팅, 스트리밍 게임 데이터 처리 등에 사용된다.
예시: 실시간 채팅 서비스
🔹 ProtoBuf 정의
service ChatService {
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string user = 1;
string message = 2;
}
🔹 서버 측 구현 (Java)
public class ChatServiceImpl extends ChatServiceGrpc.ChatServiceImplBase {
@Override
public StreamObserver<ChatMessage> chat(StreamObserver<ChatMessage> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(ChatMessage message) {
// 클라이언트 메시지 수신
System.out.println("Received message from " + message.getUser());
responseObserver.onNext(ChatMessage.newBuilder()
.setUser("Server")
.setMessage("Echo: " + message.getMessage())
.build());
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
};
}
}
🔹 클라이언트 요청 (Java)
StreamObserver<ChatMessage> requestObserver = chatStub.chat(new StreamObserver<ChatMessage>() {
@Override
public void onNext(ChatMessage message) {
System.out.println(message.getUser() + ": " + message.getMessage());
}
@Override
public void onError(Throwable t) {}
@Override
public void onCompleted() {}
});
// 메시지 전송
requestObserver.onNext(ChatMessage.newBuilder().setUser("Client").setMessage("Hello!").build());
✔ 결과: 클라이언트와 서버가 동시에 메시지를 주고받으며 실시간 소통 가능
🔍 gRPC와 Protobuf의 상호작용
Protocol Buffers(Protobuf)는 gRPC에서 사용하는 기본 데이터 직렬화 포맷이다.
gRPC의 빠른 처리 속도와 네트워크 효율성은 대부분 이 Protobuf와의 결합에서 나온다.
1️⃣ 주요 인터페이스 정의 언어 (IDL)
gRPC는 Protobuf를 통해 서비스와 메시지의 구조를 정의한다. 이때 사용되는 언어가 바로 IDL(Interface Definition Language)이다.
예시: Protobuf를 사용한 IDL 정의
syntax = "proto3";
service UserService {
rpc GetUser(UserRequest) returns (UserResponse);
}
message UserRequest {
int32 user_id = 1;
}
message UserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
}
- service ➡️ 서비스의 인터페이스 정의
- rpc ➡️ 원격 호출 메서드 정의
- message ➡️ 데이터 구조 정의
2️⃣ 효율적인 데이터 직렬화
Protobuf는 텍스트 기반 JSON에 비해 훨씬 작고 빠른 바이너리 포맷을 사용한다.
- 직렬화(Serialization) ➡️ 데이터를 바이너리 형태로 변환해 전송
- 역직렬화(Deserialization) ➡️ 수신한 데이터를 원래 객체로 복원
3️⃣ 네트워크 효율성 향상
- 작은 메시지 크기 ➡️ 대역폭 절감
- 빠른 직렬화/역직렬화 ➡️ 빠른 요청 및 응답 처리
🔗 gRPC와 HTTP/2: HTTP/2 사용의 이점 및 주의 사항
gRPC는 HTTP/2 프로토콜을 기반으로 동작한다.
HTTP/1.1의 한계를 극복하며 다음과 같은 기능적 이점을 제공한다.
✅ 스트리밍 (Streaming)
- 양방향으로 데이터를 실시간 주고받을 수 있는 기능
- 실시간 채팅, 비디오 스트리밍 서비스에 활용 가능
✅ 헤더 압축 (Header Compression)
- HTTP/2는 HPACK 압축 알고리즘을 사용해 헤더 크기를 줄여 네트워크 오버헤드 감소
- 중복된 헤더 데이터를 줄여 빠른 요청-응답 처리 가능
✅ 멀티플렉싱 (Multiplexing)
- 하나의 연결로 여러 요청/응답이 동시에 처리 가능
- REST API의 HTTP/1.1에서는 동시 요청 시 헤드 오브 라인(Head-of-Line) 블로킹 문제가 발생하지만, gRPC는 이 문제를 해결하여 빠른 병렬 처리 지원
🚨 gRPC 스크리밍 사용 시 주의 사항
gRPC 스트리밍은 클라이언트와 서버 간에 데이터를 실시간으로 주고받을 수 있는 강력한 기능을 제공한다.
그러나 스트리밍을 올바르게 사용하지 않으면 리소스 낭비, 오류 처리 문제, 성능 저하 등의 문제가 발생할 수 있다.
1️⃣ 리소스 관리에 유의
- gRPC 스트리밍은 연결을 지속적으로 유지하기 때문에, 불필요한 연결이 많아지면 서버 및 클라이언트 리소스를 과도하게 사용할 수 있다.
- 스트리밍을 사용할 때는 불필요한 연결을 즉시 닫고, 적절한 타임아웃을 설정해야 한다.
예제 (Java - 스트리밍 종료)
responseObserver.onCompleted(); // 스트리밍 종료 후 리소스 해제
- 연결 유지 시간이 너무 길어지지 않도록 관리해야 한다.
2️⃣ 다양한 오류 처리 필요성
- 스트리밍 환경에서는 네트워크 장애, 서버 다운, 클라이언트 중단 등 다양한 오류가 발생할 수 있다.
- 재시도(retry) 로직과 적절한 오류 코드 처리가 필요하다.
- 특히, gRPC의 상태 코드(Status Code)를 활용하여 오류 원인을 파악하는 것이 중요하다.
예제 (Java - 오류 처리)
@Override
public void onError(Throwable t) {
Status status = Status.fromThrowable(t);
log.info("Error occurred: {}", status);
}
- 예상치 못한 네트워크 문제나 서버 다운을 고려한 예외 처리가 필요하다.
3️⃣ Backpressure 관리의 필요성
- Backpressure란 생산자(서버)와 소비자(클라이언트) 간의 처리 속도 차이로 인해 발생하는 문제를 의미한다.
- 서버가 너무 빠르게 데이터를 보내면, 클라이언트가 이를 제대로 처리하지 못할 수 있다.
- 이를 방지하려면 클라이언트에서 데이터를 수신할 수 있는 속도를 서버가 고려하여 조절해야 한다.
예제 (Java - Flow Control 적용)
responseObserver.request(10); // 클라이언트가 처리할 수 있는 개수만큼 요청
- 클라이언트가 감당할 수 있는 만큼만 데이터를 수신하도록 조절해야 한다.
gRPC 서비스 요청 방식: HTTP/2와의 상호작용 예시
gRPC 요청은 HTTP/2의 스트림(Stream)을 통해 이루어진다.
각 RPC 호출은 고유의 스트림 ID를 가지며, 여러 스트림이 하나의 TCP 연결을 공유한다.
예시: 단일 요청-응답 흐름 (Unary RPC)
- 클라이언트 -> 서버로 요청 (HTTP/2 프레임 전송)
- 서버 -> 클라이언트로 응답 (헤더 + 데이터 프레임 전송)
- 스트림 종료 (서버와 클라이언트 간 통신 종료)
gRPC 서비스 요청 방식: 실제 요청 및 응답 프로토콜 분석
gRPC에서 요청과 응답은 HTTP/2 프레임을 통해 전송된다.
1️⃣ gRPC HTTP/2 요청 헤더
:method: POST
:scheme: http 또는 https
:path: 호출할 서비스 경로 (ex: /UserService/GetUser)
content-type: application/grpc
2️⃣ gRPC HTTP/2 응답 헤더
content-type: application/grpc
grpc-status: 요청 처리 상태 (0 → 성공, 1 → 에러 발생)
grpc-message: 에러 메시지 (에러 발생 시)
3️⃣ gRPC 요청 메시지 (Protobuf 직렬화)
\x00\x00\x00\x00\x10\x08\x01\x12\x05Alice\x1A\x11alice@example.com
- 첫 번째 바이트 -> 메시지 압축 여부 (0 = 비압축)
- 다음 4바이트 -> 메시지 길이 정보
4️⃣ gRPC 응답 메시지 (Protobuf 직렬화)
- 바이너리 형식으로 응답 데이터 전송
- 클라이언트는 이 데이터를 역직렬화해 객체로 복원
gRPC 서비스 요청 방식: 양방향 스트리밍 예시
1️⃣ 스트리밍 시작 요청 및 응답
- 클라이언트와 서버가 스트림을 설정하고 연결
2️⃣ 헤더 프레임 전송
- 클라이언트와 서버가 각각 필요한 정보를 HTTP/2 헤더에 담아 전송
3️⃣ 데이터 프레임 전송 (스트리밍 중)
- 클라이언트 -> 여러 데이터 전송
- 서버 -> 실시간으로 응답
4️⃣ 추가 헤더 전송 옵션
- 스트리밍 중 메타데이터나 새로운 설정 정보를 실시간으로 전달 가능
5️⃣ 스트리밍 종료 요청 및 응답
- 클라이언트나 서버에서 스트림 종료 신호 전송
gRPC 스트리밍 모범 사례
- 스트림 관련 전략 수립: 타임아웃, 하트비트 또는 기타 커넥션 관리 구현 필요
- 메시지 크기 최적화: 메시지 정의를 최적화하여 필드를 효율적으로 사용
- 스트림 처리 로직 분리: 스트림 처리를 위한 별도의 서비스 또는 모듈 구성
- 동시성과 병렬 처리 활용: 양방향 스트리밍에서는 서버가 동시에 여러 클라이언트부터 스트림을 처리 해야함
- 오류 및 상태 모니터링: 스트리밍 서비스의 상태와 성능을 지속적으로 모니터링하고, 오류 발생 시 알림을 받을 수 있는 로깅 및 모니터링 시스템 구축
🔥 gRPC의 Stream과 Repeated의 차이
특징StreamRepeated
특징 | Stream | Repeated |
데이터 처리 방식 | 스트리밍 데이터를 실시간으로 전송 | 모든 데이터를 한 번에 전송 |
네트워크 효율성 | 네트워크 효율적, 필요한 순간에 전송 | 데이터 양이 많을수록 비효율적 |
메모리 사용량 | 작은 메모리 사용 | 전체 데이터를 한 번에 저장해야 함 |
언제 사용해야 할까? | 데이터가 실시간으로 전송되거나, 양이 많을 때 | 데이터가 비교적 작고 일괄 전송이 가능할 때 |
예시 | 실시간 채팅, 로그 스트리밍 | 사용자 목록 전체 반환 |
'기술(Tech) > Network & System' 카테고리의 다른 글
[gRPC] gRPC 상태 코드 (0) | 2025.03.03 |
---|---|
[gRPC] RESTful API와 gRPC 간 변환 방법 (with. gRPC-Web) (1) | 2025.03.02 |
[gRPC] RPC란? (0) | 2025.02.23 |
[gRPC] Protocol Buffers(ProtoBuf): 고성능 데이터 직렬화 포맷 (0) | 2025.02.15 |
[gRPC] gRPC에 대해 알아보자 (0) | 2025.02.15 |