grpc使用实践

1 grpc概述

grpc 是一个高性能、开源的rpc框架,目前提供了多种语言版本,基于HTTP/2标准设计,拥有双向流、流控、头部压缩、单TCP连接上的多复用请求特性,接口描述语言使用protobuf。

在grpc中,一共有四种调用方式:

  • 一元RPC(unary RPC): 称为单次RPC,也就是一问一答RPC请求,是最基础最常用的调用方式。
  • 服务端流式RPC(Server-side Streaming RPC): 是一个单向流,客户端发起一次普通RPC请求,服务端通过流式返回数据集。
  • 客户端流式RPC(Client-side Streaming RPC): 是一个单向流,客户端通过流式发送数据集,服务端回复一次普通RPC请求。
  • 双向流式RPC(Bidirectional Streaming RPC): 由客户端以流式发起请求,服务端同样以流式方式响应请求。一定有客户端发起,但交互方式(谁先谁后、一次发多少、相应多少、什么时候关闭)则由程序编写的方式来控制(可以结合协程)。

Unary和Stream相比,因为省掉了中间每次建立连接的花费,所以效率上会提升一些。


grpc调用流程:

  • 客户端发起调用,即在程序中调用某个方法;
  • 对请求信息使用protobuf进行对象序列化后发给服务端;
  • 服务端接收请求后,解码请求信息,进行业务逻辑处理;
  • 对处理结果使用protobuf进行对象序列化压缩后返回给客户端;
  • 客户端接收到服务端响应后,解码结果。


grpc优点:

  • 性能好,比json编解码数读快几十倍。
  • 代码生成方便,使用proto工具自动生成对应语言代码。
  • 支持多种流传输方式,支持一元RPC、服务端流式RPC、客户端流式RPC、双流向RPC共4中传输流。
  • 有超时和取消处理机制,客户端和服务端在截止时间后对取消事件进行相关处理。


grpc缺点:

  • 可读性差
  • 不支持浏览器调用
  • 外部组件支持性差


使用场景

  • unary(一元RPC)

    • CRUD的api调用
  • service-side streaming(服务端流方式)

    • 股票app:客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端
    • app的在线push:client先发请求到server注册,然后server就可以发在线push了
  • client-side rpc streaming(客户端流方式)

    • 物联网终端向服务器报送数据
  • bi-side rpc streaming(双向流方式)

    • 聊天机器人
    • 有状态的游戏服务器进行数据交换。比如LOL,王者荣耀等竞技游戏,client和server之间需要非常频繁地交换数据


2 grpc插件和使用命令

2.1 安装插件

把下载的可执行文件全部存放到$GOPATH/bin目录下。并且把proto依赖的包include存放到目录$GOPATH/bin/include下。

# 各个插件版本
# protoc                    v3.20.1      命令
# protoc-gen-go             v1.28.0      protoc插件,根据proto文件生成*pb.go文件,是填充、序列化和检索消息类型代码。
# protoc-gen-gogofaster     v1.28.0      protoc插件,替换了protoc-gen-go插件,以提高编码和解码速度,还支持自定义标签。
# protoc-gen-go-grpc        v1.2.0       protoc插件,根据proto文件生成*_grpc.pb.go文件,是客户端和服务端的方法和接口代码。
# protoc-gen-grpc-gateway   v2.10.0      protoc插件,根据proto文件生成*pb.gw.go文件,是web的api代码。
# protoc-gen-openapiv2      v2.10.0      protoc插件,根据proto文件生成*swagger.json文件,是swagger-ui接口文档。
# protoc-gen-validate       v0.6.7       protoc插件,根据proto文件生成*pb.validate.go文件,是校验字段代码

# 下载protoc
wget https://github.com/protocolbuffers/protobuf/releases/tag/v3.20.1

# 安装protoc-gen-go、protoc-gen-go-grpc、protoc-gen-validate插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
go install github.com/envoyproxy/protoc-gen-validate@v0.6.7

