1
0
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:
chai2010 2018-12-14 16:56:15 +08:00
parent 980d281283
commit dbd8324a0b
8 changed files with 69 additions and 69 deletions

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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所示
![](../images/ch4-1-grpc-go-stack.png) ![](../images/ch4-1-grpc-go-stack.png)
*图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简单实现了一个跨网络的发布和订阅服务。

View File

@ -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服务了。

View File

@ -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的工作原理如下图
![](../images/ch4-2-grpc-gateway.png) ![](../images/ch4-2-grpc-gateway.png)
*图 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扩展是一个较大的主题感兴趣的读者可以自行参考相关文档。

View File

@ -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服务。

View File

@ -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系统用户可以根据自己实际需求选择合适的工具。