1. 소개
이 Codelab에서는 gRPC-Python을 사용하여 Python으로 작성된 경로 매핑 애플리케이션의 기반을 형성하는 클라이언트와 서버를 만듭니다.
튜토리얼이 끝나면 gRPC를 사용하여 원격 서버에 연결하여 클라이언트 경로의 기능에 관한 정보를 가져오고, 클라이언트 경로의 요약을 만들고, 서버 및 다른 클라이언트와 트래픽 업데이트와 같은 경로 정보를 교환하는 클라이언트가 있습니다.
서비스는 프로토콜 버퍼 파일에 정의되며, 이 파일은 클라이언트와 서버가 서로 통신할 수 있도록 상용구 코드를 생성하는 데 사용되므로 이 기능을 구현하는 데 드는 시간과 노력을 절약할 수 있습니다.
이 생성된 코드는 서버와 클라이언트 간의 복잡한 통신뿐만 아니라 데이터 직렬화 및 역직렬화도 처리합니다.
학습할 내용
- 프로토콜 버퍼를 사용하여 서비스 API를 정의하는 방법
- 자동 코드 생성을 사용하여 프로토콜 버퍼 정의에서 gRPC 기반 클라이언트와 서버를 빌드하는 방법
- gRPC를 사용한 클라이언트-서버 스트리밍 통신에 대한 이해
이 Codelab은 gRPC를 처음 접하거나 gRPC를 다시 살펴보려는 Python 개발자 또는 분산 시스템 빌드에 관심이 있는 모든 사용자를 대상으로 합니다. 이전 gRPC 경험은 필요하지 않습니다.
2. 시작하기 전에
필요한 항목
- Python 3.9 이상 Python 3.13을 권장합니다. 플랫폼별 설치 안내는 Python 설정 및 사용을 참고하세요. 또는 uv 또는 pyenv와 같은 도구를 사용하여 시스템이 아닌 Python을 설치합니다.
- pip를 사용하여 Python 패키지를 설치합니다.
- venv를 사용하여 Python 가상 환경을 만듭니다.
ensurepip
및 venv
패키지는 Python 표준 라이브러리의 일부이며 일반적으로 기본적으로 사용할 수 있습니다.
하지만 일부 Debian 기반 배포판 (Ubuntu 포함)에서는 python을 재배포할 때 이를 제외합니다. 패키지를 설치하려면 다음을 실행하세요.
sudo apt install python3-pip python3-venv
코드 가져오기
학습을 간소화하기 위해 이 Codelab에서는 시작하는 데 도움이 되는 사전 빌드된 소스 코드 스캐폴드를 제공합니다. 다음 단계에서는 grpc_tools.protoc
프로토콜 버퍼 컴파일러 플러그인을 사용한 gRPC 코드 생성을 비롯하여 애플리케이션을 완료하는 방법을 안내합니다.
grpc-codelabs
이 Codelab의 스캐폴드 소스 코드는 codelabs/grpc-python-streaming/start_here 디렉터리에서 확인할 수 있습니다. 코드를 직접 구현하지 않으려면 완료된 소스 코드가 completed
디렉터리에 있습니다.
먼저 Codelab 작업 디렉터리를 만들고 해당 디렉터리로 이동합니다.
mkdir grpc-python-streaming && cd grpc-python-streaming
Codelab을 다운로드하고 압축을 풉니다.
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-python-streaming/start_here
또는 codelab 디렉터리만 포함된 .zip 파일을 다운로드하고 직접 압축을 해제할 수 있습니다.
3. 메시지 및 서비스 정의
첫 번째 단계는 프로토콜 버퍼를 사용하여 애플리케이션의 gRPC 서비스, RPC 메서드, 요청 및 응답 메시지 유형을 정의하는 것입니다. 서비스에서 제공하는 기능:
- 서버가 구현하고 클라이언트가 호출하는 RPC 메서드
ListFeatures
,RecordRoute
,RouteChat
- RPC 메서드를 호출할 때 클라이언트와 서버 간에 교환되는 데이터 구조인 메시지 유형
Point
,Feature
,Rectangle
,RouteNote
,RouteSummary
이러한 RPC 메서드와 메시지 유형은 모두 제공된 소스 코드의 protos/route_guide.proto
파일에 정의됩니다.
프로토콜 버퍼는 일반적으로 protobufs로 알려져 있습니다. gRPC 용어에 대한 자세한 내용은 gRPC의 핵심 개념, 아키텍처, 수명 주기를 참고하세요.
메시지 유형 정의
소스 코드의 protos/route_guide.proto
파일에서 먼저 Point
메시지 유형을 정의합니다. Point
는 지도상의 위도-경도 좌표 쌍을 나타냅니다. 이 Codelab에서는 좌표에 정수를 사용합니다.
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
1
및 2
은 message
구조의 각 필드에 대한 고유 ID 번호입니다.
다음으로 Feature
메시지 유형을 정의합니다. Feature
는 Point
로 지정된 위치에 있는 항목의 이름이나 우편 주소에 string
필드를 사용합니다.
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
영역 내 여러 지점을 클라이언트로 스트리밍하려면 대각선으로 반대되는 두 점 lo
및 hi
로 표시되는 위도-경도 직사각형을 나타내는 Rectangle
메시지가 필요합니다.
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
또한 특정 시점에 전송된 메시지를 나타내는 RouteNote
메시지:
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
마지막으로 RouteSummary
메시지가 필요합니다. 이 메시지는 다음 섹션에서 설명하는 RecordRoute
RPC에 대한 응답으로 수신됩니다. 여기에는 수신된 개별 포인트 수, 감지된 특징 수, 각 포인트 간 거리의 누적 합계로 계산된 총 이동 거리가 포함됩니다.
message RouteSummary {
// The number of points received.
int32 point_count = 1;
// The number of known features passed while traversing the route.
int32 feature_count = 2;
// The distance covered in metres.
int32 distance = 3;
// The duration of the traversal in seconds.
int32 elapsed_time = 4;
}
서비스 메서드 정의
서비스를 정의하려면 .proto
파일에서 명명된 서비스를 지정합니다. route_guide.proto
파일에는 애플리케이션 서비스에서 제공하는 하나 이상의 메서드를 정의하는 RouteGuide
라는 service
구조가 있습니다.
서비스 정의 내에서 RPC
메서드를 정의할 때 요청 및 응답 유형을 지정합니다. Codelab의 이 섹션에서는 다음을 정의합니다.
ListFeatures
지정된 Rectangle
내에서 사용할 수 있는 Feature
객체를 가져옵니다. 결과는 한 번에 반환되지 않고 스트리밍됩니다. 사각형이 넓은 영역을 포함하고 많은 수의 기능을 포함할 수 있기 때문입니다.
이 애플리케이션에서는 서버 측 스트리밍 RPC를 사용합니다. 클라이언트는 서버에 요청을 보내고 일련의 메시지를 다시 읽는 스트림을 가져옵니다. 클라이언트는 메시지가 더 이상 없을 때까지 반환된 스트림을 읽습니다. 예에서 볼 수 있듯이 응답 유형 앞에 stream 키워드를 배치하여 서버 측 스트리밍 메서드를 지정합니다.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
이동 중인 경로의 포인트 스트림을 허용하고 이동이 완료되면 RouteSummary
를 반환합니다.
이 경우 클라이언트 측 스트리밍 RPC가 적합합니다. 클라이언트는 일련의 메시지를 작성하고 제공된 스트림을 사용하여 서버에 보냅니다. 클라이언트는 메시지 작성을 완료한 후 서버가 메시지를 모두 읽고 응답을 반환할 때까지 대기합니다. 요청 유형 앞에 스트림 키워드를 배치하여 클라이언트 측 스트리밍 메서드를 지정합니다.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
RouteChat
경로를 이동하는 동안 전송된 RouteNotes
스트림을 수락하면서 다른 RouteNotes
(예: 다른 사용자로부터)를 수신합니다.
이것이 바로 양방향 스트리밍의 사용 사례입니다. 양쪽에서 읽기-쓰기 스트림을 사용하여 일련의 메시지를 보내는 양방향 스트리밍 RPC입니다. 두 스트림은 독립적으로 작동하므로 클라이언트와 서버는 원하는 순서로 읽고 쓸 수 있습니다. 예를 들어 서버는 클라이언트 메시지를 모두 수신할 때까지 기다린 후 응답을 작성할 수도 있고, 메시지를 읽은 후 메시지를 작성하거나 읽기와 쓰기의 다른 조합을 사용할 수도 있습니다. 각 스트림의 메시지 순서는 유지됩니다. 요청과 응답 앞에 모두 스트림 키워드를 배치하여 이 유형의 메서드를 지정합니다.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
4. 클라이언트 및 서버 코드 생성
그런 다음 프로토콜 버퍼 컴파일러를 사용하여 .proto
파일에서 클라이언트와 서버 모두의 상용구 gRPC 코드를 생성합니다.
gRPC Python 코드 생성을 위해 grpcio-tools가 생성되었습니다. 다음이 포함됩니다.
message
정의에서 Python 코드를 생성하는 일반 protoc 컴파일러service
정의에서 Python 코드 (클라이언트 및 서버 스텁)를 생성하는 gRPC protobuf 플러그인
pip를 사용하여 grpcio-tools
Python 패키지를 설치합니다. 프로젝트의 종속 항목을 시스템 패키지에서 격리하기 위해 새 Python 가상 환경 (venv)을 만들어 보겠습니다.
python3 -m venv --upgrade-deps .venv
bash/zsh 셸에서 가상 환경을 활성화하려면 다음을 실행합니다.
source .venv/bin/activate
Windows 및 비표준 셸의 경우 https://docs.python.org/3/library/venv.html#how-venvs-work의 표를 참고하세요.
그런 다음 grpcio-tools를 설치합니다. 이렇게 하면 grpcio 패키지도 설치됩니다.
pip install grpcio-tools
다음 명령어를 사용하여 Python 상용구 코드를 생성합니다.
python -m grpc_tools.protoc --proto_path=./protos \
--python_out=. --pyi_out=. --grpc_python_out=. \
./protos/route_guide.proto
그러면 route_guide.proto
에 정의된 인터페이스에 대해 다음 파일이 생성됩니다.
route_guide_pb2.py
에는message
정의에서 생성된 클래스를 동적으로 생성하는 코드가 포함되어 있습니다.route_guide_pb2.pyi
은message
정의에서 생성된 '스텁 파일' 또는 '타입 힌트 파일'입니다. 구현 없이 서명만 포함합니다. 스텁 파일은 IDE에서 더 나은 자동 완성 및 오류 감지를 제공하는 데 사용할 수 있습니다.route_guide_pb2_grpc.py
는service
정의에서 생성되며 gRPC 관련 클래스와 함수를 포함합니다.
gRPC 관련 코드에는 다음이 포함됩니다.
RouteGuideStub
: gRPC 클라이언트가 RouteGuide RPC를 호출하는 데 사용할 수 있습니다.RouteGuide
서비스 구현의 인터페이스를 정의하는RouteGuideServicer
RouteGuideServicer
을 gRPC 서버에 등록하는 데 사용되는add_RouteGuideServicer_to_server
함수
5. 서버 만들기
먼저 RouteGuide
서버를 만드는 방법을 살펴보겠습니다. RouteGuide
서버를 만들고 실행하는 작업은 다음 두 가지 작업 항목으로 나뉩니다.
- 서비스의 실제 '작업'을 실행하는 함수를 사용하여 서비스 정의에서 생성된 서비스 인터페이스를 구현합니다.
- 클라이언트의 요청을 수신하고 응답을 전송하는 gRPC 서버를 실행합니다.
route_guide_server.py
을 살펴보겠습니다.
RouteGuide 구현
route_guide_server.py
에는 생성된 클래스 route_guide_pb2_grpc.RouteGuideServicer
를 서브클래스화하는 RouteGuideServicer
클래스가 있습니다.
# RouteGuideServicer provides an implementation of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):
RouteGuideServicer
은 모든 RouteGuide
서비스 메서드를 구현합니다.
서버 측 스트리밍 RPC
ListFeatures
은 클라이언트에 여러 Feature
를 전송하는 응답 스트리밍 RPC입니다.
def ListFeatures(self, request, context):
"""List all features contained within the given Rectangle."""
left = min(request.lo.longitude, request.hi.longitude)
right = max(request.lo.longitude, request.hi.longitude)
top = max(request.lo.latitude, request.hi.latitude)
bottom = min(request.lo.latitude, request.hi.latitude)
for feature in self.db:
lat, lng = feature.location.latitude, feature.location.longitude
if left <= lng <= right and bottom <= lat <= top:
yield feature
여기서 요청 메시지는 클라이언트가 Feature
를 찾으려는 route_guide_pb2.Rectangle
입니다. 이 메서드는 단일 응답을 반환하는 대신 0개 이상의 응답을 생성합니다.
클라이언트 측 스트리밍 RPC
요청 스트리밍 메서드 RecordRoute
는 요청 값의 iterator를 사용하고 단일 응답 값을 반환합니다.
def RecordRoute(self, request_iterator, context):
"""Calculate statistics about the trip composed of Points."""
point_count = 0
feature_count = 0
distance = 0.0
prev_point = None
start_time = time.time()
for point in request_iterator:
point_count += 1
if get_feature(self.db, point):
feature_count += 1
if prev_point:
distance += get_distance(prev_point, point)
prev_point = point
elapsed_time = time.time() - start_time
return route_guide_pb2.RouteSummary(
point_count=point_count,
feature_count=feature_count,
distance=int(distance),
elapsed_time=int(elapsed_time),
)
양방향 스트리밍 RPC
마지막으로 양방향 스트리밍 RPC RouteChat()
를 살펴보겠습니다.
def RouteChat(self, request_iterator, context):
"""
Receive a stream of message/location pairs, and responds with
a stream of all previous messages for the given location.
"""
prev_notes = []
for new_note in request_iterator:
for prev_note in prev_notes:
if prev_note.location == new_note.location:
yield prev_note
prev_notes.append(new_note)
이 메서드의 시맨틱은 요청 스트리밍 메서드와 응답 스트리밍 메서드의 시맨틱을 결합한 것입니다. 요청 값의 반복기가 전달되며 응답 값의 반복기 자체입니다.
서버 시작
모든 RouteGuide
메서드를 구현했다면 다음 단계는 클라이언트가 실제로 서비스를 사용할 수 있도록 gRPC 서버를 시작하는 것입니다.
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
RouteGuideServicer(),
server,
)
listen_addr = "localhost:50051"
server.add_insecure_port(listen_addr)
print(f"Starting server on {listen_addr}")
server.start()
server.wait_for_termination()
서버 start()
메서드는 차단되지 않습니다. 요청을 처리하기 위해 새 스레드가 인스턴스화됩니다. server.start()
를 호출하는 스레드는 그동안 다른 작업을 실행하지 않는 경우가 많습니다. 이 경우 server.wait_for_termination()
를 호출하여 서버가 종료될 때까지 호출 스레드를 깔끔하게 차단할 수 있습니다.
6. 클라이언트 만들기
route_guide_client.py
을 살펴보겠습니다.
스텁 만들기
서비스 메서드를 호출하려면 먼저 스텁을 만들어야 합니다.
run()
메서드에서 .proto.
로부터 생성된 route_guide_pb2_grpc
모듈의 RouteGuideStub
클래스를 인스턴스화합니다.
with grpc.insecure_channel("localhost:50051") as channel:
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
여기서 channel
는 컨텍스트 관리자로 사용되며 인터프리터가 with
블록을 벗어나면 자동으로 닫힙니다.
서비스 메서드 호출
단일 응답을 반환하는 RPC 메서드 ('response-unary' 메서드)의 경우 gRPC Python은 동기 (차단) 및 비동기 (비차단) 제어 흐름 시맨틱을 모두 지원합니다. 응답 스트리밍 RPC 메서드의 경우 호출이 즉시 응답 값의 반복자를 반환합니다. 해당 반복자의 next()
메서드 호출은 반복자에서 생성될 응답이 제공될 때까지 차단됩니다.
서버 측 스트리밍 RPC
응답 스트리밍 ListFeatures
를 호출하는 것은 시퀀스 유형을 사용하는 것과 비슷합니다.
def guide_list_features(stub):
_lo = route_guide_pb2.Point(latitude=400000000, longitude=-750000000)
_hi = route_guide_pb2.Point(latitude=420000000, longitude=-730000000)
rectangle = route_guide_pb2.Rectangle(
lo=_lo,
hi=_hi,
)
print("Looking for features between 40, -75 and 42, -73")
features = stub.ListFeatures(rectangle)
for feature in features:
print(
f"Feature called '{feature.name}'"
f" at {format_point(feature.location)}"
)
클라이언트 측 스트리밍 RPC
요청 스트리밍 RecordRoute
를 호출하는 것은 로컬 메서드에 반복자를 전달하는 것과 비슷합니다. 단일 응답을 반환하는 위의 간단한 RPC와 마찬가지로 동기적으로 호출할 수 있습니다.
def guide_record_route(stub):
feature_list = route_guide_resources.read_route_guide_database()
route_iterator = generate_route(feature_list)
route_summary = stub.RecordRoute(route_iterator)
print(f"Finished trip with {route_summary.point_count} points")
print(f"Passed {route_summary.feature_count} features")
print(f"Traveled {route_summary.distance} meters")
print(f"It took {route_summary.elapsed_time} seconds")
양방향 스트리밍 RPC
양방향 스트리밍 RouteChat
를 호출하면 서비스 측에서와 마찬가지로 요청 스트리밍과 응답 스트리밍 시맨틱이 결합됩니다.
요청 메시지를 생성하고 yield
를 사용하여 하나씩 전송합니다.
def generate_notes():
home = route_guide_pb2.Point(latitude=1, longitude=1)
work = route_guide_pb2.Point(latitude=2, longitude=2)
notes = [
make_route_note("Departing from home", home),
make_route_note("Arrived at work", work),
make_route_note("Having lunch at work", work),
make_route_note("Departing from work", work),
make_route_note("Arrived home", home),
]
for note in notes:
print(
f"Sending RouteNote for {format_point(note.location)}:"
f" {note.message}"
)
yield note
# Sleep to simulate moving from one point to another.
# Only for demonstrating the order of the messages.
time.sleep(0.1)
서버 응답을 수신하고 처리합니다.
def guide_route_chat(stub):
responses = stub.RouteChat(generate_notes())
for response in responses:
print(
"< Found previous note at"
f" {format_point(response.location)}: {response.message}"
)
도우미 메서드 호출
run에서 방금 만든 메서드를 실행하고 stub
를 전달합니다.
print("-------------- ListFeatures --------------")
guide_list_features(stub)
print("-------------- RecordRoute --------------")
guide_record_route(stub)
print("-------------- RouteChat --------------")
guide_route_chat(stub)
7. 사용해 보기
서버를 실행합니다.
python route_guide_server.py
다른 터미널에서 가상 환경을 다시 활성화하고 (source .venv/bin/activate)
) 클라이언트를 실행합니다.
python route_guide_client.py
출력을 살펴보겠습니다.
ListFeatures
먼저 기능 목록이 표시됩니다. 각 기능은 요청된 사각형 내에 있는 것으로 확인되면 서버 (서버 측 스트리밍 RPC)에서 스트리밍됩니다.
-------------- ListFeatures -------------- Looking for features between 40, -75 and 42, -73 Feature called 'Patriots Path, Mendham, NJ 07945, USA' at (lat=407838351, lng=-746143763) Feature called '101 New Jersey 10, Whippany, NJ 07981, USA' at (lat=408122808, lng=-743999179) Feature called 'U.S. 6, Shohola, PA 18458, USA' at (lat=413628156, lng=-749015468) Feature called '5 Conners Road, Kingston, NY 12401, USA' at (lat=419999544, lng=-740371136) ...
RecordRoute
두 번째로 RecordRoute
는 클라이언트에서 서버로 스트리밍된 임의로 방문한 포인트 목록을 보여줍니다(클라이언트 측 스트리밍 RPC).
-------------- RecordRoute -------------- Visiting point (lat=410395868, lng=-744972325) Visiting point (lat=404310607, lng=-740282632) Visiting point (lat=403966326, lng=-748519297) Visiting point (lat=407586880, lng=-741670168) Visiting point (lat=406589790, lng=-743560121) Visiting point (lat=410322033, lng=-747871659) Visiting point (lat=415464475, lng=-747175374) Visiting point (lat=407586880, lng=-741670168) Visiting point (lat=402647019, lng=-747071791) Visiting point (lat=414638017, lng=-745957854)
클라이언트가 방문한 모든 지점의 스트리밍을 완료하면 서버에서 비스트리밍 응답 (단항 RPC)을 수신합니다. 이 응답에는 클라이언트의 전체 경로에 대해 실행된 계산의 요약이 포함됩니다.
Finished trip with 10 points Passed 10 features Traveled 654743 meters It took 0 seconds
RouteChat
마지막으로 RouteChat
출력은 양방향 스트리밍을 보여줍니다. 클라이언트가 home
또는 work
지점을 '방문'하면 서버에 RouteNote를 전송하여 지점에 대한 메모를 기록합니다. 지점을 이미 방문한 경우 서버는 이 지점의 이전 메모를 모두 스트리밍합니다.
-------------- RouteChat -------------- Sending RouteNote for (lat=1, lng=1): Departing from home Sending RouteNote for (lat=2, lng=2): Arrived at work Sending RouteNote for (lat=2, lng=2): Having lunch at work < Found previous note at (lat=2, lng=2): Arrived at work Sending RouteNote for (lat=2, lng=2): Departing from work < Found previous note at (lat=2, lng=2): Arrived at work < Found previous note at (lat=2, lng=2): Having lunch at work Sending RouteNote for (lat=1, lng=1): Arrived home < Found previous note at (lat=1, lng=1): Departing from home
8. 다음 단계
- gRPC 소개 및 핵심 개념에서 gRPC의 작동 방식을 알아보세요.
- 기본사항 튜토리얼 살펴보기
- Python API 참조 살펴보기