# 安装protoc-gen-grpc-gateway、protoc-gen-openapiv2插件,下载地址
wget https://github.com/grpc-ecosystem/grpc-gateway/releases/download/v2.10.0/protoc-gen-grpc-gateway-v2.10.0-windows-x86_64.exe
wget https://github.com/grpc-ecosystem/grpc-gateway/releases/download/v2.10.0/protoc-gen-openapiv2-v2.10.0-windows-x86_64.exe


2.2 protoc命令使用

outPath="${serviceName}pb"  # 和proto文件的go_package名称一致,也就是文件夹名和包名一致  
mkdir -p ${outPath}  
  
# 生成pb.go和grpc.pb.go文件,
# pb.go文件是用于填充、序列化和检索消息类型的代码。
# _grpc.pb.go文件的客户端和服务器代码。
# 为了兼容旧版本protoc-gen-go生成代码,需要添加参数--go-grpc_opt=require_unimplemented_servers=false  
protoc --go_out=${outPath} --go_opt=paths=source_relative --go-grpc_out=${outPath} --go-grpc_opt=paths=source_relative *.proto  

# 生成pb.go和grpc.pb.go文件,使用protoc-gen-gogofaster插件,支持添加自定义tag,并且序列化和反序列化都比protoc-gen-go更快
protoc --gogofaster_out=${outPath} --gogofaster_opt=paths=source_relative --go-grpc_out=${outPath} --go-grpc_opt=paths=source_relative *.proto

# 生成*.pb.gw.go文件,web的api接口文件  
protoc --grpc-gateway_opt=paths=source_relative --grpc-gateway_out=${outPath} *.proto  

# 生成*.swagger.json文件  
protoc --openapiv2_opt=logtostderr=true --openapiv2_out=${outPath} *.proto

# 生成*.validate.go文件
protoc --validate_opt=paths=source_relative --validate_out=lang=go:${outPath} *.proto


旧版本protoc-gen-go生成代码命令:

protoc –go_out=plugins=grpc:. *.proto



3 protobuf简介

protobuf是一种与语言无关、平台无关、可扩展的可序列化和结构化的数据描述语言(其IDL),常用于通信协议、数据存储等,比json、XML更小,编码解码速度快得多。

语法模板:

最简单protobuf模板:

syntax = "proto3";

package helloworld;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}


包括grpc-gateway和swagger文档模板

syntax = "proto3";  
  
package proto;  
  
// 把google/api/annotations.proto和protoc-gen-openapiv2/options/annotations.proto文件存放在protoc的同级目录include下  
// protoc默认从同级目录include下查找  
import "google/api/annotations.proto";  
import "protoc-gen-openapiv2/options/annotations.proto";  
  
// 设置生成*go的包名  
option go_package = "./accountpb";  
  
  
// 生成*.swagger.json文件的一些默认设置  
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {  
  info: {  
    version: "2.0";  
  };  
  // 显示扩展文档  
  external_docs: {  
    url: "https://baidu.com";  
    description: "描述信息";  
  }  
  // 默认为HTTPS,根据实际需要设置  
  schemes: HTTP;  
};  
  
  
service Account {  
  rpc AddUser (User) returns (ID) {  
    // http设置  
    option (google.api.http) = {  
      post: "/v1/addUser"  
      body: "*"  
    };  
    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {  
      summary: "添加用户",  
      description: "add one User",  
      tags: "addUser",  
    };  
  }  
  
  rpc GetUser (ID) returns (User) {  
    // http设置  
    option (google.api.http) = {  
      get: "/v1/getUser"  
    };  
    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {  
      summary: "获取用户",  
      description: "get one user",  
      tags: "getUser",  
    };  
  }  
}  
  
message ID {  
  int64 id = 1;  
}  
  
message User {  
  int64 id = 1;  
  string name = 2;  
  string email = 3;  
}

