Skip to content

Commit

Permalink
规范 gRPC 写法
Browse files Browse the repository at this point in the history
  • Loading branch information
chai2010 committed Dec 14, 2018
1 parent 980d281 commit dbd8324
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 69 deletions.
6 changes: 3 additions & 3 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
* [4.1 RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
* [4.2 Protobuf](ch4-rpc/ch4-02-pb-intro.md)
* [4.3 玩转RPC](ch4-rpc/ch4-03-netrpc-hack.md)
* [4.4 GRPC入门](ch4-rpc/ch4-04-grpc.md)
* [4.5 GRPC进阶](ch4-rpc/ch4-05-grpc-hack.md)
* [4.6 GRPC和Protobuf扩展](ch4-rpc/ch4-06-grpc-ext.md)
* [4.4 gRPC入门](ch4-rpc/ch4-04-grpc.md)
* [4.5 gRPC进阶](ch4-rpc/ch4-05-grpc-hack.md)
* [4.6 gRPC和Protobuf扩展](ch4-rpc/ch4-06-grpc-ext.md)
* [4.7 pbgo: 基于Protobuf的框架](ch4-rpc/ch4-07-pbgo.md)
* [4.8 grpcurl工具](ch4-rpc/ch4-08-grpcurl.md)
* [4.9 补充说明](ch4-rpc/ch4-09-ext.md)
Expand Down
2 changes: 1 addition & 1 deletion ch1-basic/ch1-04-func-method-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ func main() {

我们在自己的`TB`结构体类型中重新实现了`Fatal`方法,然后通过将对象隐式转换为`testing.TB`接口类型(因为内嵌了匿名的`testing.TB`对象,因此是满足`testing.TB`接口的),然后通过`testing.TB`接口来调用我们自己的`Fatal`方法。

这种通过嵌入匿名接口或嵌入匿名指针对象来实现继承的做法其实是一种纯虚继承,我们继承的只是接口指定的规范,真正的实现在运行的时候才被注入。比如,我们可以模拟实现一个grpc的插件
这种通过嵌入匿名接口或嵌入匿名指针对象来实现继承的做法其实是一种纯虚继承,我们继承的只是接口指定的规范,真正的实现在运行的时候才被注入。比如,我们可以模拟实现一个gRPC的插件

```go
type grpcPlugin struct {
Expand Down
10 changes: 5 additions & 5 deletions ch4-rpc/ch4-02-pb-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,22 @@ service HelloService {

但是重新生成的Go代码并没有发生变化。这是因为世界上的RPC实现有千万种,protoc编译器并不知道该如何为HelloService服务生成代码。

不过在protoc-gen-go内部已经集成了一个叫grpc的插件,可以针对grpc生成代码
不过在protoc-gen-go内部已经集成了一个名字为`grpc`的插件,可以针对gRPC生成代码

```
$ protoc --go_out=plugins=grpc:. hello.proto
```

在生成的代码中多了一些类似HelloServiceServer、HelloServiceClient的新类型。这些类型是为grpc服务的,并不符合我们的RPC要求。
在生成的代码中多了一些类似HelloServiceServer、HelloServiceClient的新类型。这些类型是为gRPC服务的,并不符合我们的RPC要求。

不过grpc插件为我们提供了改进的思路,下面我们将探索如何为我们的RPC生成安全的代码。
不过gRPC插件为我们提供了改进的思路,下面我们将探索如何为我们的RPC生成安全的代码。


## 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
// A Plugin provides functionality to add to the output during
Expand Down
40 changes: 20 additions & 20 deletions ch4-rpc/ch4-04-grpc.md
Original file line number Diff line number Diff line change
@@ -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)

*图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接口:

Expand All @@ -32,13 +32,13 @@ service HelloService {
}
```

使用protoc-gen-go内置的grpc插件生成GRPC代码
使用protoc-gen-go内置的gRPC插件生成gRPC代码

```
$ protoc --go_out=plugins=grpc:. hello.proto
```

GRPC插件会为服务端和客户端生成不同的接口
gRPC插件会为服务端和客户端生成不同的接口

```go
type HelloServiceServer interface {
Expand All @@ -50,7 +50,7 @@ type HelloServiceClient interface {
}
```

GRPC通过context.Context参数,为每个方法调用提供了上下文支持。客户端在调用方法的时候,可以通过可选的grpc.CallOption类型的参数提供额外的上下文信息。
gRPC通过context.Context参数,为每个方法调用提供了上下文支持。客户端在调用方法的时候,可以通过可选的grpc.CallOption类型的参数提供额外的上下文信息。

基于服务端的HelloServiceServer接口可以重新实现HelloService服务:

Expand All @@ -65,7 +65,7 @@ func (p *HelloServiceImpl) Hello(
}
```

GRPC服务的启动流程和标准库的RPC服务启动流程类似
gRPC服务的启动流程和标准库的RPC服务启动流程类似

```go
func main() {
Expand All @@ -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
func main() {
Expand All @@ -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方法:

Expand Down Expand Up @@ -220,7 +220,7 @@ for {

## 4.4.4 发布和订阅模式

在前一节中,我们基于Go内置的RPC库实现了一个简化版的Watch方法。基于Watch的思路虽然也可以构造发布和订阅系统,但是因为RPC缺乏流机制导致每次只能返回一个结果。在发布和订阅模式中,由调用者主动发起的发布行为类似一个普通函数调用,而被动的订阅者则类似GRPC客户端单向流中的接收者。现在我们可以尝试基于GRPC的流特性构造一个发布和订阅系统
在前一节中,我们基于Go内置的RPC库实现了一个简化版的Watch方法。基于Watch的思路虽然也可以构造发布和订阅系统,但是因为RPC缺乏流机制导致每次只能返回一个结果。在发布和订阅模式中,由调用者主动发起的发布行为类似一个普通函数调用,而被动的订阅者则类似gRPC客户端单向流中的接收者。现在我们可以尝试基于gRPC的流特性构造一个发布和订阅系统

发布订阅是一个常见的设计模式,开源社区中已经存在很多该模式的实现。其中docker项目中提供了一个pubsub的极简实现,下面是基于pubsub包实现的本地发布订阅代码:

Expand Down Expand Up @@ -276,7 +276,7 @@ service PubsubService {
}
```

其中Publish是普通的RPC方法,Subscribe则是一个单向的流服务。然后grpc插件会为服务端和客户端生成对应的接口
其中Publish是普通的RPC方法,Subscribe则是一个单向的流服务。然后gRPC插件会为服务端和客户端生成对应的接口

```go
type PubsubServiceServer interface {
Expand Down Expand Up @@ -397,5 +397,5 @@ func main() {
}
```

到此我们就基于GRPC简单实现了一个跨网络的发布和订阅服务
到此我们就基于gRPC简单实现了一个跨网络的发布和订阅服务

Loading

0 comments on commit dbd8324

Please sign in to comment.