GRPC
Simple service definition
Define your service using Protocol Buffers, a powerful binary serialization toolset and language
Categories
Clients
ETC
- grpcio-tools - Protobuf code generator for gRPC (python)
- dapr - Distributed Application Runtime
- grpc-proxy - gRPC proxy is a Go reverse proxy that allows for rich routing of gRPC calls with minimum overhead.
- Caddy
asyncio
- [추천] gRPC AsyncIO API — gRPC Python 1.33.2 documentation
- Stackoverflow - How can I use gRPC with asyncio (gRPC with asyncio)
- Stackoverflow - How to implement a async grpc python server?
- Stackoverflow - handling async streaming request in grpc python
- [추천] Running an HTTP & GRPC Python Server Concurrently on AWS Elastic Beanstalk 1 (aiohttp, grpc)
Projects
- Pure-Python gRPC implementation for asyncio
- https://github.com/vmagamedov/grpclib
- https://grpclib.readthedocs.io/en/latest/
- tonic
- A native gRPC client & server implementation with async/await support.
- https://github.com/hyperium/tonic
Channel
- Multi channel 을 사용해야 할까?
- 일단 가이드를 보면 한 주소로는 1개의 Connection(채널)만을 사용하도록 권고하고 있다. 종종 Throughput을 늘리기 위해서 connection pool 을 이용해서, 여러 채널을 동시에 여는 내용도 있기는 하지만 매우 드물다.
Channel Argument
For example, if you want to disable TCP port reuse, you may construct channel arguments like:
버퍼링 및 플러시 관련 옵션들
# 전역적으로 flush 동작 제어
# Control flush behavior globally
channel_options = [
# Disable write coalescing (forces immediate send)
# 쓰기 병합 비활성화 (즉시 전송 강제)
('grpc.http2.write_buffer_size', 0),
# Set BDP (Bandwidth Delay Product) probing off
('grpc.http2.bdp_probe', 0),
# Reduce initial window size
# 초기 윈도우 크기 감소
('grpc.http2.initial_window_size', 65535),
# Disable buffering
# 버퍼링 비활성화
('grpc.optimization_target', 'latency'), # vs 'throughput'
]
channel = grpc.aio.insecure_channel(
'localhost:50051',
options=channel_options
)
기본 데이터 크기 제한
gRPC의 기본 최대 메시지 크기는 4MB입니다.
다음 옵션으로 변경 가능:
- grpc.max_send_message_length - 최대 전송 크기
- grpc.max_receive_message_length - 최대 수신 크기
연결 지속성
서버가 종료된 상태에서 클라이언트를 실행했다.
참고로 클라이언트 코드는 다음과 같다:
conn, err := grpc.NewClient(
pc.uploadAddress,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.Config{
BaseDelay: pc.uploadConnectTimeout,
Multiplier: 1.0,
MaxDelay: pc.uploadRetryInterval,
},
MinConnectTimeout: pc.uploadConnectTimeout,
}),
)
// ...
이 상태에서 클라이언트에서 RPC를 호출하면 당연하게도 작동하지 않는다.
이 후 서버를 실행해 놓으면 (ConnectParams에 지정한 Delay 값에 의거하여) 잠시 후 RPC 를 호출하면 잘 연결된다.
이 후 서버를 다시 끊고, 다시 실행해도 잘 작동한다.
이것으로 연결 지속성은 라이브러리가 알아서 잘 처리함을 알 수 있다.
주소 이름 규칙 (gRPC Name Resolution)
gRPC는 DNS를 기본 이름 시스템으로 지원합니다. 다양한 배치에서 다수의 대체 이름 시스템이 사용됩니다. 우리는 이름 시스템의 범위와 이름에 대한 해당 구문을 지원하기에 충분히 일반적인 API를 지원합니다. 다양한 언어로 된 gRPC 클라이언트 라이브러리는 플러그인 메커니즘을 제공하므로 다른 이름 시스템에 대한 해석기를 연결할 수 있습니다.
gRPC 채널 구성에 사용되는 완전한 자체 포함 이름은 RFC 3986에 정의된 URI 구문을 사용합니다 .
URI 체계는 사용할 리졸버 플러그인을 나타냅니다. 체계 접두사가 지정되지 않았거나 체계를 알 수 없는 경우 기본적으로 dns 체계가 사용됩니다.
DNS
dns:[//authority/]host[;:port] -- DNS (기본값)
-
host: DNS를 통해 확인할 호스트입니다. -
port: 각 주소에 대해 반환할 포트입니다. 지정하지 않으면 443이 사용됩니다(그러나 일부 구현에서는 안전하지 않은 채널의 경우 기본적으로 80으로 설정됨). -
authority: 일부 구현에서만 지원되지만 사용할 DNS 서버를 나타냅니다. (C-core에서 기본 DNS 확인자는 이를 지원하지 않지만 c-ares 기반 확인자는 "IP:port" 형식으로 지정하는 것을 지원합니다.)
Unix domain socket
unix:path 또는, unix://absolute_path -- Unix 도메인 소켓 (Unix 시스템만 해당)
-
path원하는 소켓의 위치를 나타냅니다. - 첫 번째 형식에서 경로는 상대적이거나 절대적일 수 있습니다.
- 두 번째 형식에서 경로는 절대 경로여야 합니다 (즉, 실제로 세 개의 슬래시가 있습니다. 경로 앞에 두 개, 절대 경로를 시작하는 슬래시 하나).
예제:
Abstract Namespace Unix domain socket
unix-abstract:abstract_path -- 추상 네임스페이스의 Unix 도메인 소켓 (Unix 시스템만 해당)
-
abstract_path: 추상 네임스페이스의 이름을 나타냅니다. - 이름은 파일 시스템 경로 이름과 연결되어 있지 않습니다.
- 소켓에 권한이 적용되지 않습니다. 모든 프로세스/사용자가 소켓에 액세스할 수 있습니다.
- 추상 소켓의 기본 구현은 널 바이트('\0')를 첫 번째 문자로 사용하지만, 구현체에서 이 null을 앞에 추가합니다. 즉,
abstract_path에 null을 포함하지 마십시오. -
abstract_path: null 바이트를 포함할 수 없습니다.- TODO( https://github.com/grpc/grpc/issues/24638 ): Unix는 추상 소켓 이름에 null 바이트를 포함할 수 있지만 gRPC C-core 구현에서는 지원하지 않습니다.
IP 버전 특정
다음 체계는 gRPC C-core 구현에서 지원되지만 다른 언어에서는 지원되지 않을 수 있습니다.
IPv4
ipv4:address[:port][,address[:port],...] -- IPv4 주소
- 다음 형식의 쉼표로 구분된 여러 주소를 지정할 수 있습니다
address[:port].-
address사용할 IPv4 주소입니다. -
port사용하는 포트입니다. 지정하지 않으면 443이 사용됩니다.
-
IPv6
ipv6:address[:port][,address[:port],...] -- IPv6 주소
- 다음 형식의 쉼표로 구분된 여러 주소를 지정할 수 있습니다
address[:port].-
address사용할 IPv6 주소입니다. To use with a port the address must enclosed in literal square brackets ([and]). Example:ipv6:[2607:f8b0:400e:c00::ef]:443oripv6:[::]:1234 -
port사용하는 포트입니다. 지정하지 않으면 443이 사용됩니다.
-
etcd
향후에는 다음과 같은 추가 구성표 etcd가 추가될 수 있습니다. 즉, 아직 없다.
passthrough:///bufnet
- bufconn
gRPC에서 사용되는 커스텀 리졸버 스킴(resolver scheme)으로, 주로 테스트 환경에서 in-memory gRPC 연결을 구성할 때 사용된다.
구성 요소
-
passthrough:// - gRPC의 내장 name resolver 중 하나로, 주어진 주소를 DNS 조회 없이 그대로(pass-through) 사용한다.
-
bufnet -
buf.build에서 제공하는 in-memory 네트워크 리스너의 이름이다. (google.golang.org/grpc/test/bufconn패키지에서 유래)
용도
실제 TCP 포트를 열지 않고 메모리 내에서 gRPC 서버/클라이언트 통신을 테스트할 때 사용한다.
// In-memory listener 생성 (메모리 내 리스너 생성)
lis := bufconn.Listen(1024 * 1024)
// gRPC 서버를 bufconn 리스너에서 실행
go func() {
srv := grpc.NewServer()
srv.Serve(lis)
}()
// 클라이언트에서 passthrough 스킴으로 연결
conn, err := grpc.Dial(
"passthrough:///bufnet",
grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
return lis.DialContext(ctx) // 실제 네트워크 대신 bufconn 사용
}),
grpc.WithInsecure(),
)
왜 passthrough인가?
일반적으로 grpc.Dial("localhost:50051")처럼 호출하면 gRPC는 내부적으로 DNS resolver를 사용한다. 하지만 bufnet은 실제 호스트명이 아니므로 DNS 조회가 실패한다. passthrough:/// 스킴을 사용하면 DNS 조회를 건너뛰고, 커스텀 ContextDialer가 직접 연결을 처리하게 된다.
핵심 정리
| 항목 | 설명 |
| 목적 | 포트 없이 in-memory gRPC 테스트 |
| 장점 | 빠르고, 포트 충돌 없음, CI/CD 친화적 |
| 패키지 | |
| 주 사용처 | 단위 테스트, 통합 테스트 |
서비스 정의
많은 RPC 시스템과 마찬가지로 gRPC는 서비스 정의 개념을 기반으로하며 매개 변수 및 반환 유형을 사용하여 원격으로 호출 할 수있는 메서드를 지정합니다. 기본적으로 gRPC는 프로토콜 버퍼를 사용 합니다.서비스 인터페이스와 페이로드 메시지의 구조를 모두 설명하기위한 인터페이스 정의 언어 (IDL)로 사용됩니다. 원하는 경우 다른 대안을 사용할 수 있습니다.
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
gRPC를 사용하면 4 가지 종류의 서비스 방법을 정의 할 수 있습니다.
- 클라이언트가 서버에 단일 요청을 보내고 일반 함수 호출처럼 단일 응답을받는 단항 RPC입니다.
- 클라이언트가 서버에 요청을 보내고 메시지 시퀀스를 다시 읽기위한 스트림을 가져 오는 서버 스트리밍 RPC입니다. 클라이언트는 더 이상 메시지가 없을 때까지 반환 된 스트림에서 읽습니다. gRPC는 개별 RPC 호출 내에서 메시지 순서를 보장합니다.
- 클라이언트가 일련의 메시지를 작성하고 제공된 스트림을 사용하여 서버로 보내는 클라이언트 스트리밍 RPC입니다. 클라이언트가 메시지 쓰기를 마치면 서버가 메시지를 읽고 응답을 반환 할 때까지 기다립니다. 다시 gRPC는 개별 RPC 호출 내에서 메시지 순서를 보장합니다.
- 양방향 스트리밍 RPC는 양쪽에서 읽기-쓰기 스트림을 사용하여 일련의 메시지를 전송합니다. 두 스트림은 독립적으로 작동하므로 클라이언트와 서버는 원하는 순서대로 읽고 쓸 수 있습니다. 예를 들어 서버는 응답을 작성하기 전에 모든 클라이언트 메시지를 수신하기를 기다릴 수도 있고 메시지를 읽은 다음 메시지를 쓸 수도 있습니다. 또는 읽기와 쓰기의 다른 조합. 각 스트림의 메시지 순서는 유지됩니다.
서버에 여러 protobuf 서비스 등록
gRPC:Python#서버에 여러 protobuf 서비스 등록 항목 참조.
인터셉터 (Interceptor)
gRPC는 clientConn/server 단위로 인터셉터를 구현하고 설치할 수 있는 간단한 API를 제공합니다.
인터셉터는 각 RPC 호출을 중간에서 가로채는 역할을 합니다.
사용자는 인터셉터를 사용하여 로깅, 인증/권한부여, 메트릭 수집 등 RPC 전체를 아우르는 공용 기능을 수행할 수 있습니다.
Debugging
- Stackoverflow - python grpc deadline exceeded errors in large percentages
- gRPC environment variables
You could try running under environment variables:
-
GRPC_VERBOSITY=DEBUG -
GRPC_TRACE=all
Healthcheck
- gPRC + Healthcheck 뽀개기 | eagle705's Note
- Github - grpc/doc/health-checking.md
- Github - grpc/src/python/grpcio_health_checking/ - gRPC Python Health Checking
protocolization.sh
recc에서 사용한 protocolization.sh 스크립트:
#!/usr/bin/env bash
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" || exit; pwd)
PROTOC_VERSION=$("$ROOT_DIR/python" -m grpc_tools.protoc --version | sed "s/libprotoc //g")
function print_error
{
# shellcheck disable=SC2145
echo -e "\033[31m$@\033[0m" 1>&2
}
function print_message
{
# shellcheck disable=SC2145
echo -e "\033[32m$@\033[0m"
}
trap 'cancel_black' INT
function cancel_black
{
print_error "An interrupt signal was detected."
exit 1
}
function run_proto
{
local module=$1
local name=$2
local args=(
"-Irecc/proto/${module}"
"--python_out=recc/proto/${module}"
"--mypy_out=recc/proto/${module}"
"--grpc_python_out=recc/proto/${module}"
"recc/proto/${module}/${name}.proto"
)
print_message "grpc_tools.protoc ${args[*]}"
"$ROOT_DIR/python" -m grpc_tools.protoc "${args[@]}"
# Do not use this flag: `--mypy_grpc_out=recc/proto`
# I need to generate an asynchronous function, but generating a normal function.
local pattern_regex="^import ${name}_pb2 as "
local replace_regex="import recc\\.proto\\.${module}\\.${name}_pb2 as "
local grpc_python_out_path="recc/proto/${module}/${name}_pb2_grpc.py"
sed -i.tmp \
-e "s/${pattern_regex}/${replace_regex}/" \
"${grpc_python_out_path}"
rm "${grpc_python_out_path}.tmp"
}
if [[ -z $PROTOC_VERSION ]]; then
"$ROOT_DIR/python" -m pip install grpcio-tools
fi
run_proto rpc rpc_api
TCP/IP vs Unix Domain Sockets vs Named PIPE
Unix Domain Socket#TCP/IP vs Unix Domain Sockets vs Named PIPE 항목 참조.
Troubleshooting
No module named 'google'
Python에서 google 모듈이 없다는 에러가 발생할 수 있다.
Warning, treated as error:
autodoc: failed to import module 'rpc_client' from module 'recc.core'; the following exception was raised:
No module named 'google'
이럴땐 protobuf 패키지를 설치하면 된다.
Stubs 또는 Channels 를 공유할 수 있나?
스트림 전송시 Flush 안되고 패킷이 묶여서 송출되는 이슈
버퍼 크기를 0으로 조절해봐라? <- 확인 필요.
RuntimeError: Failed to bind to address
recc 프로젝트 진행중 UDS 사용시, 다음과 같은 에러가 발생했다!
GRPC_VERBOSITY=debug환경변수 설정 후, 확인해 봤다:
D0629 10:22:55.548612300 1 ev_posix.cc:173] Using polling engine: epollex
D0629 10:22:55.548718900 1 lb_policy_registry.cc:42] registering LB policy factory for "grpclb"
D0629 10:22:55.548746500 1 lb_policy_registry.cc:42] registering LB policy factory for "priority_experimental"
D0629 10:22:55.548765400 1 lb_policy_registry.cc:42] registering LB policy factory for "weighted_target_experimental"
D0629 10:22:55.548783100 1 lb_policy_registry.cc:42] registering LB policy factory for "pick_first"
D0629 10:22:55.548798600 1 lb_policy_registry.cc:42] registering LB policy factory for "round_robin"
D0629 10:22:55.548817900 1 dns_resolver_ares.cc:499] Using ares dns resolver
D0629 10:22:55.548861800 1 certificate_provider_registry.cc:33] registering certificate provider factory for "file_watcher"
D0629 10:22:55.548884200 1 lb_policy_registry.cc:42] registering LB policy factory for "cds_experimental"
D0629 10:22:55.548900500 1 lb_policy_registry.cc:42] registering LB policy factory for "xds_cluster_impl_experimental"
D0629 10:22:55.548920100 1 lb_policy_registry.cc:42] registering LB policy factory for "xds_cluster_resolver_experimental"
D0629 10:22:55.548936100 1 lb_policy_registry.cc:42] registering LB policy factory for "xds_cluster_manager_experimental"
E0629 10:22:55.557492000 1 server_chttp2.cc:49] {"created":"@1624929775.557413200","description":"No address added out of total 1 resolved","file":"src/core/ext/transport/chttp2/server/chttp2_server.cc","file_line":872,"referenced_errors":[{"created":"@1624929775.557389300","description":"Unable to configure socket","fd":10,"file":"src/core/lib/iomgr/tcp_server_utils_posix_common.cc","file_line":216,"referenced_errors":[{"created":"@1624929775.557261000","description":"Invalid argument","errno":22,"file":"src/core/lib/iomgr/tcp_server_utils_posix_common.cc","file_line":190,"os_error":"Invalid argument","syscall":"bind"}]}]}
E 2021-06-29 10:22:55,558 recc.rpc: Failed to bind to address unix:///.recc/socket/recc.test_task.sock; set GRPC_VERBOSITY=debug environment variable to see detailed error message.
Traceback (most recent call last):
File "/.recc-package/recc/rpc/task_server.py", line 171, in run_task_until_complete
asyncio.run(run_task_server(config))
File "/usr/local/lib/python3.8/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "/.recc-package/recc/rpc/task_server.py", line 142, in run_task_server
server_info = create_task_server(config)
File "/.recc-package/recc/rpc/task_server.py", line 65, in create_task_server
accepted_port_number = server.add_insecure_port(rpc_address)
File "/usr/local/lib/python3.8/site-packages/grpc/aio/_server.py", line 83, in add_insecure_port
return _common.validate_port_binding_result(
File "/usr/local/lib/python3.8/site-packages/grpc/_common.py", line 166, in validate_port_binding_result
raise RuntimeError(_ERROR_MESSAGE_PORT_BINDING_FAILED % address)
RuntimeError: Failed to bind to address unix:///.recc/socket/recc.test_task.sock; set GRPC_VERBOSITY=debug environment variable to see detailed error message.
D 2021-06-29 10:22:55,602 grpc._cython.cygrpc: __dealloc__ called on running server <grpc._cython.cygrpc.AioServer object at 0x7fd3e5cfa5e0> with status 1
소켓을 생성한 디렉토리는 Docker Volume으로 연결된 상태였고, Host 와 Guest 가 서로 다른 OS 일 경우(예를 들면 Host가 macOS, Guest가 Linux), Unix Domain Socket 을 생성할 수 없다.
dial tcp 192.168.1.100:33332: connect: no route to host
Feb 23 14:23:09 geosoft-ALN35 edge[673]: {"level":"error","ts":1771824189.5644286,"caller":"proxy/client.go:869","msg":"Failed to subscribe to upstream notifications","error":"rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp 192.168.1.100:33332: connect: no route to host\"","stacktrace":"dataops-service/lib/proxy.(*ProxyClient).subscribeToUpstream\n\t/home/zer0/Project/faivv-dataops/lib/proxy/client.go:869\ndataops-service/lib/proxy.(*ProxyClient).processUpstreamNotifications\n\t/home/zer0/Project/faivv-dataops/lib/proxy/client.go:835\nsync.(*WaitGroup).Go.func1\n\t/home/zer0/.opm/var/go/root/go1.25.6.linux-amd64/go/src/sync/waitgroup.go:239"}
gRPC 관련 에러 부분만 추출하면:
error:
rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 192.168.1.100:33332: connect: no route to host"
stacktrace:
dataops-service/lib/proxy.(*ProxyClient).subscribeToUpstream
/home/zer0/Project/faivv-dataops/lib/proxy/client.go:869
dataops-service/lib/proxy.(*ProxyClient).processUpstreamNotifications
/home/zer0/Project/faivv-dataops/lib/proxy/client.go:835
sync.(*WaitGroup).Go.func1
/home/zer0/.opm/var/go/root/go1.25.6.linux-amd64/go/src/sync/waitgroup.go:239
gRPC 연결 오류네요. 핵심은:
dial tcp 192.168.1.100:33332: connect: no route to host
edge 서비스가 upstream (192.168.1.100:33332) 으로 gRPC subscribe 연결을 시도하는데, 네트워크 경로 자체가 없다는 의미입니다.
확인할 사항:
- 대상 호스트 상태 — 192.168.1.100 서버가 살아있는지 (
ping 192.168.1.100) - 포트 리스닝 — 대상에서 33332 포트가 열려있는지 (
ss -tlnp | grep 33332) - 방화벽/iptables — 대상 또는 중간 경로에서 차단하고 있는지 (
iptables -L -n) - 네트워크 인터페이스 — geosoft-ALN35 에서 192.168.1.100 대역으로의 라우팅이 정상인지 (
ip route get 192.168.1.100)
no route to host는 TCP handshake조차 안 되는 상황이라 애플리케이션 레벨보다는 네트워크/방화벽 쪽 문제일 가능성이 높습니다.
See also
- FlatBuffers
- RPC
- connectrpc
- Connect-Web - 브라우저에서 RPC 호출하는 TypeScript 라이브러리
Favorite site
- GRPC web site
- Github - Awesome gRPC
- This tutorial provides a basic C++ programmer's introduction to working with gRPC.
- 구글의 HTTP 기반의 RPC 프로토콜 GRPC
- 프로덕션 환경에서 사용하는 golang과 gRPC
- Akka gRPC Quickstart with Scala (gRPC, Scala, Akka)
- Microservices with gRPC. | by 디지털 세상을 만드는 아날로거 | Medium
- gRPC 서비스와 HTTP API 비교 | Microsoft Docs
Guide
References
-
Running_an_HTTP_GRPC_Python_Server_Concurrently_on_AWS_Elastic_Beanstalk.pdf ↩