注:service、rpc、message名称都是大写,而message里的字段是小写或下划线


protobuf与go语言常见数据类型映射表:

proto go
bool bool
string string
bytes []byte
int32 int32
int64 int64
uint32 uint32
uint64 uint64
float float32
double float64
sint32, sfixed32 int32
sint64, sfixed64 int64
fixed32 unit32
fixed64 unit64


复合类型映射表:

(1) 数组类型

message HelloRequest {
    repeated string name = 1;  // 等价go的[]string
}


(2) 嵌套类型

message User {
    string name = 1;
}

message HelloRequest {
    repeated User users = 1;  // 等价go的[]User
}


(3) map

message HelloRequest {
    map<string, string> names = 2;  // 等价go的map[string]striing
}


4 grpc使用示例

4.1 一些调试grpc工具

(1) bloomrpc

bloomRPC旨在为探索和查询 GRPC 服务提供最简单、最高效的开发人员体验,通过界面调试。

github: https://github.com/bloomrpc/bloomrpc


启动rpc服务端,导入proto文件,填写ip和端口,点击中间绿色按钮调用,如下图所示:

bloomRPC


(2) evans

Evans 是通过命令行容易调试 gRPC 客户端工具,命令中自带自动提示功能,使用非常方便


(3) grpcurl

grpcurl是一个命令行工具,可让您与gRPC服务器进行交互,基本上是curl针对gRPC服务器的。


4.2 protobuf序列化和反序列化

点击查看protobuf示例代码


4.3 四种调用方式

点击查看helloworld示例代码


4.4 日志打印

点击查看日志示例代码


4.5 元数据操作

在HTTP/1.1中,通常通过Header来传递数据,对于grpc(HTTP/2)来说,很少直接用Header传递数据,一般使用metadata来传递和操作数据,metadata是一个map结构(map[string][]string),共有两种创建方式:

  • 直接使用函数 metadata.New(map[string]string{})
  • 直接调用函数 metadata.Pairs(key,value),默认会把key转为小写,如果key相同,会追加到对应key的[]string上

在grpc中,为了防止metadata从入站rpc直接转发到出站rpc情况,因此metadata分为传入和传出两种:

  • metadata.NewIncomingContext: 创建一个附加了传入metadata的新上下文,仅供自身的grpc使用。
  • metadata.NewOutgoingContext: 创建一个附加了传出metadata的新上下文,仅供外部的grpc使用。

在grpc中,metadata是存储在context的,context中的数据是在请求的Header中的,因此通过Header可以看到metadata数据。

设置自定义metadata信息示例:

key = "authorization"

// 创建 metadata 和 context
md := metadata.Pairs(key, "Bearer eyJhb...ssw5c")
ctx := metadata.NewOutgoingContext(context.Background(), md)


读出自定义metadata信息示例:

key = "authorization"

// 使用metadata包读取key
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
    return status.Errorf(codes.DataLoss, "failed to get metadata")
}
if authorization, ok := md[key]; ok {
    fmt.Printf("metadata: %s=%v\n", key, authorization)
} else {
    fmt.Printf("not found '%s' in metadata\n", key)
}


// 或使用封装好的包metautils读取key
authorization := metautils.ExtractIncoming(ctx).Get(key)
fmt.Println(color1)


点击查看元数据示例代码


4.6 拦截器

grpc拦截器(Interceptor)可以在每一个RPC方法的前面或后面做统一的特殊处理,并且不直接侵入业务代码, 例如鉴权校验、超时控制、日志记录、链路跟踪等。拦截器的类型分为两种:

  • 一元拦截器(UnaryInterceptor):拦截和处理一元RPC调用。
  • 流拦截器(StreamInterceptor):拦截和处理流式RPC调用。

