找回密码
 立即注册
查看: 369|回复: 0

grpc同时提供restful api

[复制链接]
发表于 2022-4-6 09:09 | 显示全部楼层 |阅读模式
概述

根据google cloud的API设计指南:
对比rest api和rpc: 2010 年,大约 74% 的公共网络 API 是 HTTP REST,虽然 HTTP/JSON API 在互联网上非常流行,但它们承载的流量比传统的 RPC API 要小,视频内容,数据中心内部 RPC API来承载大多数网络流量
在实际使用中,人们会出于不同目的选择 RPC API 和 HTTP/JSON API,理想情况下,API 平台应该为所有类型的 API 提供最佳支持

而藏宝阁的使用场景,服务端调用的接口如果统一使用rpc, 也可能存在需要提供rest api的可能,比如同时提供给游戏/大神/cc等调用,grpc不一定方便对接

Google API 和 Cloud Endpoints gRPC API 使用 HTTP 映射功能进行 JSON/HTTP 到 Protocol Buffers/RPC 的转码: gRPC Transcoding

gRPC Transcoding的映射定义demo, 留意option(google.api.http)配置:
// Returns a specific bookstore shelf.rpc GetShelf(GetShelfRequest) returns (Shelf) {  // Client example - returns the first shelf:  //   curl http://DOMAIN_NAME/v1/shelves/1  option (google.api.http) = { get: "/v1/shelves/{shelf}" };}...// Request message for GetShelf method.message GetShelfRequest {  // The ID of the shelf resource to retrieve.  int64 shelf = 1;}
也可以使用独立的yaml文件配置映射,不过推荐在proto文件中定义

支持gRPC Transcoding的系统包括:Google APIs, Cloud Endpoints, gRPC Gateway, and Envoy proxy

Google APIs我们可以无视, 下面对其他3种方式做个说明
Cloud Endpoints


Cloud Endpoints是google cloud提供的服务,部署结构如下图所示,

image.png

留意图中红框的部分是部署在google cloud的Endpoints服务,因为依赖google cloud提供的服务,我们也无法使用
gRPC Gateway


使用这个grpc服务来举例: https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/endpoints/bookstore-grpc

如下命令启动gprc server, 默认端口8000
python bookstore_server.py
gRPC Gateway 完全是go的实现,以go grpc为基础,需要生成go grpc的stub,在额外生成grpc gateway的stub
所以需要安装go的环境, 安装protobuf
之后 go install:
google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
google.golang.org/protobuf/cmd/protoc-gen-go@latest

