mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 12:32:21 +00:00
规范 gRPC 写法
This commit is contained in:
parent
980d281283
commit
dbd8324a0b
@ -36,9 +36,9 @@
|
|||||||
* [4.1 RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
|
* [4.1 RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
|
||||||
* [4.2 Protobuf](ch4-rpc/ch4-02-pb-intro.md)
|
* [4.2 Protobuf](ch4-rpc/ch4-02-pb-intro.md)
|
||||||
* [4.3 玩转RPC](ch4-rpc/ch4-03-netrpc-hack.md)
|
* [4.3 玩转RPC](ch4-rpc/ch4-03-netrpc-hack.md)
|
||||||
* [4.4 GRPC入门](ch4-rpc/ch4-04-grpc.md)
|
* [4.4 gRPC入门](ch4-rpc/ch4-04-grpc.md)
|
||||||
* [4.5 GRPC进阶](ch4-rpc/ch4-05-grpc-hack.md)
|
* [4.5 gRPC进阶](ch4-rpc/ch4-05-grpc-hack.md)
|
||||||
* [4.6 GRPC和Protobuf扩展](ch4-rpc/ch4-06-grpc-ext.md)
|
* [4.6 gRPC和Protobuf扩展](ch4-rpc/ch4-06-grpc-ext.md)
|
||||||
* [4.7 pbgo: 基于Protobuf的框架](ch4-rpc/ch4-07-pbgo.md)
|
* [4.7 pbgo: 基于Protobuf的框架](ch4-rpc/ch4-07-pbgo.md)
|
||||||
* [4.8 grpcurl工具](ch4-rpc/ch4-08-grpcurl.md)
|
* [4.8 grpcurl工具](ch4-rpc/ch4-08-grpcurl.md)
|
||||||
* [4.9 补充说明](ch4-rpc/ch4-09-ext.md)
|
* [4.9 补充说明](ch4-rpc/ch4-09-ext.md)
|
||||||
|
@ -458,7 +458,7 @@ func main() {
|
|||||||
|
|
||||||
我们在自己的`TB`结构体类型中重新实现了`Fatal`方法,然后通过将对象隐式转换为`testing.TB`接口类型(因为内嵌了匿名的`testing.TB`对象,因此是满足`testing.TB`接口的),然后通过`testing.TB`接口来调用我们自己的`Fatal`方法。
|
我们在自己的`TB`结构体类型中重新实现了`Fatal`方法,然后通过将对象隐式转换为`testing.TB`接口类型(因为内嵌了匿名的`testing.TB`对象,因此是满足`testing.TB`接口的),然后通过`testing.TB`接口来调用我们自己的`Fatal`方法。
|
||||||
|
|
||||||
这种通过嵌入匿名接口或嵌入匿名指针对象来实现继承的做法其实是一种纯虚继承,我们继承的只是接口指定的规范,真正的实现在运行的时候才被注入。比如,我们可以模拟实现一个grpc的插件:
|
这种通过嵌入匿名接口或嵌入匿名指针对象来实现继承的做法其实是一种纯虚继承,我们继承的只是接口指定的规范,真正的实现在运行的时候才被注入。比如,我们可以模拟实现一个gRPC的插件:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type grpcPlugin struct {
|
type grpcPlugin struct {
|
||||||
|
@ -83,22 +83,22 @@ service HelloService {
|
|||||||
|
|
||||||
但是重新生成的Go代码并没有发生变化。这是因为世界上的RPC实现有千万种,protoc编译器并不知道该如何为HelloService服务生成代码。
|
但是重新生成的Go代码并没有发生变化。这是因为世界上的RPC实现有千万种,protoc编译器并不知道该如何为HelloService服务生成代码。
|
||||||
|
|
||||||
不过在protoc-gen-go内部已经集成了一个叫grpc的插件,可以针对grpc生成代码:
|
不过在protoc-gen-go内部已经集成了一个名字为`grpc`的插件,可以针对gRPC生成代码:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ protoc --go_out=plugins=grpc:. hello.proto
|
$ protoc --go_out=plugins=grpc:. hello.proto
|
||||||
```
|
```
|
||||||
|
|
||||||
在生成的代码中多了一些类似HelloServiceServer、HelloServiceClient的新类型。这些类型是为grpc服务的,并不符合我们的RPC要求。
|
在生成的代码中多了一些类似HelloServiceServer、HelloServiceClient的新类型。这些类型是为gRPC服务的,并不符合我们的RPC要求。
|
||||||
|
|
||||||
不过grpc插件为我们提供了改进的思路,下面我们将探索如何为我们的RPC生成安全的代码。
|
不过gRPC插件为我们提供了改进的思路,下面我们将探索如何为我们的RPC生成安全的代码。
|
||||||
|
|
||||||
|
|
||||||
## 4.2.2 定制代码生成插件
|
## 4.2.2 定制代码生成插件
|
||||||
|
|
||||||
Protobuf的protoc编译器是通过插件机制实现对不同语言的支持。比如protoc命令出现`--xxx_out`格式的参数,那么protoc将首先查询是否有内置的xxx插件,如果没有内置的xxx插件那么将继续查询当前系统中是否存在protoc-gen-xxx命名的可执行程序,最终通过查询到的插件生成代码。对于Go语言的protoc-gen-go插件来说,里面又实现了一层静态插件系统。比如protoc-gen-go内置了一个grpc插件,用户可以通过`--go_out=plugins=grpc`参数来生成grpc相关代码,否则只会针对message生成相关代码。
|
Protobuf的protoc编译器是通过插件机制实现对不同语言的支持。比如protoc命令出现`--xxx_out`格式的参数,那么protoc将首先查询是否有内置的xxx插件,如果没有内置的xxx插件那么将继续查询当前系统中是否存在protoc-gen-xxx命名的可执行程序,最终通过查询到的插件生成代码。对于Go语言的protoc-gen-go插件来说,里面又实现了一层静态插件系统。比如protoc-gen-go内置了一个gRPC插件,用户可以通过`--go_out=plugins=grpc`参数来生成gRPC相关代码,否则只会针对message生成相关代码。
|
||||||
|
|
||||||
参考grpc插件的代码,可以发现generator.RegisterPlugin函数可以用来注册插件。插件是一个generator.Plugin接口:
|
参考gRPC插件的代码,可以发现generator.RegisterPlugin函数可以用来注册插件。插件是一个generator.Plugin接口:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// A Plugin provides functionality to add to the output during
|
// A Plugin provides functionality to add to the output during
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
# 4.4 GRPC入门
|
# 4.4 gRPC入门
|
||||||
|
|
||||||
GRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。GRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。本节将讲述GRPC的简单用法。
|
gRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。gRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。本节将讲述gRPC的简单用法。
|
||||||
|
|
||||||
## 4.4.1 GRPC技术栈
|
## 4.4.1 gRPC技术栈
|
||||||
|
|
||||||
Go语言的GRPC技术栈如图4-1所示:
|
Go语言的gRPC技术栈如图4-1所示:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图4-1 gRPC技术栈*
|
*图4-1 gRPC技术栈*
|
||||||
|
|
||||||
最底层为TCP或Unix Socket协议,在此之上是HTTP/2协议的实现,然后在HTTP/2协议之上又构建了针对Go语言的GRPC核心库。应用程序通过GRPC插件生产的Stub代码和GRPC核心库通信,也可以直接和GRPC核心库通信。
|
最底层为TCP或Unix Socket协议,在此之上是HTTP/2协议的实现,然后在HTTP/2协议之上又构建了针对Go语言的gRPC核心库。应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信。
|
||||||
|
|
||||||
## 4.4.2 GRPC入门
|
## 4.4.2 gRPC入门
|
||||||
|
|
||||||
如果从Protobuf的角度看,GRPC只不过是一个针对service接口生成代码的生成器。我们在本章的第二节中手工实现了一个简单的Protobuf代码生成器插件,只不过当时生成的代码是适配标准库的RPC框架的。现在我们将学习GRPC的用法。
|
如果从Protobuf的角度看,gRPC只不过是一个针对service接口生成代码的生成器。我们在本章的第二节中手工实现了一个简单的Protobuf代码生成器插件,只不过当时生成的代码是适配标准库的RPC框架的。现在我们将学习gRPC的用法。
|
||||||
|
|
||||||
创建hello.proto文件,定义HelloService接口:
|
创建hello.proto文件,定义HelloService接口:
|
||||||
|
|
||||||
@ -32,13 +32,13 @@ service HelloService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
使用protoc-gen-go内置的grpc插件生成GRPC代码:
|
使用protoc-gen-go内置的gRPC插件生成gRPC代码:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ protoc --go_out=plugins=grpc:. hello.proto
|
$ protoc --go_out=plugins=grpc:. hello.proto
|
||||||
```
|
```
|
||||||
|
|
||||||
GRPC插件会为服务端和客户端生成不同的接口:
|
gRPC插件会为服务端和客户端生成不同的接口:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type HelloServiceServer interface {
|
type HelloServiceServer interface {
|
||||||
@ -50,7 +50,7 @@ type HelloServiceClient interface {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
GRPC通过context.Context参数,为每个方法调用提供了上下文支持。客户端在调用方法的时候,可以通过可选的grpc.CallOption类型的参数提供额外的上下文信息。
|
gRPC通过context.Context参数,为每个方法调用提供了上下文支持。客户端在调用方法的时候,可以通过可选的grpc.CallOption类型的参数提供额外的上下文信息。
|
||||||
|
|
||||||
基于服务端的HelloServiceServer接口可以重新实现HelloService服务:
|
基于服务端的HelloServiceServer接口可以重新实现HelloService服务:
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func (p *HelloServiceImpl) Hello(
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
GRPC服务的启动流程和标准库的RPC服务启动流程类似:
|
gRPC服务的启动流程和标准库的RPC服务启动流程类似:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -80,9 +80,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
首先是通过`grpc.NewServer()`构造一个GRPC服务对象,然后通过GRPC插件生成的RegisterHelloServiceServer函数注册我们实现的HelloServiceImpl服务。然后通过`grpcServer.Serve(lis)`在一个监听端口上提供GRPC服务。
|
首先是通过`grpc.NewServer()`构造一个gRPC服务对象,然后通过gRPC插件生成的RegisterHelloServiceServer函数注册我们实现的HelloServiceImpl服务。然后通过`grpcServer.Serve(lis)`在一个监听端口上提供gRPC服务。
|
||||||
|
|
||||||
然后就可以通过客户端链接GRPC服务了:
|
然后就可以通过客户端链接gRPC服务了:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -101,13 +101,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
其中grpc.Dial负责和GRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的GRPC服务提供的方法。
|
其中grpc.Dial负责和gRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的gRPC服务提供的方法。
|
||||||
|
|
||||||
GRPC和标准库的RPC框架有一个区别,GRPC生成的接口并不支持异步调用。不过我们可以在多个Goroutine之间安全地共享GRPC底层的HTTP/2链接,因此可以通过在另一个Goroutine阻塞调用的方式模拟异步调用。
|
gRPC和标准库的RPC框架有一个区别,gRPC生成的接口并不支持异步调用。不过我们可以在多个Goroutine之间安全地共享gRPC底层的HTTP/2链接,因此可以通过在另一个Goroutine阻塞调用的方式模拟异步调用。
|
||||||
|
|
||||||
## 4.4.3 GRPC流
|
## 4.4.3 gRPC流
|
||||||
|
|
||||||
RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间。因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合。同时传统RPC模式也不适用于对时间不确定的订阅和发布模式。为此,GRPC框架针对服务器端和客户端分别提供了流特性。
|
RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间。因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合。同时传统RPC模式也不适用于对时间不确定的订阅和发布模式。为此,gRPC框架针对服务器端和客户端分别提供了流特性。
|
||||||
|
|
||||||
服务端或客户端的单向流是双向流的特例,我们在HelloService增加一个支持双向流的Channel方法:
|
服务端或客户端的单向流是双向流的特例,我们在HelloService增加一个支持双向流的Channel方法:
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ for {
|
|||||||
|
|
||||||
## 4.4.4 发布和订阅模式
|
## 4.4.4 发布和订阅模式
|
||||||
|
|
||||||
在前一节中,我们基于Go内置的RPC库实现了一个简化版的Watch方法。基于Watch的思路虽然也可以构造发布和订阅系统,但是因为RPC缺乏流机制导致每次只能返回一个结果。在发布和订阅模式中,由调用者主动发起的发布行为类似一个普通函数调用,而被动的订阅者则类似GRPC客户端单向流中的接收者。现在我们可以尝试基于GRPC的流特性构造一个发布和订阅系统。
|
在前一节中,我们基于Go内置的RPC库实现了一个简化版的Watch方法。基于Watch的思路虽然也可以构造发布和订阅系统,但是因为RPC缺乏流机制导致每次只能返回一个结果。在发布和订阅模式中,由调用者主动发起的发布行为类似一个普通函数调用,而被动的订阅者则类似gRPC客户端单向流中的接收者。现在我们可以尝试基于gRPC的流特性构造一个发布和订阅系统。
|
||||||
|
|
||||||
发布订阅是一个常见的设计模式,开源社区中已经存在很多该模式的实现。其中docker项目中提供了一个pubsub的极简实现,下面是基于pubsub包实现的本地发布订阅代码:
|
发布订阅是一个常见的设计模式,开源社区中已经存在很多该模式的实现。其中docker项目中提供了一个pubsub的极简实现,下面是基于pubsub包实现的本地发布订阅代码:
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ service PubsubService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
其中Publish是普通的RPC方法,Subscribe则是一个单向的流服务。然后grpc插件会为服务端和客户端生成对应的接口:
|
其中Publish是普通的RPC方法,Subscribe则是一个单向的流服务。然后gRPC插件会为服务端和客户端生成对应的接口:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type PubsubServiceServer interface {
|
type PubsubServiceServer interface {
|
||||||
@ -397,5 +397,5 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
到此我们就基于GRPC简单实现了一个跨网络的发布和订阅服务。
|
到此我们就基于gRPC简单实现了一个跨网络的发布和订阅服务。
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# 4.5 GRPC进阶
|
# 4.5 gRPC进阶
|
||||||
|
|
||||||
作为一个基础的RPC框架,安全和扩展是经常遇到的问题。本节将简单介绍如何对GRPC进行安全认证。然后介绍通过GRPC的截取器特性,以及如何通过截取器优雅地实现Token认证、调用跟踪以及Panic捕获等特性。最后介绍了GRPC服务如何和其他Web服务共存。
|
作为一个基础的RPC框架,安全和扩展是经常遇到的问题。本节将简单介绍如何对gRPC进行安全认证。然后介绍通过gRPC的截取器特性,以及如何通过截取器优雅地实现Token认证、调用跟踪以及Panic捕获等特性。最后介绍了gRPC服务如何和其他Web服务共存。
|
||||||
|
|
||||||
## 4.5.1 证书认证
|
## 4.5.1 证书认证
|
||||||
|
|
||||||
GRPC建立在HTTP/2协议之上,对TLS提供了很好的支持。我们前面章节中GRPC的服务都没有提供证书支持,因此客户端在链接服务器中通过`grpc.WithInsecure()`选项跳过了对服务器证书的验证。没有启用证书的GRPC服务在和客户端进行的是明文通讯,信息面临被任何第三方监听的风险。为了保障GRPC通信不被第三方监听篡改或伪造,我们可以对服务器启动TLS加密特性。
|
gRPC建立在HTTP/2协议之上,对TLS提供了很好的支持。我们前面章节中gRPC的服务都没有提供证书支持,因此客户端在链接服务器中通过`grpc.WithInsecure()`选项跳过了对服务器证书的验证。没有启用证书的gRPC服务在和客户端进行的是明文通讯,信息面临被任何第三方监听的风险。为了保障gRPC通信不被第三方监听篡改或伪造,我们可以对服务器启动TLS加密特性。
|
||||||
|
|
||||||
可以用以下命令为服务器和客户端分别生成私钥和证书:
|
可以用以下命令为服务器和客户端分别生成私钥和证书:
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ $ openssl req -new -x509 -days 3650 \
|
|||||||
|
|
||||||
以上命令将生成server.key、server.crt、client.key和client.crt四个文件。其中以.key为后缀名的是私钥文件,需要妥善保管。以.crt为后缀名是证书文件,也可以简单理解为公钥文件,并不需要秘密保存。在subj参数中的`/CN=server.grpc.io`表示服务器的名字为`server.grpc.io`,在验证服务器的证书时需要用到该信息。
|
以上命令将生成server.key、server.crt、client.key和client.crt四个文件。其中以.key为后缀名的是私钥文件,需要妥善保管。以.crt为后缀名是证书文件,也可以简单理解为公钥文件,并不需要秘密保存。在subj参数中的`/CN=server.grpc.io`表示服务器的名字为`server.grpc.io`,在验证服务器的证书时需要用到该信息。
|
||||||
|
|
||||||
有了证书之后,我们就可以在启动GRPC服务时传入证书选项参数:
|
有了证书之后,我们就可以在启动gRPC服务时传入证书选项参数:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -172,13 +172,13 @@ func main() {
|
|||||||
|
|
||||||
服务器端同样改用credentials.NewTLS函数生成证书,通过ClientCAs选择CA根证书,并通过ClientAuth选项启用对客户端进行验证。
|
服务器端同样改用credentials.NewTLS函数生成证书,通过ClientCAs选择CA根证书,并通过ClientAuth选项启用对客户端进行验证。
|
||||||
|
|
||||||
到此我们就实现了一个服务器和客户端进行双向证书验证的通信可靠的GRPC系统。
|
到此我们就实现了一个服务器和客户端进行双向证书验证的通信可靠的gRPC系统。
|
||||||
|
|
||||||
## 4.5.2 Token认证
|
## 4.5.2 Token认证
|
||||||
|
|
||||||
前面讲述的基于证书的认证是针对每个GRPC链接的认证。GRPC还为每个GRPC方法调用提供了认证支持,这样就基于用户Token对不同的方法访问进行权限管理。
|
前面讲述的基于证书的认证是针对每个gRPC链接的认证。gRPC还为每个gRPC方法调用提供了认证支持,这样就基于用户Token对不同的方法访问进行权限管理。
|
||||||
|
|
||||||
要实现对每个GRPC方法进行认证,需要实现grpc.PerRPCCredentials接口:
|
要实现对每个gRPC方法进行认证,需要实现grpc.PerRPCCredentials接口:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type PerRPCCredentials interface {
|
type PerRPCCredentials interface {
|
||||||
@ -220,7 +220,7 @@ func (a *Authentication) RequireTransportSecurity() bool {
|
|||||||
|
|
||||||
在GetRequestMetadata方法中,我们返回地认证信息包装login和password两个信息。为了演示代码简单,RequireTransportSecurity方法表示不要求底层使用安全链接。
|
在GetRequestMetadata方法中,我们返回地认证信息包装login和password两个信息。为了演示代码简单,RequireTransportSecurity方法表示不要求底层使用安全链接。
|
||||||
|
|
||||||
然后在每次请求GRPC服务时就可以将Token信息作为参数选项传人:
|
然后在每次请求gRPC服务时就可以将Token信息作为参数选项传人:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -241,7 +241,7 @@ func main() {
|
|||||||
|
|
||||||
通过grpc.WithPerRPCCredentials函数将Authentication对象转为grpc.Dial参数。因为这里没有启用安全链接,需要传人grpc.WithInsecure()表示忽略证书认证。
|
通过grpc.WithPerRPCCredentials函数将Authentication对象转为grpc.Dial参数。因为这里没有启用安全链接,需要传人grpc.WithInsecure()表示忽略证书认证。
|
||||||
|
|
||||||
然后在GRPC服务端的每个方法中通过Authentication类型的Auth方法进行身份认证:
|
然后在gRPC服务端的每个方法中通过Authentication类型的Auth方法进行身份认证:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type grpcServer struct { auth *Authentication }
|
type grpcServer struct { auth *Authentication }
|
||||||
@ -280,7 +280,7 @@ func (a *Authentication) Auth(ctx context.Context) error {
|
|||||||
|
|
||||||
## 4.5.3 截取器
|
## 4.5.3 截取器
|
||||||
|
|
||||||
GRPC中的grpc.UnaryInterceptor和grpc.StreamInterceptor分别对普通方法和流方法提供了截取器的支持。我们这里简单介绍普通方法的截取器用法。
|
gRPC中的grpc.UnaryInterceptor和grpc.StreamInterceptor分别对普通方法和流方法提供了截取器的支持。我们这里简单介绍普通方法的截取器用法。
|
||||||
|
|
||||||
要实现普通方法的截取器,需要为grpc.UnaryInterceptor的参数实现一个函数:
|
要实现普通方法的截取器,需要为grpc.UnaryInterceptor的参数实现一个函数:
|
||||||
|
|
||||||
@ -294,19 +294,19 @@ func filter(ctx context.Context,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
函数的ctx和req参数就是每个普通的RPC方法的前两个参数。第三个info参数表示当前是对应的那个GRPC方法,第四个handler参数对应当前的GRPC方法函数。上面的函数中首先是日志输出info参数,然后调用handler对应的GRPC方法函数。
|
函数的ctx和req参数就是每个普通的RPC方法的前两个参数。第三个info参数表示当前是对应的那个gRPC方法,第四个handler参数对应当前的gRPC方法函数。上面的函数中首先是日志输出info参数,然后调用handler对应的gRPC方法函数。
|
||||||
|
|
||||||
要使用filter截取器函数,只需要在启动GRPC服务时作为参数输入即可:
|
要使用filter截取器函数,只需要在启动gRPC服务时作为参数输入即可:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
server := grpc.NewServer(grpc.UnaryInterceptor(filter))
|
server := grpc.NewServer(grpc.UnaryInterceptor(filter))
|
||||||
```
|
```
|
||||||
|
|
||||||
然后服务器在收到每个GRPC方法调用之前,会首先输出一行日志,然后再调用对方的方法。
|
然后服务器在收到每个gRPC方法调用之前,会首先输出一行日志,然后再调用对方的方法。
|
||||||
|
|
||||||
如果截取器函数返回了错误,那么该次GRPC方法调用将被视作失败处理。因此,我们可以在截取器中对输入的参数做一些简单的验证工作。同样,也可以对handler返回的结果做一些验证工作。截取器也非常适合前面对Token认证工作。
|
如果截取器函数返回了错误,那么该次gRPC方法调用将被视作失败处理。因此,我们可以在截取器中对输入的参数做一些简单的验证工作。同样,也可以对handler返回的结果做一些验证工作。截取器也非常适合前面对Token认证工作。
|
||||||
|
|
||||||
下面是截取器增加了对GRPC方法异常的捕获:
|
下面是截取器增加了对gRPC方法异常的捕获:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func filter(
|
func filter(
|
||||||
@ -326,7 +326,7 @@ func filter(
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
不过GRPC框架中只能为每个服务设置一个截取器,因此所有的截取工作只能在一个函数中完成。开源的grpc-ecosystem项目中的go-grpc-middleware包已经基于GRPC对截取器实现了链式截取器的支持。
|
不过gRPC框架中只能为每个服务设置一个截取器,因此所有的截取工作只能在一个函数中完成。开源的grpc-ecosystem项目中的go-grpc-middleware包已经基于gRPC对截取器实现了链式截取器的支持。
|
||||||
|
|
||||||
以下是go-grpc-middleware包中链式截取器的简单用法
|
以下是go-grpc-middleware包中链式截取器的简单用法
|
||||||
|
|
||||||
@ -347,7 +347,7 @@ myServer := grpc.NewServer(
|
|||||||
|
|
||||||
## 4.5.4 和Web服务共存
|
## 4.5.4 和Web服务共存
|
||||||
|
|
||||||
GRPC构建在HTTP/2协议之上,因此我们可以将GRPC服务和普通的Web服务架设在同一个端口之上。因为目前Go语言版本的GRPC实现还不够完善,只有启用了TLS协议之后才能将GRPC和Web服务运行在同一个端口。
|
gRPC构建在HTTP/2协议之上,因此我们可以将gRPC服务和普通的Web服务架设在同一个端口之上。因为目前Go语言版本的gRPC实现还不够完善,只有启用了TLS协议之后才能将gRPC和Web服务运行在同一个端口。
|
||||||
|
|
||||||
服务器证书的生成过程前文已经讲过,这里不再赘述。启用普通的https服务器非常简单:
|
服务器证书的生成过程前文已经讲过,这里不再赘述。启用普通的https服务器非常简单:
|
||||||
|
|
||||||
@ -367,7 +367,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
而单独启用带证书的GRPC服务也是同样的简单:
|
而单独启用带证书的gRPC服务也是同样的简单:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -382,9 +382,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
因为GRPC服务已经实现了ServeHTTP方法,可以直接作为Web路由处理对象。如果将GRPC和Web服务放在一起,会导致GRPC和Web路径的冲突,在处理时我们需要区分两类服务。
|
因为gRPC服务已经实现了ServeHTTP方法,可以直接作为Web路由处理对象。如果将gRPC和Web服务放在一起,会导致gRPC和Web路径的冲突,在处理时我们需要区分两类服务。
|
||||||
|
|
||||||
我们可以通过以下方式生成同时支持Web和GRPC协议的路由处理函数:
|
我们可以通过以下方式生成同时支持Web和gRPC协议的路由处理函数:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -397,7 +397,7 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
|
if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
|
||||||
grpcServer.ServeHTTP(w, r) // GRPC Server
|
grpcServer.ServeHTTP(w, r) // gRPC Server
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +408,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
首先GRPC是建立在HTTP/2版本之上,如果HTTP不是HTTP/2协议则必然无法提供GRPC支持。同时,每个GRPC调用请求的Content-Type类型会被标注为"application/grpc"类型。
|
首先gRPC是建立在HTTP/2版本之上,如果HTTP不是HTTP/2协议则必然无法提供gRPC支持。同时,每个gRPC调用请求的Content-Type类型会被标注为"application/grpc"类型。
|
||||||
|
|
||||||
这样我们就可以在GRPC端口上同时提供Web服务了。
|
这样我们就可以在gRPC端口上同时提供Web服务了。
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 4.6 GRPC和Protobuf扩展
|
# 4.6 gRPC和Protobuf扩展
|
||||||
|
|
||||||
目前开源社区已经围绕Protobuf和GRPC开发出众多扩展,形成了庞大的生态。本节我们将简单介绍验证器和REST接口扩展。
|
目前开源社区已经围绕Protobuf和gRPC开发出众多扩展,形成了庞大的生态。本节我们将简单介绍验证器和REST接口扩展。
|
||||||
|
|
||||||
## 4.6.1 验证器
|
## 4.6.1 验证器
|
||||||
|
|
||||||
@ -157,19 +157,19 @@ func (this *Message) Validate() error {
|
|||||||
|
|
||||||
生成的代码为Message结构体增加了一个Validate方法,用于验证该成员是否满足Protobuf中定义的条件约束。无论采用何种类型,所有的Validate方法都用相同的签名,因此可以满足相同的验证接口。
|
生成的代码为Message结构体增加了一个Validate方法,用于验证该成员是否满足Protobuf中定义的条件约束。无论采用何种类型,所有的Validate方法都用相同的签名,因此可以满足相同的验证接口。
|
||||||
|
|
||||||
通过生成的验证函数,并结合GRPC的截取器,我们可以很容易为每个方法的输入参数和返回值进行验证。
|
通过生成的验证函数,并结合gRPC的截取器,我们可以很容易为每个方法的输入参数和返回值进行验证。
|
||||||
|
|
||||||
## 4.6.2 REST接口
|
## 4.6.2 REST接口
|
||||||
|
|
||||||
GRPC服务一般用于集群内部通信,如果需要对外暴露服务一般会提供等价的REST接口。通过REST接口比较方便前端JavaScript和后端交互。开源社区中的grpc-gateway项目就实现了将GRPC服务转为REST服务的能力。
|
gRPC服务一般用于集群内部通信,如果需要对外暴露服务一般会提供等价的REST接口。通过REST接口比较方便前端JavaScript和后端交互。开源社区中的grpc-gateway项目就实现了将gRPC服务转为REST服务的能力。
|
||||||
|
|
||||||
grpc-gateway的工作原理如下图:
|
grpc-gateway的工作原理如下图:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 4-2 Grpc-Gateway工作流程*
|
*图 4-2 gRPC-Gateway工作流程*
|
||||||
|
|
||||||
通过在Protobuf文件中添加路由相关的元信息,通过自定义的代码插件生成路由相关的处理代码,最终将REST请求转给更后端的GRPC服务处理。
|
通过在Protobuf文件中添加路由相关的元信息,通过自定义的代码插件生成路由相关的处理代码,最终将REST请求转给更后端的gRPC服务处理。
|
||||||
|
|
||||||
路由扩展元信息也是通过Protobuf的元数据扩展用法提供:
|
路由扩展元信息也是通过Protobuf的元数据扩展用法提供:
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ service RestService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
我们首先为GRPC定义了Get和Post方法,然后通过元扩展语法在对应的方法后添加路由信息。其中“/get/{value}”路径对应的是Get方法,`{value}`部分对应参数中的value成员,结果通过json格式返回。Post方法对应“/post”路径,body中包含json格式的请求信息。
|
我们首先为gRPC定义了Get和Post方法,然后通过元扩展语法在对应的方法后添加路由信息。其中“/get/{value}”路径对应的是Get方法,`{value}`部分对应参数中的value成员,结果通过json格式返回。Post方法对应“/post”路径,body中包含json格式的请求信息。
|
||||||
|
|
||||||
然后通过以下命令安装protoc-gen-grpc-gateway插件:
|
然后通过以下命令安装protoc-gen-grpc-gateway插件:
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ func RegisterRestServiceHandlerFromEndpoint(
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
RegisterRestServiceHandlerFromEndpoint函数用于将定义了Rest接口的请求转发到真正的GRPC服务。注册路由处理函数之后就可以启动Web服务了:
|
RegisterRestServiceHandlerFromEndpoint函数用于将定义了Rest接口的请求转发到真正的gRPC服务。注册路由处理函数之后就可以启动Web服务了:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -250,9 +250,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
首先通过runtime.NewServeMux()函数创建路由处理器,然后通过RegisterRestServiceHandlerFromEndpoint函数将RestService服务相关的REST接口中转到后面的GRPC服务。grpc-gateway提供的runtime.ServeMux类也实现了http.Handler接口,因此可以和标准库中的相关函数配合使用。
|
首先通过runtime.NewServeMux()函数创建路由处理器,然后通过RegisterRestServiceHandlerFromEndpoint函数将RestService服务相关的REST接口中转到后面的gRPC服务。grpc-gateway提供的runtime.ServeMux类也实现了http.Handler接口,因此可以和标准库中的相关函数配合使用。
|
||||||
|
|
||||||
当GRPC和REST服务全部启动之后,就可以用curl请求REST服务了:
|
当gRPC和REST服务全部启动之后,就可以用curl请求REST服务了:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl localhost:8080/get/gopher
|
$ curl localhost:8080/get/gopher
|
||||||
@ -277,5 +277,5 @@ $ protoc -I. \
|
|||||||
|
|
||||||
## 4.6.3 Nginx
|
## 4.6.3 Nginx
|
||||||
|
|
||||||
最新的Nginx对GRPC提供了深度支持。可以通过Nginx将后端多个GRPC服务聚合到一个Nginx服务。同时Nginx也提供了为同一种GRPC服务注册多个后端的功能,这样可以轻松实现GRPC负载均衡的支持。Nginx的GRPC扩展是一个较大的主题,感兴趣的读者可以自行参考相关文档。
|
最新的Nginx对gRPC提供了深度支持。可以通过Nginx将后端多个gRPC服务聚合到一个Nginx服务。同时Nginx也提供了为同一种gRPC服务注册多个后端的功能,这样可以轻松实现gRPC负载均衡的支持。Nginx的gRPC扩展是一个较大的主题,感兴趣的读者可以自行参考相关文档。
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
# 4.8 grpcurl工具
|
# 4.8 grpcurl工具
|
||||||
|
|
||||||
Protobuf本身具有反射功能,可以在运行时获取对象的Proto文件。grpc同样也提供了一个名为reflection的反射包,用于为grpc服务提供查询。GRPC官方提供了一个C++实现的grpc_cli工具,可以用于查询GRPC列表或调用GRPC方法。但是C++版本的grpc_cli安装比较复杂,我们推荐用纯Go语言实现的grpcurl工具。本节将简要介绍grpcurl工具的用法。
|
Protobuf本身具有反射功能,可以在运行时获取对象的Proto文件。gRPC同样也提供了一个名为reflection的反射包,用于为gRPC服务提供查询。gRPC官方提供了一个C++实现的grpc_cli工具,可以用于查询gRPC列表或调用gRPC方法。但是C++版本的grpc_cli安装比较复杂,我们推荐用纯Go语言实现的grpcurl工具。本节将简要介绍grpcurl工具的用法。
|
||||||
|
|
||||||
## 4.8.1 启动反射服务
|
## 4.8.1 启动反射服务
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
如果启动了gprc反射服务,那么就可以通过reflection包提供的反射服务查询GRPC服务或调用GRPC方法。
|
如果启动了gprc反射服务,那么就可以通过reflection包提供的反射服务查询gRPC服务或调用gRPC方法。
|
||||||
|
|
||||||
## 4.8.2 查看服务列表
|
## 4.8.2 查看服务列表
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ HelloService.HelloService
|
|||||||
grpc.reflection.v1alpha.ServerReflection
|
grpc.reflection.v1alpha.ServerReflection
|
||||||
```
|
```
|
||||||
|
|
||||||
其中HelloService.HelloService是在protobuf文件定义的服务。而ServerReflection服务则是reflection包注册的反射服务。通过ServerReflection服务可以查询包括本身在内的全部GRPC服务信息。
|
其中HelloService.HelloService是在protobuf文件定义的服务。而ServerReflection服务则是reflection包注册的反射服务。通过ServerReflection服务可以查询包括本身在内的全部gRPC服务信息。
|
||||||
|
|
||||||
## 4.8.3 服务的方法列表
|
## 4.8.3 服务的方法列表
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ message String {
|
|||||||
|
|
||||||
## 4.8.5 调用方法
|
## 4.8.5 调用方法
|
||||||
|
|
||||||
在获取GRPC服务的详细信息之后就可以json调用GRPC方法了。
|
在获取gRPC服务的详细信息之后就可以json调用gRPC方法了。
|
||||||
|
|
||||||
下面命令通过`-d`参数传入一个json字符串作为输入参数,调用的是HelloService服务的Hello方法:
|
下面命令通过`-d`参数传入一个json字符串作为输入参数,调用的是HelloService服务的Hello方法:
|
||||||
|
|
||||||
@ -193,4 +193,4 @@ $ grpcurl -plaintext -d @ localhost:1234 HelloService.HelloService/Channel
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
通过grpcurl工具,我们可以在没有服务端代码的环境下测试GRPC服务。
|
通过grpcurl工具,我们可以在没有服务端代码的环境下测试gRPC服务。
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
## 4.9 补充说明
|
## 4.9 补充说明
|
||||||
|
|
||||||
目前专门讲述RPC的图书比较少。目前Protobuf和GRPC的官网都提供了详细的参考资料和例子。本章重点讲述了Go标准库的RPC和基于Protobuf衍生的GRPC框架,同时也简单展示了如何自己定制一个RPC框架。之所以聚焦在这几个有限的主题,是因为这几个技术都是Go语言团队官方在进行维护,和Go语言契合也最为默契。不过RPC依然是一个庞大的主题,足以单独成书。目前开源世界也有很多富有特色的RPC框架,还有针对分布式系统进行深度定制的RPC系统,用户可以根据自己实际需求选择合适的工具。
|
目前专门讲述RPC的图书比较少。目前Protobuf和gRPC的官网都提供了详细的参考资料和例子。本章重点讲述了Go标准库的RPC和基于Protobuf衍生的gRPC框架,同时也简单展示了如何自己定制一个RPC框架。之所以聚焦在这几个有限的主题,是因为这几个技术都是Go语言团队官方在进行维护,和Go语言契合也最为默契。不过RPC依然是一个庞大的主题,足以单独成书。目前开源世界也有很多富有特色的RPC框架,还有针对分布式系统进行深度定制的RPC系统,用户可以根据自己实际需求选择合适的工具。
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user