由于客户端和服务端有各自的一元拦截器和流拦截器, 因此,在gRPC中, 也可以说共有四种类型的拦截器。

  • 服务端一元拦截器(StreamServerInterceptor)
  • 服务端流拦截器(StreamServerInterceptor)
  • 客户端一元拦截器(UnaryClientInterceptor)
  • 客户端流拦截器(StreamClientInterceptor)

因为grpc拦截器类型不能重复,当需要多个拦截器时,借助go-grpc-middleware库来实现,安装库

go get -u github.com/grpc-ecosystem/go-grpc-middleware

go-grpc-middleware 拦截器分类:

Auth

  • grpc_auth 一个可定制的(通过AuthFunc)的认证中间件

Logging

  • grpc_ctxtags - 一个为上下文添加标签图的库,数据由请求主体填充
  • grpc_zap 将zap日志库整合到gRPC处理程序中。
  • grpc_logrus 将logrus日志库整合到gRPC处理程序中。
  • grpc_kit 将go-kit日志库整合到gRPC处理程序中。

Monitoring

grpc_prometheus ⚡ - ⚡ - OpenTracing grpc_opentracing -

Client

  • grpc_retry 一个通用的gRPC响应代码重试机制

Server grpc_validator - grpc_recovery - ratelimit - - grpc_validator 来自.proto选项的codegen入站消息验证 - grpc_recovery 将恐慌转化为gRPC错误 - ratelimit 由你自己的限制器限制grpc速率


点击查看拦截器示例代码


4.7 keepalive

点击查看keepalive示例代码


4.8 超时

点击查看超时示例代码


4.9 recovery

点击查看recovery示例代码


4.10 生成swagger接口文档

(1) 安装插件 protoc-gen-openapiv2

# windows环境
wget https://github.com/grpc-ecosystem/grpc-gateway/releases/download/v2.10.0/protoc-gen-openapiv2-v2.10.0-windows-x86_64.exe

# 下载完成后改名为protoc-gen-openapiv2.exe,并移动到$GOPATH/bin/目录下


(2) 下载swagger UI文件

# 下载swagger UI文件,然后解压
wget https://github.com/swagger-api/swagger-ui/archive/v3.37.0.zip

# 在$GOPATH/bin/include/目录下新建swagger目录,在$GOPATH/bin目录下有protoc文件
mkdir -p $GOPATH/bin/include/swagger

# 把swagger-ui里的dist目录下所有文件移动到$GOPATH/bin/include/swagger/


(3) 安装go-bindata和go-bindata-assetfs库

go-bindata工具主要为了将swagger-ui静态文件转为go代码,go-bindata-assetfs库是为了使外能够部访问swagger UI。

go get -u github.com/go-bindata/go-bindata/...
go get -u github.com/elazarl/go-bindata-assetfs/...


(4) 将swagger静态资源转为go代码

# 创建目录swagger-ui和swagger,其中swagger-ui存放dist目录下所有静态文件,swagger存放把静态文件转换后的go文件
mkdir -p pkg/swagger-ui pkg/swagger

# 转换为go
go-bindata --nocompress -pkg=swagger -o=pkg/swagger/data.go pkg/swagger-ui/...

把转换后的data.go文件复制到当前swagger目录下。


(5) 测试swagger UI服务

点击查看完整的swagger-ui示例代码

启动服务,在浏览器访问 http://127.0.0.1:8080/swagger-ui/ ,把 http://127.0.0.1:8080/swagger/hello.swagger.json 复制到swagger界面执行,就可以执行接口测试了。

注:hello.swagger.json中的schemes字段值为空,在swagger测试时默认使用https,导致本来http接口无法访问,所以需要在生成的*.swagger.json文档中手动加入schemes字段,这样可以选择https或http来测试接口。

  "schemes":[
    "https",
    "http"
  ],


4.11 validate

点击查看validate示例代码


4.12 tag

点击查看tag示例代码


4.13 TLS

点击查看TLS示例代码


4.14 JWT

点击查看JWT示例代码


4.15 restful api 调用 grpc