在 bookstore-grpc 创建gw目录,生成go gpc/grpc gateway stub及编写go代码
mkdir gwcd gwgo mod init cbg/demorpc/gw
使用 http_bookstore.proto, 生成stub代码
留意需要需要修改下http_bookstore.proto, 加上option go_package = "cbg/demorpc/gw/pb";
执行如下命令
cp ../http_bookstore.proto ./# 留意修改加上option go_package = "cbg/demorpc/gw/pb";# 因为http_bookstore.proto依赖googleapi的proto filegit clone https://github.com/googleapis/googleapis.git# 生成stub代码# mkdir pbprotoc -I . -I ./googleapis/ --grpc-gateway_out ./pb \    --grpc-gateway_opt paths=source_relative \    --grpc-gateway_opt generate_unbound_methods=true \    --go_out ./pb --go_opt paths=source_relative \    --go-grpc_out ./pb --go-grpc_opt paths=source_relative \    http_bookstore.proto
创建main.go文件,内容如下:
package mainimport (  "context"  "flag"  "net/http"  "github.com/golang/glog"  "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"  "google.golang.org/grpc"  "google.golang.org/grpc/credentials/insecure"  pb "cbg/demorpc/gw/pb")var (  // command-line options:  // gRPC server endpoint  grpcServerEndpoint = flag.String("grpc-server-endpoint",  "localhost:8000", "gRPC server endpoint"))func run() error {  ctx := context.Background()  ctx, cancel := context.WithCancel(ctx)  defer cancel()  // Register gRPC server endpoint  // Note: Make sure the gRPC server is running properly and accessible  mux := runtime.NewServeMux()  opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}  err := pb.RegisterBookstoreHandlerFromEndpoint(ctx, mux,  *grpcServerEndpoint, opts)  if err != nil {    return err  }  // Start HTTP server (and proxy calls to gRPC server endpoint)  return http.ListenAndServe(":8081", mux)}func main() {  flag.Parse()  defer glog.Flush()  if err := run(); err != nil {    glog.Fatal(err)  }}
执行如下命令,启动gateway服务, 监听8081端口:
go mod tidygo run main.go
查看效果:
$ curl 127.0.0.1:8081/v1/shelves{"shelves":[{"id":"1", "theme":"Fiction"}, {"id":"2", "theme":"Fantasy"}]}Envoy


安装envoy: https://www.envoyproxy.io/docs/envoy/latest/start/install#tools-images

生成descriptor file
mkdir envoycd envoycp ../http_bookstore.proto ./# 因为http_bookstore.proto依赖googleapi的proto filegit clone https://github.com/googleapis/googleapis.git# 生成 descriptor fileprotoc -o api_descriptor.pb -I ./googleapis/ -I ./  --include_imports  http_bookstore.proto
创建conig.yaml, 添加如下内容:
static_resources:  listeners:  - name: listener1    address:      socket_address: {address: 0.0.0.0, port_value: 8080}    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:              # NOTE: by default, matching happens based on the gRPC route, and not on the incoming request path.              # Reference: https://envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_json_transcoder_filter#route-configs-for-transcoded-requests              - match: {prefix: "/endpoints.examples.bookstore.Bookstore"}                route: {cluster: grpc, timeout: 60s}          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: "/home/gpx/demorpc/envoy/api_descriptor.pb"              services: ["endpoints.examples.bookstore.Bookstore"]              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    type: STATIC    lb_policy: ROUND_ROBIN    connect_timeout: 2s    dns_lookup_family: V4_ONLY    typed_extension_protocol_options:      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions        explicit_http_config:          http2_protocol_options: {}    load_assignment:      cluster_name: grpc      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: 127.0.0.1                port_value: 8000
启动envoy
envoy -c config.yaml
验证结果:
$ curl 127.0.0.1:8080/v1/shelves{ "shelves": [  {   "id": "1",   "theme": "Fiction"  },  {   "id": "2",   "theme": "Fantasy"  } ]}istio


因为istio使用的sidecar也是envoy,所以也可以配置istio的sidecar做这样的转码

istio并为提供直接设置grpc转码的配置,但istio开了直接patch sidecar的envoy配置的扣子: EnvoyFilter,可以实现上面Envoy的配置

同样使用bookstore的例子
部署如下的deploy + service:
apiVersion: apps/v1kind: Deploymentmetadata:  name: bookstore  labels:    app: bookstorespec:  replicas: 1  selector:    matchLabels:      app: bookstore  template:    metadata:      labels:        app: bookstore    spec:      containers:      - name: bookstore        image: dockerhub.nie.netease.com/xiebaogong/bookstore:v1        args:        - "--port"        - "9090"        ports:        - containerPort: 9090---apiVersion: v1kind: Servicemetadata:  name: bookstore  labels:    app: bookstore    service: bookstorespec:  ports:  - port: 9091    targetPort: 9090    name: grpc    appProtocol: grpc  selector:    app: bookstore
进入bookstore容器,验证grpc服务部署成功
# python bookstore_client.py  --host=bookstore --port=9091ListShelves: shelves {  id: 1  theme: "Fiction"}shelves {  id: 2  theme: "Fantasy"}
配置EnvoyFilter
apiVersion: networking.istio.io/v1alpha3kind: EnvoyFiltermetadata:  name: bookstore-grpc-jsonspec:  workloadSelector:    labels:      app: bookstore  configPatches:  - applyTo: HTTP_FILTER    match:      context: SIDECAR_INBOUND      listener:        portNumber: 9090        filterChain:          filter:            name: "envoy.filters.network.http_connection_manager"            subFilter:              name: "envoy.filters.http.router"    patch:      operation: INSERT_BEFORE      value:        name: envoy.filters.http.grpc_json_transcoder        typed_config:          "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder          proto_descriptor_bin: Ctd4ChVnb29nbGUvYXBpL2h0dH....          print_options:            add_whitespace: true            always_print_primitive_fields: true            always_print_enums_as_ints: false            preserve_proto_field_names: false
留意上面的 proto_descriptor_bin配置, 取值内容是上面Envoy demo中的api_descriptor.pb转成base64,copy上去
使用 kubectl apply -f filter.yaml 部署上述配置之后,可在k8s的任意容器中验证结果:
$ curl bookstore:9091/v1/shelves{ "shelves": [  {   "id": "1",   "theme": "Fiction"  },  {   "id": "2",   "theme": "Fantasy"  } ]
因为EnvoyFilter直接暴露了Envoy的配置,在不通版本下不太一样,上面的测试是在istio 1.11上完成的,在istio 1.4下配置不通,如下:
apiVersion: networking.istio.io/v1alpha3kind: EnvoyFiltermetadata:  name: bookstore-grpc-json  namespace: cbg-xie1spec:  workloadSelector:    labels:      app: bookstore  configPatches:  - applyTo: HTTP_FILTER    match:      context: SIDECAR_INBOUND      listener:        portNumber: 9390        filterChain:          filter:            name: "envoy.http_connection_manager"            subFilter:              name: "envoy.router"    patch:      operation: INSERT_BEFORE      value:        name: envoy.grpc_json_transcoder        config:          auto_mapping: true          proto_descriptor_bin: Ctd4ChVnb29nbGUvYXB...          services: ["endpoints.examples.bookstore.Bookstore"]          print_options:            add_whitespace: true            always_print_primitive_fields: true            always_print_enums_as_ints: false            preserve_proto_field_names: false
改配置部署成功,并通过istioctl proxy-config listener 查看,配置确实写入了, 但是访问并不生效,怀疑是bug
总结


gRPC Gateway, Envoy, istio配置这3种方式对比下来各有优劣

gRPC Gateway需要引入go,对于非go语言,引入go,流程有些复杂,不太友好
Envoy 再引入这么一个专门的proxy,比较重,也可以考虑和业务容器分开单独部署
istio配置,不同版本的兼容性不太好

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-5-8 03:44 , Processed in 0.158672 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表