之前写过一篇go gRPC初体验(win10+普通网络),今天写个姊妹篇,说一说thrift。
thrift和gprc呢,使用都很广泛,以我现在的水平还无法评价孰好孰坏,反正实习的时候我看的程序里,grpc和thrift都有出现,所以说都学习一下,是最保险的。
为什么我先写的grpc呢,因为grpc的官方文档有中文版,凡是先挑简单的做嘛,而thrift,我查了查,好像并没有中文文档,网上虽然也有一些文章,但总觉得不够有代表性,还是跟着官方文档走更让人放心,于是乎,我瞅了瞅英文版的文档,发现竟然还可以,以我的英文水平基本可以看懂,这就好办了,跟着官方文档走就欧克了。
进入正文之前,还是先声明一下受众群体,跟grpc那篇一样,直接截图过来。
首先,我们要安装thrift命令,因为后面要用这个命令生成go代码。
很简单,不需要什么编译安装,官方有现成的exe文件,下载地址:http://archive.apache.org/dist/thrift/
页面是上面这样的,可以看到最新版的是0.13.0版本,就它了,点进去。
我们是windows系统,所以直接下载exe文件就行,下载之后,放到任意PATH路径下面,这里我依旧是选择了放在$GOROOT/bin下,注意,拷贝进去之后,把名字改成thrift.exe,就像下面这样。
到这就完事儿了,验证一下,打开CMD,输入:
thrift --version
看到版本号,像下面这样,就说明thrift安装成功了。
其实呢,如果只是go语言用,下载源码里面的go包就可以了,但是我们为了看官方文档,就把整个项目拉了下来。
git clone https://github.com/apache/thrift.git $GOPATH/github.com/apache/
这里不得不说,这个下载的速度那是真的快,500kBps,不知道是不是apache官方库带宽大的缘故,反正就是贼快。
下下来之后可以看一下,go语言需要的包在这里。
项目下下来了,接下来,就可以看官方文档,就是那个README.md了。刚开始看我是忐忑的,生怕它写的太高深,好在,文档不长,而且我很快就找到了我想要的,就在这里,Project Hierarchy,也就是代码层次结构,这块说了在tutorial路径下是快速开始的教程,哈哈,真是要啥来啥。
tutorial路径下有各个语言文件夹,应该就是各个语言的示例程序,还有一个README.md,很显然,这就是新手教程了。
新手教程很短,这是好事儿,毕竟是英文的,这块也就不盼着它多生动形象了。。。
简单翻译一下:
这一套下来,正是我惯用的初体验流程啊,哈哈,啥也不说了,后面就跟着它说的一步一步走了。
本来初体验呢,是不想学的太深的,但是瞅了眼tutorial.thrift,发现挺简单的,甚至作者还有些小幽默,所以这里还是推荐初学者看一看,我这里也简单说一说几个要点。
前面一大堆都是注释,注释有三种形式:
#
/**/
//
也就是shell的注释语法,和c的注释语法,在thrift文件里都有效。
这里给出了thrift支持的变量类型,包括下面这些:
* bool Boolean, one byte
* i8 (byte) Signed 8-bit integer
* i16 Signed 16-bit integer
* i32 Signed 32-bit integer
* i64 Signed 64-bit integer
* double 64-bit floating point value
* string String
* binary Blob (byte array)
* map Map from one type to another
* list Ordered list of one type
* set Set of unique elements of one type
也就是说在thrift里写这些类型,编译之后就会生成编程语言里对应类型的变量。
这个瞅着挺厉害,还能像C一样include,像下面这样:
include "shared.thrift"
这样的话,就可以把其他thrift文件里面定义的结构体或者方法引过来用了。
结构体,这个是最常用的,这里面我们可以为变量添加optional属性,加了这个属性的变量是可以省略的,或者说不是必须要有的。
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
还有常量、枚举、异常,不说了,略过。
最后说服务的定义,这块作者为了展示thrift支持继承,特意给了个继承的写法。
service Calculator extends shared.SharedService {
/**
* A method definition looks like C code. It has a return type, arguments,
* and optionally a list of exceptions that it may throw. Note that argument
* lists and exception lists are specified using the exact same syntax as
* field lists in struct or exception definitions.
*/
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
/**
* This method has a oneway modifier. That means the client only makes
* a request and does not listen for any response at all. Oneway methods
* must be void.
*/
oneway void zip()
}
这里面呢,给了几个调用方法,有参数没有参数的,又返回值没返回值的,最后还特别的给了一个oneway熟悉的方法,加了这个属性的调用,只需要服务请求者把请求发出去就行了,不需要回复,有点意思哈。
虽然大致了解了thrift语法了,但是作为初体验,我们还是不自己写了,就用它这个tutorial.thrift吧,毕竟给的样例代码也是基于这个tutorial.thrift生成的代码写的。
首先,我们还是自己在$GOPATH/src下新建一个项目,假装是我们自己在开发一个需要rpc调用的客户端和服务器程序,项目就叫goThrift-test吧,建好之后,把tutorial下面的shared.thrift和tutorial.thrift拷贝到这个项目里。
接下来,就执行教程里给的那个命令,生成go代码。
thrift -r --gen go tutorial.thrift
这个成功以后啥提示都不会有。
但是,我们想要的代码,已经生成了,在一个叫gen-go的文件夹里。
看到生成这么多文件,我是拒绝的,因为不知道都是干啥的啊。。。这点grpc我看就很好,就生成一个go文件。。。
这个生成的代码,如果你用熟了,是不用看的,直接用就完事儿了,但是我是初学者嘛,生成了个啥我都不知道,咋用啊。。。所以,还是按照新手教程说的,看看生成的代码。
问题是,生成那么多个文件,都要看吗?
这个新手教程没说,我只能百度一下,按网上说的大概就是,-remote文件夹里面是客户端示例代码,常量在-const文件里,而最重要的一部分代码,包括结构体,服务,以及序列化反序列化都在和thrift文件同名的那个go文件中,反正就是,这块只看tutorial.go就行了。
tutorial.go引用了shared那个thrift文件生成的shared包,这是因为tutorial.thrift文件里include了shared的嘛,还记得吧。但是这会带来一个问题,生成的tutorial.go只知道要引用shared包,却不知道shared包的位置。
可以看到,那个shared是红色的,也就是找不到这个包,因此,我们要加上这个包相对于$GOPATH/src的相对路径。
"goThrift-test/gen-go/shared"
这样就好了,这样的问题还有几个,如果你现在没有发现,等会儿编译的时候也会报出来,没事儿,到时候都加上路径就没问题了。
tutorial.go文件里首先是一堆序列化、反序列化、解析、组装消息内容的函数,这些并不是没用的,比如在写服务器的处理函数的时候,我们可能会用到解析消息内容的函数。
但这些内部具体怎么实现的就不用管了,需要的时候用就行了,这块我也就不细看了。
生成的代码里面,我们最感兴趣的还是客户端的调用还有服务端的处理是怎么实现的。客户端还是服务器都要有服务里定义的那几个方法,就是ping、add那些,所以这块我们自然想到了go语言的接口,而thrift生成的代码也确实是定义了一个服务名称命名的接口,就是下面这段。
type Calculator interface {
shared.SharedService
//Ahh, now onto the cool part, defining a service. Services just need a name
//and can optionally inherit from another service using the extends keyword.
// A method definition looks like C code. It has a return type, arguments,
// and optionally a list of exceptions that it may throw. Note that argument
// lists and exception lists are specified using the exact same syntax as
// field lists in struct or exception definitions.
Ping(ctx context.Context) (err error)
// Parameters:
// - Num1
// - Num2
Add(ctx context.Context, num1 int32, num2 int32) (r int32, err error)
// Parameters:
// - Logid
// - W
Calculate(ctx context.Context, logid int32, w *Work) (r int32, err error)
// This method has a oneway modifier. That means the client only makes
// a request and does not listen for any response at all. Oneway methods
// must be void.
Zip(ctx context.Context) (err error)
}
首先,由于是继承的shared,所以里面有shared那个接口,然后,接口里定义了服务中的几个方法。
由于客户端代码没有任何逻辑,只需要把请求参数传递给服务端就可以了,所以客户端的代码thrift直接给我们生成好了。
首先,是一个客户端结构体。
type CalculatorClient struct {
*shared.SharedServiceClient
}
后面给出了构造它的工厂函数。
客户端结构体实现了上面给的服务接口,也就是实现了接口中的所有方法,比如这个Add方法。
func (p *CalculatorClient) Add(ctx context.Context, num1 int32, num2 int32) (r int32, err error) {
var _args2 CalculatorAddArgs
_args2.Num1 = num1
_args2.Num2 = num2
var _result3 CalculatorAddResult
if err = p.Client_().Call(ctx, "add", &_args2, &_result3); err != nil {
return
}
return _result3.GetSuccess(), nil
}
这段其实很好理解,里面那个Call方法肯定就是把参数封装好然后发给服务端。
服务端的话,自然也是要实现上面那个接口,只不过,服务端的处理逻辑不是固定的,所以这块thrift没有办法为我们自动生成,需要我们自己去定义结构体实现这个接口,自己去写每个方法的实现逻辑。
再后面的一些processor啊,还有参数读写之类的方法结构体都是thrift框架用的,就不用管了,阅读代码的部分就先到这里。
按照新手教程,下一步就是看tutorial文件夹里面给的样例程序了,go语言的是这些。
这里面呢,client.go里封装了客户端的方法,server.go里面封装了服务端的方法,这两个方法都是在main.go里被调用的,也就是说,运行main函数,既可以跑客户端,又可以跑服务器,而跑客户端还是服务器是靠这个选项来区分的。
这块填true,跑起来就是服务端,填false,跑起来就是客户端。
main.go,server.go,client.go这仨都没啥说的,都是调用了thrift框架的方法或者刚刚通过thrift文件生成的代码,要说的是这个handler.go,还记得上面一节看生成的代码的时候,我说过服务端的实现不是生成的,而是要自己写,是没错,这个handler.go里面就是我们自己实现的服务接口,里面有每一个方法的处理逻辑,比如这个Add方法。
func (p *CalculatorHandler) Add(ctx context.Context, num1 int32, num2 int32) (retval17 int32, err error) {
fmt.Print("add(", num1, ",", num2, ")\n")
return num1 + num2, nil
}
很简单,先打印调用内容,然后把客户端传过来的两数相加再返回就完事儿了。
欧克,到这儿我们其实已经大致明白thrift框架的用法和原理了,接下来,就跑一下子试试吧,嘿嘿,这才是我最喜欢的。
首先,把服务端跑起来。
go run main.go server.go client.go handler.go
如果报错说找不到包,像下面这样:
或者结构不对,像下面这样:
莫慌,都是包的问题,把所有引用生成包的地方:
import (
"shared"
"tutorial"
)
都加上相对$GOPATH/src的路径就好了,像下面这样。
import (
"goThrift-test/gen-go/shared"
"goThrift-test/gen-go/tutorial"
)
然后,服务端就跑起来了:
接下来,打开另一个terminal,把main函数里的server那里按上一节说的改成false,再运行,就是客户端了。
可以看到,客户端依次调用了所有的接口,全都得到了我们期待的结果,啦啦啦。
再瞅一眼服务器:
也像我们设计的那样,把调用内容打印了出来,欧克,一切正常,收工!
新手教程的最后一句话是,按着它说的做,足以让你为使用thrift做自己的项目做好准备了,而我想说的是,跟着官方新手教程走,准没错!
这一趟下来,thrift语法也了解了,thrift怎么生成代码也会了,生成的代码怎么用也知道了,哈哈,美滋滋。
当然,实际使用过程中,有很多用法我们还需要通过实战来熟悉,不过有了这次初体验打下的基础,我相信后面遇到再大的问题也不至于毫无头绪了,哈哈。