gRPC-Gateway 是 Google 协议缓冲区编译器 protoc 的 插件。它读取 protobuf 服务定义并生成一个反向代理服务器,该服务器将 RESTful HTTP API 转换为 gRPC。该服务器是根据 google.api.http 您的服务定义中的注释生成的。

grpc-gateway框架图


点击查看grpc-gateway示例代码


4.16 重试

点击查看重试示例代码


4.17 限流

点击查看限流示例代码


4.18 注册与发现

在分布式系统中,为了实现高可用,通常同一个服务会部署多个,为了使访问流量要均衡分散到多个服务上。

客户端要访问服务端,需要知道服务端ip地址和端口,如果服务数量比较少,并且服务不会频繁更改ip和端口,人还可以处理,如果服务数量多了,通过人工力量处理就非常麻烦了,需要动态获取服务端地址,也就是服务注册与发现,常见角色:

  • 注册中心:承担对服务信息进行注册、协调、管理等工作。
  • 服务提供者(服务端): 暴露特定端口,并提供一个到多个的服务来允许外部访问。
  • 服务消费者(客户端): 调用服务方。

服务注册与发现原理:”服务提供者”在启动服务时会将自己的服务信息(ip地址、端口号、版本号等)注册到”注册中心”。”服务消费者”在进行调用时,会以约定命名标识(如服务名)到”注册中心”查询,发现当前哪些具体的服务可以调用。”注册中心”再根据约定的负载均衡算法进行调度,最终请求到服务提供者。

另外,当”服务提供者”出现问题时,或是当定期的”心跳检测”发现”服务提供者”无正确响应时,那么这个出现问题的服务就会被下线,并标识为不可用。即在启动时上报”注册中心”进行注册,把被检测到出问题的服务下线,以此来维护服务注册和发现。

点击查看etcd服务注册与发现示例代码


4.19 负载均衡

常见的负载均衡有客户端负载均衡和服务端负载均衡。

(1) 客户端负载均衡

客户端负载是指在调用时,由客户端到”注册中心”对服务提供者进行查询,并获取所需的服务清单。服务清单中包含各个服务的实际信息(如ip地址、端口号、集群命名空间等)。由客户端使用特定的负载均衡策略(如轮询)在服务清单中选择一个或多个服务进行调用。

  • 优点: 高性能、去中心化,并且不需要借助独立的外部负载均衡组件。
  • 缺点: 实现成本较高, 要对不同语言的客户端实现各自对应的SDK及其负载均衡策略。


(2) 服务端负载均衡

服务端负载,又被称为”代理”模式,在服务端侧搭设独立的负载均衡器,负载均衡器再根据给定的目标名称(如服务名)找到适合调用的服务实例,因此它具备负载均衡和反向代理两项功能。

  • 优点: 简单、透明,客户端不需要知道背后的逻辑,只需按给定的目标名称调用、访问即可,由服务端侧管理负载、均衡策略及代理
  • 缺点: 外部的负载均衡器理论上可能成为性能瓶颈,会受到负载均衡器的吞吐率影响,并且与客户端负载相比,有可能出现更高的网络延迟。同时,必须要保持高可用,因为它是整个系统的 关键节点,一旦出现问题,影响非常大。


(3) grpc官方设计思路

  • 客户端根据服务名称发起请求。
  • 名称解析器解析服务名称并返回,服务名称解析成一个或多个ip地址,每个ip都会有标识,标识分为服务端地址、负载均衡地址、客户端使用的负载均衡策略。
  • 客户端根据服务端类型选择相应的策略,如果grpc客户端获取的地址是负载均衡器地址,那么客户端将使用grpclb策略,否则使用服务配置请求的负载均衡策略;如果服务配置未请求负载均衡策略,则客户端默认选择第一个可用的服务端地址。
  • 最后根据不同的策略进行实际调用。

grpc默认支持两种负载均衡算法pick_first 和 round_robin。

点击查看负载均衡示例代码

