项目中服务都是以GRPC的方式提供,但是为了和前台对接,需要以Restful的方式提供接口。于是研究了GPRC到Restful接口的转换方法。常用的转换方法有一下3种。
- grpc + envoy + grpc-web,grpc-web是一套js库,前台通过grpc-web和envoy实现和grpc服务的交互。该方法的缺点是前台仍然需要拿到proto文件,然后生成对应的js文件才可以调用服务。无法达到服务对调用者透明的效果。
- grpc + grpc-gateway,grpc-gateway是go实现的一个代理转发库,未实验。
- grpc + envoy + grpc-json transcoder,本文主要通过这种方式实现grpc服务的Restful接口化。
无论使用grpc-web还是grpc-json transcode都需要envoy的转发。下面先说明如何使用envoy。
1 envoy的使用
envoy是由C++开发的一个代理服务,功能非常强大。可以编译安装,也可以直接使用docker。本文实验直接通过docker的方式使用envoy。具体操作命令如下:
1.1 下载镜像
docker pull envoyproxy/envoy-dev
1.2 启动envoy
docker run --name envoy --rm -d -p 5858:5858 -p 9901:9901 \
-v /root/service/etc/envoy:/etc/envoy \
envoyproxy/envoy-dev
各个参数的说明:
- --name,启动的容器名称。
- 5858为监听器端口, 9901为管理端口,端口号和后面的envoy.yaml文件中的配置相对应。
- -v /root/service/etc/envoy:/etc/envoy,将envoy.yaml和proto.pb映射到envoy的默认配置路径下。
envoy涉及的概念较多,可以先参考基本概念,理解envoy配置文件中各个参数的含义。
2 创建grpc服务
2.1 编写grpc的proto文件
本实验中使用的proto文件wind.proto的内容如下,
syntax = "proto3";
import "google/api/annotations.proto";
package wind_power;
service WindServer {
rpc wind_predict(Request) returns (Response) {
option (google.api.http) = {
get: "/predict"
};
}
rpc send_data(Request) returns (Response) {
option (google.api.http) = {
post: "/send",
body: "*"
};
}
}
message Request {
string content = 1;
}
message Response {
string msg = 1;
int32 code = 2;
}
proto中添加了两个接口,分别用于测试get方法和post方法。
2.2 生成proto.pb描述文件以及wind.proto对应的pb文件
python -m grpc_tools.protoc -I../../googleapis-master -I. \
--include_imports --include_source_info --descriptor_set_out=proto.pb \
--python_out=.. --grpc_python_out=.. wind.proto
执行上述命令之后会生成3个文件,proto.pb/wind_pb2.py/wind_pb2_grpc.py。其中proto.pb为对应的协议描述文件,后面envoy.yaml中需要配置该文件所在的路径。
注意:需要先将googleapis这个googleapis项目下载都指定的路径下,并将上述命令中的第一个-I替换成googleapis所在的路径。
2.3 grpc服务端代码
服务文件名称wind_predict_srv.py
# -*- coding: utf-8 -*-
# --------------------------------
# Name: wind_predict_srv.py
# Author: george
# Date: 2020/7/25
# --------------------------------
import grpc
import logging
from concurrent import futures
import proto.wind_pb2 as wind_pb2
import proto.wind_pb2_grpc as wind_pb2_grpc
class WindPredictSrv(wind_pb2_grpc.WindServerServicer):
def wind_predict(self, request, context):
print("call wind_predict")
return wind_pb2.Response(msg='%s!' % request.content)
def send_data(self, request, context):
print("call send_data")
return wind_pb2.Response(msg='%s!' % request.content)
def server():
grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
wind_pb2_grpc.add_WindServerServicer_to_server(WindPredictSrv(), grpc_server)
grpc_server.add_insecure_port('[::]:50052')
grpc_server.start()
grpc_server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
server()
2.4 grpc客户端代码
客户端文件名称wind_predict_client.py
import grpc
import logging
import proto.wind_pb2 as wind_pb2
import proto.wind_pb2_grpc as wind_pb2_grpc
def run():
option = [('grpc.keepalive_timeout_ms', 10000)]
with grpc.insecure_channel(target='127.0.0.1:50052', options=option) as channel:
stub = wind_pb2_grpc.WindServerStub(channel)
request = wind_pb2.Request(content='hello grpc')
response = stub.wind_predict(request, timeout=10)
print("Greeter client received: " + response.msg)
if __name__ == '__main__':
logging.basicConfig()
run()
2.5 测试grpc服务是否正常
启动服务端
python wind_predict_srv.py
新启动一个窗口,使用grpc客户端测试grpc服务
python wind_predict_client.py
若打印Greeter client received: hello grpc!,说明grpc服务构建成功。
3 配置envoy
envoy的使用主要是通过envoy的配置文件实现。本实验中envoy的配置文件如下,配置文件名称envoy.yaml。
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
static_resources:
listeners:
- name: listener1
address:
socket_address: { address: 0.0.0.0, port_value: 5858 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: grpc_json
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/wind_power.WindServer" }
route: { cluster: grpc, timeout: { seconds: 60 } }
http_filters:
- name: envoy.filters.http.grpc_json_transcoder
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
proto_descriptor: "/etc/envoy/proto.pb"
services: ["wind_power.WindServer"]
print_options:
add_whitespace: true
always_print_primitive_fields: true
always_print_enums_as_ints: false
preserve_proto_field_names: false
- name: envoy.filters.http.router
clusters:
- name: grpc
connect_timeout: 1.25s
type: logical_dns
lb_policy: round_robin
dns_lookup_family: V4_ONLY
http2_protocol_options: {}
upstream_connection_options:
tcp_keepalive:
keepalive_time: 300
load_assignment:
cluster_name: grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 0.0.0.0
port_value: 50052
4 请求验证
grpc服务正常启动,envoy的yaml文件编辑好且envoy正常启动就可以测试请求了。
4.1 测试get请求
curl http://localhost:5858/predict?content=george
4.2 测试post方式请求
curl -H "Content-Type:application/json" -X POST -d '{"content": "hello grpc"}' \
http://localhost:5858/send
注意:这里名的predict和send方法是在proto文件中通过option的方式绑定的,如:
rpc wind_predict(Request) returns (Response) {
option (google.api.http) = {
get: "/predict"
};
}
FAQ
问题1:upstream connect error or disconnect/reset before headers. reset reason: connection failure
出现这种情况一般都是因为网络问题。实验中启动envoy容器时没有指定网络,默认使用的是bridge网络,而gprc服务是在宿主机上的,二者不在同一个网段,这种情况下访问就会出现上面的问题。解决方法:
- ① 启动容器时指定host网络,即添加 --network="host"。
- ② 将grpc服务和envoy放置在同一个网段的网络上。
参考
- Quick start: https://www.envoyproxy.io/docs/envoy/latest/start/start#
- 中文非官方文档:https://www.servicemesher.com/envoy/
- Envoy、gRPC和速率限制例子: https://juejin.im/post/6844903715149709326
- 使用envoy proxy对grpc服务进行负载平衡例子: https://segmentfault.com/a/1190000019815416
- 服务网格代理Envoy入门: https://cloud.tencent.com/developer/article/1574575
- envoy grpc-bridage官方示例:https://github.com/envoyproxy/envoy/tree/master/examples/grpc-bridge
- 使用Envoy将gRPC转码为HTTP/JSON: https://zhuanlan.zhihu.com/p/51518920
- Envoy 入门教程: https://www.qikqiak.com/envoy-book/getting-started/
- Ditching REST with gRPC-web and Envoy https://medium.com/swlh/ditching-rest-with-grpc-web-and-envoy-bfaa89a39b32
- Go grpc example https://github.com/Bingjian-Zhu/go-grpc-example
- Grpc gateway https://github.com/grpc-ecosystem/grpc-gateway
- Envoy and gRPC-Web: a fresh new alternative to REST https://blog.envoyproxy.io/envoy-and-grpc-web-a-fresh-new-alternative-to-rest-6504ce7eb880
- Our experience designing and building gRPC services https://www.bugsnag.com/blog/using-grpc-in-production
- gRPC-JSON transcoder https://www.bookstack.cn/read/envoyproxy-1.6.0/6d0680e95312aafc.md
- Building a Service Mesh with Envoy
- Envoy 基于文件的动态配置
- Envoy 基于 API 的动态配置
- Envoy 的健康检查
- 基于文件的xDS配置,V3官方教程
anvoy + grpc-json 参考
- 下载需要的proto文件,google apis: https://github.com/googleapis/googleapis
- gRPC-JSON transcoder 官方示例
- https://cloud.google.com/endpoints/docs/grpc/transcoding?hl=zh-cn
- 经典请求测试的例子:https://medium.com/google-cloud/grpc-transcoding-e101cc53d51d
- 哔哩哔哩视频教程 https://www.bilibili.com/video/BV1vf4y1X7Kd?t=913
- 使用Envoy将gRPC转码为HTTP/JSON
- Envoy 的架构与基本配置解析
网友评论