在公司实习的时候,发现代码里面有grpc,当时啥也不懂,也不知道咋用的,好在实习期间并没有需要新增rpc调用的地方,但还是觉得趁早弄明白比较好,以后总会用到。
既然是初体验,肯定是从啥都没有开始的,网上很多文章,安装各种包、命令讲的不是很系统,所以本篇就记录一下从安装开始,到运行一个小case的全过程。
首先声明一下,我是在win10上操作的,我觉得吧,什么东西,如果能在windows上搞通了,在linux和mac上自然也就没问题了。
另外再声明一下,我电脑没有,以下所有操作都是普通网络环境就可以搞定的。
grpc使用protocol buffers作为IDL,这很好理解,都是谷歌出的,而使用grpc,第一步其实就是自己写好proto文件,然后使用protoc命令生成go语言包,所以,我们首先要装protoc命令,这个东西我一开始一看还挺恐慌的,想着是不是得编译安装啥的,后来发现并不用,直接下载现成的exe文件就完事儿了,这里直接给出网址 https://github.com/protocolbuffers/protobuf/releases
这个网址打开是这样的:
可以看到最新版本已经是3.11了,那么问题来了,一大堆版本,有各种语言的,还有各种系统的,我们要下哪个呢?说实话我也不懂。。。不过既然是要在windows上体验,就下个win64的吧,最后的成功证明,我的直觉是对的,哈哈。
下下来之后,解压,然后,直接将bin里面的exe文件,就是下面这个,拷贝到PATH里的任一一个路径下面就行,既然我是为了用它做go语言的grpc,所以我选择了把它放在我的GOROOT/bin里面。
然后,打开cmd验证一下,输入
protoc --version
看到版本号,就说明protoc安装成功了。
这块呢,因为是普通网络环境,go get是用不了的,所以我们用git下载grpc的go语言包。
git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
这里注意,我们clone下来之后把包放到了$GOPATH/src/google.golang.org/grpc,这是因为,谷歌只是把程序放在了github一份,但是其他包引用grpc包的时候用的还是google.golang.org/grpc这个路径。
一般情况下,装个一个比较大的包会依赖很多其他包,grpc也是如此,但是正常情况下,到这里我们也不知道它缺啥依赖,所以先不管,最后运行程序的时候根据错误提示,缺啥补啥就好了。
老实说,第一次自己体验,到这里我已经认为可以了。。。所以直接开始尝试了。结果是失败了,但是我觉得没啥,按网上写的一通乱装,都不知道装的那些东西是干啥的,倒不如遇到问题了,再看到底是缺了啥,然后再去装。
言归正传,初次体验,我的目标很简单,就是跑通grpc包里提供的helloworld,grpc的官方文档教的其实也是这个。
首先,在$GOPATH/src/下新建一个工程,就叫goGrpc-test吧。
然后,新建一个helloworld文件夹,在里面新建一个helloworld.proto文件,把grpc包里提供的helloworld.proto文件内容拷贝进去,proto文件在这里:
proto文件内容是这些:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
这个proto文件吧,我以前也没接触过,要自己写肯定是完全不会,不过看这个写好的其实也能懂大概意思。
欧克,这个proto大概理解了,接下来就是生成对应的go语言代码,这里终于要用到我们最开始安装的protoc了,命令如下:
protoc --go_out=plugins=grpc:. helloworld.proto
然鹅,失败了。。。
这报错信息说找不到,protoc-gen-go,看这个命令,有一个--go_out,这个应该就是生成go代码的意思,看来,生成go代码还需要装这个protoc-gen-go才行,好吧,退出体验,继续装命令。
看网上的文章,这个又是go get装的,既然是go-get,都可以通过git clone+go install取代,所以我们首先拉下代码。
git clone https://github.com/golang/protobuf.git $GOPATH/src/github.com/golang/protobuf
注意,这里我直接把protobuf的go语言包拉了下来,因为里面有protoc-gen-go。
接下来就是安装,安装其实就是编译包里面的文件,生成一个exe文件,然后放到$GOPATH/bin下面,安装命令如下:
go install github.com/golang/protobuf/protoc-gen-go
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2020.4.18补充:今天,一个同学按照我的blog装protoc-gen-go的时候,报了这个错:
按照这个错误,我打开我的github.com\golang\protobuf\protoc-gen-go\main.go瞅了一眼,发现28行并没有import它说找不到的这个包,后来发现,golang/protobuf这个仓库在2020.4.13提交过一次:
这次提交修改了main.go的内容,修改之后该怎么安装protoc-gen-go我还没有研究,不过经过试验,把版本回退到我clone的时候那个版本之后再go install是欧克的,所以如果你想先凑合用的话,就先把protobuf回退到这个版本,再go install。
git reset --hard d23c5127
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
go install执行成功不会有什么安装成功之类的信息,我们可以直接去$GOPATH/bin下看看有没有生成对应的exe文件。
看到有这个,那就是装成功了。
刚才失败了,说是找不到这个proto-gen-go,现在我们装上了,可以再试一把了。
这回,没有报错,而且成功生成了一个go文件。
打开生成的代码一看,妈呀,刚才20行的proto文件,竟然生成了一个两百多行的go程序,好厉害,不得不说,这些rpc框架确实是很方便,如果没有这个,这些代码就要我们手动去写。
行了,go代码也生成了,接下来我们就可以使用这些生成好的代码,写一个客户端,一个服务器,体验一下rpc了。
客户端和服务器的程序呢,我也是用的grpc的helloworld包里面提供的,为了模拟这是我自己写的一个rpc,我又把里面的内容拷出来放到我自己新建的文件里了。
首先,自己新建一个client.go,一个server.go。
然后,把grpc包helloword里的客户端和服务器程序相应的贴进去。
贴进去之后,改一下,import,改成我们刚刚自己生成的helloworld包。
最终的server.go如下:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "goGrpc-test/helloworld"
)
const (
port = ":50051"
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
client.go代码如下:
package main
import (
"context"
"log"
"os"
"time"
"google.golang.org/grpc"
pb "goGrpc-test/helloworld"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
到这里已经忍不住开始激动了,马上要见证成果了!先把server跑起!
这一大堆错吓了我一跳。。。好在定睛一看,只是缺少依赖包而已。。。
根据上面的报错,先把这个golang.org/x/net装上。
git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
然后再跑一下。
还缺个text和genproto,一口气装上:
git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
依赖装好了,再来!
先服务器跑起。
这回没报错,还弹出个windows防火墙,说明开始监听端口了,嘿嘿。
接下来,客户端走起,注意这里客户端和服务器分别跑在两个terminal里。
哈哈,成功了,客户端收到了来自服务器的回复,正是我们熟悉的Hello world!
再瞅一眼服务器。
也打印到了接收到的信息,完美!
到这里,go语言grpc初体验就算是成功收尾了。
这一趟做下来,也算是对grpc有个基本认识了,总结一下,当我们需要增加一个grpc接口的时候,其实就是以下几步
这么一看,还蛮简单的,哈哈。
最后还想简单记录一个点,之前实习的时候在一次会上听到的,这个点就是,为什么程序不全使用thrift,而是同时使用thrift和grpc?
首先,thrift是Facebook的开源rpc框架,也是一个使用非常广泛的rpc框架(这个我也体验了一把,感兴趣的看这里go Thrift初体验(win10+普通网络))。
那么grpc和thrift相比优势在哪里呢,主要在于,grpc是基于http-2设计的,http-2的一个重要特点是在单个TCP连接上可以复用多个请求,这一点thrift是做不到的,也就是说,thrift的rpc,一次调用在收到回复之前,是会占用一个TCP连接的,当并发量很高,而一次调用耗时又很长的时候,thrift会出现连接被占满的情况,导致调用堆积,延时增大,而grpc,多个调用可以复用一个连接,这样显然就比thrift支持的并发调用数量更高。
如果是同机房调用,调用一次的延时是很低的,这个时候grpc的优势并不能体现,但如果服务请求者和服务提供者在不同机房,一次通信的延时可能很大,这个时候用grpc就是一个更好的选择。
grpc的官方文档有一句是,grpc在移动设备上表现更好,其实也就是上面说的这个道理。