点击查看结合etcd做负载均衡示例代码


4.20 链路跟踪

在微服务复制的分布式场景下,注入链路追踪是非常重要和必要的。做链路追踪的基本条件是注入追踪信息,而最简单的方法就是使用服务端和客户端拦截器组成完整的链路信息,具体如下:

  • 服务端拦截器:从metadata中提取链路信息, 将其设置并追加到服务端的调用上下文中。也就是说,如果发现本次调用并没有上一级的链路信息,那么它将会生成对应的父级信息,自己成为父级;如果发现本次调用存在既有的上一级链路信息,那么它将会根据上一级链路信息进行设置,成为其子级。
  • 客户端拦截器:从调用的上下文中提取链路信息, 并将其作为metadata追加到rpc调用中。

借助OpenTracing API和Jaeger Client两个go库实现与追踪系统对接。

点击查看gin调用rpc的链路跟踪示例代码

点击查看rpc调用rpc的链路跟踪示例代码


4.21 熔断

点击查看熔断示例代码


4.22 prometheus监控

固定标签

所有服务器端指标都以grpc_server名称开头。所有客户端指标都以grpc_client. 他们都有镜像概念,所有方法都包含相同的丰富标签:

  • grpc_servicegRPC 服务名称,它是 protobufpackagegrpc_service部分名称的组合。例如 package = mwitkow.testproto和 service TestService组合的标签是grpc_service="mwitkow.testproto.TestService"

  • grpc_method- 在 gRPC 服务上调用的方法的名称。例如 grpc_method="Ping"

  • grpc_type- gRPC类型的请求。区分两者非常重要,尤其是对于延迟测量。

    • unary是单请求单响应 RPC
    • client_stream是一个多请求、单响应的 RPC
    • server_stream是一个单请求、多响应的 RPC
    • bidi_stream是一个多请求、多响应的 RPC

此外,对于已完成的 RPC,使用以下标签:

  • grpc_code- 人类可读的gRPC 状态码。所有状态的列表都很长,但这里有一些常见的:

    • OK- 表示 RPC 成功
    • IllegalArgument- RPC 包含错误值
    • Internal- 未向客户端披露服务器端错误


点击查看默认指标监控示例代码

点击查看自定义指标监控示例代码


把client和server配置到prometheus之后,就可以在prometheus进行一些有用的查询了。

# 1分钟请求率 qps
sum(rate(grpc_server_started_total{job="hello_grpc_server"}[1m])) by (grpc_service)

