Skip to content

Commit

Permalink
ch4: 规范化内容编号
Browse files Browse the repository at this point in the history
  • Loading branch information
chai2010 committed Aug 5, 2018
1 parent bfb993c commit ec7ad01
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 24 deletions.
8 changes: 4 additions & 4 deletions ch4-rpc/ch4-01-rpc-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

RPC是远程过程调用的简称,是分布式系统中不同节点间流行的通信方式。在互联网时代,RPC已经和IPC一样成为一个不可或缺的基础构件。因此Go语言的标准库也提供了一个简单的RPC实现,我们将以此为入口学习RPC的各种用法。

## RPC版"Hello, World"
## 4.1.1 RPC版"Hello, World"

Go语言的RPC包的路径为net/rpc,也就是放在了net包目录下面。因此我们可以猜测该RPC包是建立在net包基础之上的。在第一章“Hello, World”革命一节最后,我们基于http实现了一个打印例子。下面我们尝试基于rpc实现一个类似的例子。

Expand Down Expand Up @@ -64,7 +64,7 @@ func main() {

由这个例子可以看出RPC的使用其实非常简单。

## 更安全的PRC接口
## 4.1.2 更安全的PRC接口

在涉及RPC的应用中,作为开发人员一般至少有三种角色:首选是服务端实现RPC方法的开发人员,其次是客户端调用RPC方法的人员,最后也是最重要的是制定服务端和客户端RPC接口规范的设计人员。在前面的例子中我们为了简化将以上几种角色的工作全部放到了一起,虽然看似实现简单,但是不利于后期的维护和工作的切割。

Expand Down Expand Up @@ -178,7 +178,7 @@ func main() {
在新的RPC服务端实现中,我们用RegisterHelloService函数来注册函数,这样不仅可以避免命名服务名称的工作,同时也保证了传入的服务对象满足了RPC接口的定义。最后我们新的服务改为支持多个TCP链接,然后为每个TCP链接提供RPC服务。


## 跨语言的RPC
## 4.1.3 跨语言的RPC

标准库的RPC默认采用Go语言特有的gob编码,因此从其它语言调用Go语言实现的RPC服务将比较困难。在互联网的微服务时代,每个RPC以及服务的使用者都可能采用不同的编程语言,因此跨语言是互联网时代RPC的一个首要条件。得益于RPC的框架设计,Go语言的RPC其实也是很容易实现跨语言支持的。

Expand Down Expand Up @@ -287,7 +287,7 @@ type serverResponse struct {

因此无论采用何种语言,只要遵循同样的json结构,以同样的流程就可以和Go语言编写的RPC服务进行通信。这样我们就实现了跨语言的RPC。

## Http上的RPC
## 4.1.4 Http上的RPC

Go语言内在的RPC框架已经支持在Http协议上提供RPC服务。但是框架的http服务同样采用了内置的gob协议,并且没有提供采用其它协议的接口,因此从其它语言依然无法访问的。在前面的例子中,我们已经实现了在TCP协议之上运行jsonrpc服务,并且通过nc命令行工具成功实现了RPC方法调用。现在我们尝试在http协议上提供jsonrpc服务。

Expand Down
6 changes: 3 additions & 3 deletions ch4-rpc/ch4-02-pb-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,并于2008年对外开源。Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能。但是我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础工具。

## Protobuf入门
## 4.2.1 Protobuf入门

对于没有用过Protobuf的读者,建议先从官网了解下基本用法。这里我们尝试将Protobuf和RPC结合在一起使用,通过Protobuf来最终保证RPC的接口规范和安全。Protobuf中最基本的数据单元是message,是类似Go语言中结构体的存在。在message中可以嵌套message或其它的基础数据类型的成员。

Expand Down Expand Up @@ -94,7 +94,7 @@ $ protoc --go_out=plugins=grpc:. hello.proto
不过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生成相关代码。

Expand Down Expand Up @@ -230,7 +230,7 @@ $ protoc --go-netrpc_out=plugins=netrpc:. hello.proto

至此,手工定制的Protobuf代码生成插件终于可以工作了。

## 自动生成完整的RPC代码
## 4.2.3 自动生成完整的RPC代码

在前面的例子中我们已经构建了最小化的netrpcPlugin插件,并且通过克隆protoc-gen-go的主程序创建了新的protoc-gen-go-netrpc的插件程序。现在开始继续完善netrpcPlugin插件,最终目标是生成RPC安全接口。

Expand Down
8 changes: 4 additions & 4 deletions ch4-rpc/ch4-03-netrpc-hack.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

在不同的场景中RPC有着不同的需求,因此开源的社区就诞生了各种RPC框架。本节我们将尝试Go内置RPC框架在一些比较特殊场景的用法。

## 客户端RPC的实现原理
## 4.3.1 客户端RPC的实现原理

Go语言的RPC库最简单的使用方式是通过`Client.Call`方法进行同步阻塞调用,该方法的实现如下:

Expand Down Expand Up @@ -69,7 +69,7 @@ func (call *Call) done() {

`Call.done`方法的实现可以得知`call.Done`管道会将处理后的call返回。

## 基于RPC实现Watch功能
## 4.3.2 基于RPC实现Watch功能

在很多系统中都提供了Watch监视功能的接口,当系统满足某种条件时Watch方法返回监控的结果。在这里我们可以尝试通过RPC框架实现一个基本的Watch功能。如前文所描述,因为`client.send`是线程安全的,我们也可以通过在不同的Goroutine中同时并发阻塞调用RPC方法。通过在一个独立的Goroutine中调用Watch函数进行监控。

Expand Down Expand Up @@ -175,7 +175,7 @@ func doClientWork(client *rpc.Client) {

首先启动一个独立的Goroutine监控key的变化。同步的watch调用会阻塞,直到有key发生变化或者超时。然后在通过Set方法修改KV值时,服务器会将变化的key通过Watch方法返回。这样我们就可以实现对某些状态的监控。

## 反向RPC
## 4.3.3 反向RPC

通常的RPC是基于C/S结构,RPC的服务端对应网络的服务器,RPC的客户端也对应网络客户端。但是对于一些特殊场景,比如在公司内网提供一个RPC服务,但是在外网无法链接到内网的服务器。这种时候我们可以参考类似反向代理的技术,首先从内网主动链接到外网的TCP服务器,然后基于TCP链接向外网提供RPC服务。

Expand Down Expand Up @@ -248,7 +248,7 @@ func doClientWork(clientChan <-chan *rpc.Client) {
首先从管道去取一个RPC客户端对象,并且通过defer语句指定在函数退出前关闭客户端。然后是执行正常的RPC调用。


## 上下文信息
## 4.3.4 上下文信息

基于上下文我们可以针对不同客户端提供定制化的RPC服务。我们可以通过为每个信道提供独立的RPC服务来实现对上下文特性的支持。

Expand Down
6 changes: 3 additions & 3 deletions ch4-rpc/ch4-04-grpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

GRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。GRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。本节将讲述GRPC的简单用法。

## GRPC入门
## 4.4.1 GRPC入门

如果从Protobuf的角度看,GRPC只不过是一个针对service接口生成代码的生成器。我们在本章的第二节中手工实现了一个简单的Protobuf代码生成器插件,只不过当时生成的代码是适配标准库的RPC框架的。现在我们将学习GRPC的用法。

Expand Down Expand Up @@ -95,7 +95,7 @@ func main() {

GRPC和标准库的RPC框架有一个区别,GRPC生成的接口并不支持异步调用。不过我们可以在多个Goroutine之间安全地共享GRPC底层的HTTP/2链接,因此可以通过在另一个Goroutine阻塞调用的方式模拟异步调用。

## GRPC流
## 4.4.2 GRPC流

RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间。因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合。同时传统RPC模式也不适用于对时间不确定的订阅和发布模式。为此,GRPC框架针对服务器端和客户端分别提供了流特性。

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

这样就完成了完整的流接收和发送支持。

## 发布和订阅模式
## 4.4.3 发布和订阅模式

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

Expand Down
8 changes: 4 additions & 4 deletions ch4-rpc/ch4-05-grpc-hack.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

作为一个基础的RPC框架,安全和扩展是经常遇到的问题。本节将简单介绍如何对GRPC进行安全认证。然后介绍通过GRPC的截取器特性,以及如何通过截取器优雅地实现Token认证、调用跟踪以及Panic捕获等特性。最后介绍了GRPC服务如何和其他Web服务共存。

## 证书认证
## 4.5.1 证书认证

GRPC建立在HTTP/2协议之上,对TLS提供了很好的支持。我们前面章节中GRPC的服务都没有提供证书支持,因此客户端在链接服务器中通过`grpc.WithInsecure()`选项跳过了对服务器证书的验证。没有启用证书的GRPC服务在和客户端进行的是明文通讯,信息面临被任何第三方监听的风险。为了保障GRPC通信不被第三方监听串改或伪造,我们可以对服务器启动TLS加密特性。

Expand Down Expand Up @@ -174,7 +174,7 @@ func main() {

到此我们就实现了一个服务器和客户端进行双向证书验证的通信可靠的GRPC系统。

## Token认证
## 4.5.2 Token认证

前面讲述的基于证书的认证是针对每个GRPC链接的认证。GRPC还为每个GRPC方法调用提供了认证支持,这样就基于基于用户Token对不同对方法访问进行权限管理。

Expand Down Expand Up @@ -278,7 +278,7 @@ func (a *Authentication) Auth(ctx context.Context) error {

详细地认证工作主要在Authentication.Auth方法中完成。首先通过metadata.FromIncomingContext从ctx上下文中获取元信息,然后取出相应的认证信息进行认证。如果认证失败,则返回一个codes.Unauthenticated类型地错误。

## 截取器
## 4.5.3 截取器

GRPC中的grpc.UnaryInterceptor和grpc.StreamInterceptor分别对普通方法和流方法提供了截取器的支持。我们这里简单介绍普通方法的截取器用法。

Expand Down Expand Up @@ -345,7 +345,7 @@ myServer := grpc.NewServer(

感兴趣的同学可以参考go-grpc-middleware包的代码。

## 和Web服务共存
## 4.5.4 和Web服务共存

GRPC构建在HTTP/2协议之上,因此我们可以将GRPC服务和普通的Web服务架设在同一个端口之上。因为目前Go语言版本的GRPC实现还不够完善,只有启用了TLS协议之后才能将GRPC和Web服务运行在同一个端口。

Expand Down
4 changes: 2 additions & 2 deletions ch4-rpc/ch4-06-grpc-ext.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

目前开源社区已经围绕Protobuf和GRPC开发出众多扩展,形成了庞大的生态。本节我们将简单介绍验证器和REST接口扩展。

## 验证器
## 4.6.1 验证器

到目前位置,我们接触的全部是第三版的Protobuf语法。第二版的Protobuf有个默认值特性,可以为字符串或数值类型的成员定义默认值。

Expand Down Expand Up @@ -159,7 +159,7 @@ func (this *Message) Validate() error {

通过生成的验证函数,并结合GRPC的截取器,我们可以很容易为每个方法的输入参数和返回值进行验证。

## REST接口
## 4.6.2 REST接口

GRPC服务一般用于集群内部通信,如果需要对外暴露服务一般会提供等价的REST接口。通过REST接口比较方便前端JavaScript和后端交互。开源社区中的grac-gateway项目就实现了将GRPC服务转为REST服务的能力。

Expand Down
8 changes: 4 additions & 4 deletions ch4-rpc/ch4-07-pbgo.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pbgo是我们专门针对本节内容设计的较为完整的迷你框架,它基于Protobuf的扩展语法,通过插件自动生成rpc和rest相关代码。在本章第二节我们已经展示过如何定制一个Protobuf代码生成插件,并生成了rpc部分的代码。在本节我们将重点讲述pbgo中和Protobuf扩展语法相关的rest部分的工作原理。

## Protobuf扩展语法
## 4.7.1 Protobuf扩展语法

目前Protobuf相关的很多开源项目都使用到了Protobuf的扩展语法。在前一节中提到的验证器就是通过给结构体成员增加扩展元信息实现验证。在grpc-gateway项目中,则是通过为服务的每个方法增加Http相关的映射规则实现对Rest接口的支持。pbgo也是通过Protobuf的扩展语法来为rest接口增加元信息。

Expand Down Expand Up @@ -56,7 +56,7 @@ service HelloService {

首先我们通过导入`github.com/chai2010/pbgo/pbgo.proto`文件引入扩展定义,然后在HelloService的Hello方法中使用了pbgo定义的扩展。Hello方法扩展的信息表示该方法对应一个REST接口,只有一个GET方法对应"/hello/:value"路径。在REST方法的路径中采用了httprouter路由包的语法规则,":value"表示路径中的该字段对应的是参数中同名的成员。

## 插件中读取扩展信息
## 4.7.2 插件中读取扩展信息

在本章的第二节我们已经简单讲述过Protobuf插件的工作原理,并且展示了如何生成RPC必要的代码。插件是一个generator.Plugin接口:

Expand Down Expand Up @@ -124,7 +124,7 @@ func (p *pbgoPlugin) getServiceMethodOption(

有了扩展信息之后,我们就可以参考第二节中生成RPC代码的方式生成REST相关的代码。

## 生成REST代码
## 4.7.3 生成REST代码

pbgo框架同时也提供了一个插件用于生成REST代码。不过我们的目的是学习pbgo框架的设计过程,因此我们先尝试手写Hello方法对应的REST代码,然后插件再根据手写的代码构造模板自动生成代码。

Expand Down Expand Up @@ -192,7 +192,7 @@ func _handle_HelloService_Hello_get(

在手工构造完成最终代码的结构之后,就可以在此基础上构造插件生成代码的模板。完整的插件代码和模板在`protoc-gen-pbgo/pbgo.go`文件,读者可以自行参考。

## 启动REST服务
## 4.7.4 启动REST服务

虽然从头构造pbgo框架的过程比较繁琐,但是使用pbgo构造REST服务却是异常简单。首先要构造一个满足HelloServiceInterface接口的服务对象:

Expand Down

0 comments on commit ec7ad01

Please sign in to comment.