大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 ,若此文对你有帮助,一定记得点个关注⭐与小红星❤️或加入到作者知识星球『 全栈工程师修炼指南』,转发收藏学习不迷路 。
Nginx 反向代理 gRPC 浅析与实践
描述: 上文《Nginx | HTTP 反向代理:WebSocket 实时双向数据传输》作者带领各位看友一起学习实践了使用 Python 3 实现一个简易的 WebSocket 服务,并通过 Nginx 进行反向代理,相信各位看友已经对 WebSocket 有了初步的了解。接下来,我们将转入另一个话题:Nginx 如何反向代理高性能的 gRPC 服务。
本章将介绍了如何在 Linux 服务器使用 Python 3.11 搭建一个简易 gRPC 服务/客户端,然后使用 Nginx 作为反向代理服务器进行转发,最后使用 Python 代码进行测试被代理的 gRPC 服务是否正常。
同样,在实践之前,我们先简要介绍一下 gRPC 的相关知识。
温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_7wc0af7viwqr.html
gRPC 介绍
gRPC 是一种现代的开源高性能远程过程调用(RPC)框架,可运行于任何环境。它能高效连接数据中心内部及跨数据中心的服务,并支持可插拔的负载均衡、追踪、健康检查和认证功能。此外,该框架同样适用于分布式计算的最后阶段,用于连接设备、移动应用和浏览器至后端服务,由 Google 主要开发者开发并维护,是 CNCF 孵化出的项目。
使用场景,例如在需要对接口进行严格约束的情况,比如我们提供了一个公共的服务,很多人,甚至公司外部的人也可以访问这个服务,这时对于接口我们希望有更加严格的约束,我们不希望客户端给我们传递任意的数据,尤其是考虑到安全性的因素,我们通常需要对接口进行更加严格的约束,此时gRPC就可以通过protobuf来提供严格的接口约束。
另外,对于性能有更高的要求时,在需要传递大量的数据,而又希望不影响我们的性能,这此时也可以考虑gRPC服务,因为通过protobuf我们可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,并且通过 http2 我们可以实现异步的请求,从而大大提高了通信效率。但是,通常我们不会去单独使用gRPC,而是将gRPC作为一个部件进行使用,这是因为在生产环境,我们面对大并发的情况下,需要使用分布式系统来去处理,而gRPC并没有提供分布式系统相关的一些必要组件,生产环境下还需要提供包括负载均衡,限流熔断,监控报警,服务注册和发现等等必要的组件。
官方网站:https://grpc.io/
知识扩展:RPC是远程过程调⽤的简称,是分布式系统中不同节点间流⾏的通信⽅式。在互联⽹时代,RPC已经和 IPC⼀样成为⼀个不可或缺的基础构件。
gPRC 有何优点?
高性能、开源的通用 RPC 框架,支持多种编程语言实现,包括 Go、C++、Java、Python 等SDK。
基于 Protocol Buffers(protobuf)消息格式,实现功能强大且高效二进制协议,目前版本为 v3,性能高于 JSON、XML 等文本格式。
基于 HTTP/2 标准,支持双向流(客户端<>服务端)、头部压缩、多路复用等特性,底层使用 TCP 或 Unix Socket 协议,性能高于 HTTP/1.x。
weiyigeek.top-gRPC框架图图
由下图可知,使用 C++ 编写的 gRPC 服务端,应用程序通过 gPRC 插件生产的 stub 代码与 gRPC 服务端进行通信,同理客户端也可直接与服务器通讯,前提是需要知道其接口与传递的参数类型。
weiyigeek.top-gPRC服务端与客户端图
前面提到 grpc 性能高,一方面在于使用 Protobuf 二进制格式,它是一种IDL(interface description language)语言,用于在不同服务之间序列化数据,推送的是二进制数据流,其格式与JSON格式如下图所示:
weiyigeek.top-Protobuf格式与JSON格式对比图
由上图杜比可知,Protobuf 比 JSON 更紧凑(无需分隔符、空字段省略、Tag 中是用字段的数字值然后转换成二进制进行表示的)、更高效(tag 的里面存储了字段的类型,Length 记录了字段值的长度,可根据如此获取 value 值),从而大幅减少需要传输的数据量以及提高性能。
好了,回到今天的正题,若要在 Nginx 中实现 gRPC 反向代理,我们需要使用到 ngx_http_grpc_module 模块,该模块支持 gRPC 服务,以及其依赖的 ngx_http_v2_module 模块,后者提供了对 HTTP/2 的支持,好在 Nginx 中已经在编译时默认启用了这两个模块,当然多需要取消 grpc 模块,可通过 --without-http_grpc_module 参数禁用。
指令参数
由于 gPRC 使用了 http 框架,因此其指令的使用方法与反向代理 http 类似,前面几章 [https://wx.zsxq.com/tags/Nginx/48811882815218] 我们已经详细的讲解过 http 反向代理相关指令,所以此处不再累述了,只是 gPRC 反向代理指令前缀有所不同而已,考虑到各看友查找不方便作者给出了其功能指令对应表,如下所示,更多指令可查阅官方文档(https://nginx.org/en/docs/http/ngx_http_grpc_module.html)。
weiyigeek.top-gPRC与http反向代理对应表图
实践演示
步骤 01.首先,使用 Python3 实现一个完整的gRPC服务示例,包含服务端、客户端的示例,如下所示:
项目结构
grpc-demo/
├── proto/ # Protobuf定义目录
│ └── demo.proto
├── py_generated/ # 生成的Python代码(自动)
│ ├── demo_pb2.py
│ └── demo_pb2_grpc.py
├── server.py # 同步服务端
├── client.py # 同步客户端
└── requirements.txt # 依赖文件
- 定义服务接口 (proto/demo.proto)
tee proto/demo.proto <<'EOF'
syntax = "proto3";
package demo;
// 用户信息服务
service UserService {
// 简单RPC
rpc GetUser (UserRequest) returns (User) {}
// 双向流式RPC
rpc Chat (stream ChatMessage) returns (stream ChatMessage) {}
}
// 消息类型定义
message UserRequest {
string user_id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
int32 age = 4;
repeatedstring tags = 5; // 标签列表
}
message ChatMessage {
string user_id = 1;
string message = 2;
int64 timestamp = 3;
}
EOF
- 然后安装依赖,并使用 grpcio-tools 生成代码,如下所示:
# 依赖安装
tee requirements.txt <<EOF
grpcio==1.62.0
grpcio-tools==1.62.0
protobuf==4.25.3
EOF
# 安装依赖
pip install -r requirements.txt
# 生成Python代码
mkdir ./py_generated
python3 -m grpc_tools.protoc
-I./proto
--python_out=./py_generated
--grpc_python_out=./py_generated
./proto/demo.proto
- 同步服务端实现 (server.py)
tee server.py <<'EOF'
import grpc
from concurrent import futures
import time
from typing import Iterator
from py_generated import demo_pb2, demo_pb2_grpc
class UserServicer(demo_pb2_grpc.UserServiceServicer):
"""gRPC服务实现"""
def __init__(self):
# 模拟数据库
self.users = {
"1": demo_pb2.User(id="1", name="张三", email="zhangsan@example.com", age=25, tags=["python", "golang"]),
"2": demo_pb2.User(id="2", name="李四", email="lisi@example.com", age=30, tags=["java", "rust"]),
"3": demo_pb2.User(id="3", name="王五", email="wangwu@example.com", age=28, tags=["cpp", "python"]),
}
def GetUser(self, request: demo_pb2.UserRequest, context) -> demo_pb2.User:
"""简单RPC - 获取单个用户"""
print(f"[GetUser] 请求用户ID: {request.user_id}")
if request.user_id in self.users:
return self.users[request.user_id]
# 设置gRPC错误码
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details(f"用户 {request.user_id} 不存在")
return demo_pb2.User()
def Chat(self, request_iterator: Iterator[demo_pb2.ChatMessage], context) -> Iterator[demo_pb2.ChatMessage]:
"""双向流式RPC - 聊天功能"""
print("[Chat] 聊天会话开始")
for message in request_iterator:
print(f" 收到来自 {message.user_id} 的消息: {message.message}")
# 模拟AI回复
response = demo_pb2.ChatMessage(
user_id="server-bot",
message=f"收到你的消息: '{message.message}'",
timestamp=int(time.time() * 1000)
)
yield response
def serve():
"""启动gRPC服务器"""
server = grpc.server(
futures.ThreadPoolExecutor(max_workers=10)
)
# 添加服务
demo_pb2_grpc.add_UserServiceServicer_to_server(
UserServicer(), server
)
# 启动服务器
host = "0.0.0.0"
port = 50051
server.add_insecure_port(f'{host}:{port}')
server.start()
print(f"✅ gRPC服务器启动在端口 {port}")
print(" 服务已就绪...")
try:
server.wait_for_termination()
except KeyboardInterrupt:
print("n 服务器关闭")
server.stop(0)
if __name__ == '__main__':
serve()
EOF
# 导出环境变量
export PYTHONPATH=/tmp/gRPC/py_generated:$PYTHONPATH
# 运行服务端
python3 server.py
- 同步客户端实现 (client.py)
tee client.py <<'EOF'
import grpc
import time
from py_generated import demo_pb2, demo_pb2_grpc
def run():
"""测试所有gRPC方法"""
# 创建通道
channel = grpc.insecure_channel('localhost:50051')
stub = demo_pb2_grpc.UserServiceStub(channel)
print("=" * 50)
print("开始测试gRPC客户端")
print("=" * 50)
# 1. 测试简单RPC
print("n1. 测试简单RPC (GetUser)")
try:
response = stub.GetUser(demo_pb2.UserRequest(user_id="1"))
print(f" 获取用户成功: {response.name} (年龄: {response.age})")
print(f" 标签: {', '.join(response.tags)}")
except grpc.RpcError as e:
print(f" 错误: {e.code()} - {e.details()}")
# 2. 测试双向流式RPC
print("n2. 测试双向流式RPC (Chat)")
def generate_messages():
messages = [
"你好,服务器!",
"今天天气怎么样?",
"gRPC好用吗?",
"再见!"
]
for i, msg in enumerate(messages, 1):
yield demo_pb2.ChatMessage(
user_id=f"client-user-{i}",
message=msg,
timestamp=int(time.time() * 1000)
)
time.sleep(1)
chat_stream = stub.Chat(generate_messages())
# 同时发送和接收
try:
for response in chat_stream:
print(f" 服务器回复: {response.message}")
except KeyboardInterrupt:
print(" 聊天中断")
if __name__ == '__main__':
run()
EOF
# 导出环境变量
export PYTHONPATH=/tmp/gRPC/py_generated:$PYTHONPATH
# 运行客户端
python3 client.py
weiyigeek.top-gRPC服务测试图
步骤 02.在完成上述步骤后,已经通过运行server.py来启动服务器,并通过client.py测试各种功能,此时成功搭建了一个简单的gRPC服务端和客户端,下面将使用 Nginx 反向代理 gRPC 服务,配置如下:
tee /usr/local/nginx/conf.d/server.conf <<'EOF'
# 后端 grpc 服务地址
upstream grpc_backend {
server 10.20.172.214:50051; # 上游 grpc 服务地址
keepalive 10; # 设置保持10个空闲的保活连接。
keepalive_timeout 60s; # 设置与上游服务器的长连接空闲连接的超时时间。
}
server {
# 监听 4433 端口,注意不需要添加 SSL 指令
listen 4433;
# 虚拟主机服务器名称
server_name server.weiyigeek.top;
default_type text/html;
# grpc 需启用 HTTP/2 支持
http2 on;
# 日志文件
access_log /var/log/nginx/server.log main;
error_log /var/log/nginx/server.err.log debug;
# SSL 证书文件
ssl_certificate /usr/local/nginx/certs/server.crt;
# ssl_certificate_key /usr/local/nginx/certs/server.key;
# 加密的 SSL 证书密钥文件(根据需求选择)
ssl_certificate_key /usr/local/nginx/certs/server_encrypted.key;
ssl_password_file /usr/local/nginx/certs/ssl_password.txt;
# 支持的 SSL/TLS 协议版本
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
# 支持的 SSL/TLS 加密套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE:ECDH:AES:HIGH:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!NULL:!aNULL:!eNULL:!EXPORT:!PSK:!ADH:!DH:!DES:!MD5:!RC4;
# SSL 会话缓存
ssl_session_cache shared:SSL:10m;
# SSL 会话超时时间
ssl_session_timeout 10m;
# 优先使用服务器端支持的加密套件
ssl_prefer_server_ciphers on;
# Websocket 反向代理配置
location / {
grpc_pass grpc_backend; # 转发到后端服务
grpc_set_header Host $host;
grpc_connect_timeout 60s; # 设置连接超时时间
grpc_read_timeout 60s; # 设置超时时间
grpc_set_header X-Real-IP $remote_addr; # 转发客户端 IP 地址
}
}
EOF
步骤 03.验证配置并重启 Nginx 服务,修改 client.py 代码将 grpc 请求地址 localhost:50051 改成 Nginx 反向代理中配置的地址 server.weiyigeek.top:4433,如下所示:
$ nginx -t && nginx -s reload
$ vim client.py
...
# 创建通道
grpc_server = "server.weiyigeek.top:4433"
print("正在连接gRPC服务端:",grpc_server)
channel = grpc.insecure_channel(f'{grpc_server}')
stub = demo_pb2_grpc.UserServiceStub(channel)
...
步骤 04.重新运行客户端,验证 gRPC 请求是否成功通过 Nginx 反向代理到后端服务,由图可知通过反向代理成功转发 gRPC 请求。
# 运行测试
$ python3 client.py
正在连接gRPC服务端: server.weiyigeek.top:4433
==================================================
开始测试gRPC客户端
==================================================
1. 测试简单RPC (GetUser)
获取用户成功: 李四 (年龄: 30)
标签: java, rust
2. 测试双向流式RPC (Chat)
服务器回复: 收到你的消息: '你好,服务器!'
服务器回复: 收到你的消息: '今天天气怎么样?'
服务器回复: 收到你的消息: 'gRPC好用吗?'
服务器回复: 收到你的消息: '再见!'
# 查看 Nginx 日志,可以看见是使用的 HTTP/2 协议进行通信。
$ tail -f /var/log/nginx/server.log -n 6
10.20.172.214 - - [06/Jan/2026:16:11:59 +0800] "POST /demo.UserService/GetUser HTTP/2.0" 200 48 "-""grpc-python/1.62.0 grpc-c/39.0.0 (linux; chttp2)""-"
10.20.172.214 - - [06/Jan/2026:16:12:03 +0800] "POST /demo.UserService/Chat HTTP/2.0" 200 262 "-""grpc-python/1.62.0 grpc-c/39.0.0 (linux; chttp2)""-"
weiyigeek.top-反向代理gRPC图
至此,我们已经成功搭建了一个基于 gRPC 的服务端和客户端,并通过 Nginx 实现了反向代理,想必大家都有所收获,希望这篇文章能够帮助到大家,也希望大家能多多转发、点赞支持,谢谢!
338