# 一元请求错误率
sum(rate(grpc_server_handled_total{job="hello_grpc_server",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service)

# 一元请求错误百分比
sum(rate(grpc_server_handled_total{job="hello_grpc_server",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service) / sum(rate(grpc_server_started_total{job="hello_grpc_server",grpc_type="unary"}[1m])) by (grpc_service) * 100.0

# 平均响应流大小
sum(rate(grpc_server_msg_sent_total{job="hello_grpc_server",grpc_type="server_stream"}[10m])) by (grpc_service) / sum(rate(grpc_server_started_total{job="hello_grpc_server",grpc_type="server_stream"}[10m])) by (grpc_service)

# 一元请求的 99% 延迟
histogram_quantile(0.99, sum(rate(grpc_server_handling_seconds_bucket{job="hello_grpc_server",grpc_type="unary"}[5m])) by (grpc_service,le))

# 慢速一元查询的百分比 (>250ms)
100.0 - (sum(rate(grpc_server_handling_seconds_bucket{job="hello_grpc_server",grpc_type="unary",le="0.25"}[5m])) by (grpc_service) / sum(rate(grpc_server_handling_seconds_count{job="hello_grpc_server",grpc_type="unary"}[5m])) by (grpc_service)) * 100.0


4.23 grpc错误处理

grpc返回字段有Code和Message两部分,官方定义的状态码如下:

Code 状态码 说明
0 OK 成功
1 Canceled 该操作被调用方取消
2 Unknown 未知错误,如果不是grpc状态类型都统一归为未知错误,一般是用户自定义错误
3 InvalidArgument 无效参数
4 DeadlineExceeded 在操作完成之前超过了约定的最后期限
5 NotFound 找不到
6 AlreadyExists 已经存在
7 PermissionDenied 权限不足
8 ResourceExhausted 资源耗尽
9 FailedPrecondition 该操作被拒绝,因为未处于执行该操作所需的态
10 Aborted 该操作被中止
11 OutOfRange 超出范围,尝试执行的操作超出了约定的有
12 Unimplemented 未实现
13 Internal 内部错误
14 Unavailable 该服务当前不可用
15 DataLoss 不可恢复的数据丢失或损坏
16 Unauthenticated 身份验证元数据无效或凭据回调失败


在grpc的状态信息中一共包含三个属性,分别是错误码(code)、错误消息(message)、错误信息详情(Details),从any.proto文件引入detail字段,作为应用程序的错误码原型,重新封装grpc错误码和业务错误码。点击查看重新封装grpc错误码

外部客户端可以直接调用 errcode.ToGRPCERROR(errcode.ERROR_LOGIN_DAIL)返回错误信息,而内部客户端获取错误详情代码如下:

    err := errcode.ToGRPCERROR(errcode.ERROR_LOGIN_DAIL)
    details := errcode.FromError(err).Details()



5 注意事项

5.1 使用grpc-gateway注意事项

(1) 生成swagger.json,默认使用HTTPS,但是rpc和web并没有设置TLS传输,请求会出错。

使用swagger-ui测试接口,返回错误 “ TypeError: Failed to fetch”,原因是使用https调用接口,而服务端web并没有开启https,调用会出错。

解决办法:

设置swagger.json,把HTTPS改为HTTP
"schemes": [  
  "http"  
]

或者修改proto文件,然后重新生成swagger.json
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {  
  // 默认为HTTPS,根据实际需要设置  
  schemes: HTTP;
}


(2) grpc-gateway的web服务注册路由pb.Register***HandlerFromEndpoint(ctx, gwMux, grpcAddr, options)的option和grpc服务grpc.NewServer(options...)的option设置要一致,要么同时使用TLS传输,要么同时取消TLS传输,否则报错类似http: TLS handshake error from 127.0.0.1:14323: remote error: tls: unknown certificate

如果grpc使用了TLS传输,web服务建议也使用,web服务使用TLS监听服务,:http.ListenAndServeTLS(webAddr, certfile.Path("server.crt"), certfile.Path("server.key"), mux),此时swagger-ui都是使用https访问。


5.2 注意ctx混淆使用

(1) rpc的client端设置ctx,ctx的value通过header传递,server端接收到header,转为ctx。

// client端
md := metadata.Pairs(
  "uid","100",
  "authorization", token,
)  
ctx := metadata.NewOutgoingContext(context.Background(), md)


// 服务端读取可以使用grpc的metadata包读取,也可以用第三方封装方法读取github.com/grpc-ecosystem/go-grpc-middleware/util/metautils
metautils.ExtractIncoming(ctx).Get("uid")
metautils.ExtractIncoming(ctx).Get("authorization")


(2) ctx只在同一个服务内传递,新添加的kv和读取使用context的方法

// ctx设置value
newCtx := context.WithValue(ctx, "tokenInfo", cc) // 后面方法可以通过ctx.Value("tokenInfo").(*jwt.CustomClaims)

// ctx读取key
tokenInfo, ok := ctx.Value("tokenInfo").(*auth.Token) // 从拦截器设置值读取


5.3 注意etcd版本问题

etcd v3.5.0之后版本解决了grpc版本不兼容问题,etcd v3.5.0之后版本会优先使用代理地址代替etcd服务地址,使用时注意关闭代理。



专题「golang相关」的其它文章 »