在公司實習的時候,發現代碼裏面有grpc,當時啥也不懂,也不知道咋用的,好在實習期間並沒有需要新增rpc調用的地方,但還是覺得趁早弄明白比較好,以後總會用到。
既然是初體驗,肯定是從啥都沒有開始的,網上很多文章,安裝各種包、命令講的不是很系統,所以本篇就記錄一下從安裝開始,到運行一個小case的全過程。
首先聲明一下,我是在win10上操作的,我覺得吧,什麼東西,如果能在windows上搞通了,在linux和mac上自然也就沒問題了。
另外再聲明一下,我電腦沒有翻牆,以下所有操作都是普通網絡環境就可以搞定的。
安裝protoc
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安裝成功了。
下載grpc包
這塊呢,因爲是普通網絡環境,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也是如此,但是正常情況下,到這裏我們也不知道它缺啥依賴,所以先不管,最後運行程序的時候根據錯誤提示,缺啥補啥就好了。
體驗1
老實說,第一次自己體驗,到這裏我已經認爲可以了。。。所以直接開始嘗試了。結果是失敗了,但是我覺得沒啥,按網上寫的一通亂裝,都不知道裝的那些東西是幹啥的,倒不如遇到問題了,再看到底是缺了啥,然後再去裝。
言歸正傳,初次體驗,我的目標很簡單,就是跑通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文件吧,我以前也沒接觸過,要自己寫肯定是完全不會,不過看這個寫好的其實也能懂大概意思。
- syntax = "proto3" 這個就是指定proto版本,我們一開始是裝的就是3.11,所以這裏也是proto3
- option 這幾行我是真的毫無頭緒,這不是go麼,跟java有啥關係。。。
- package helloworld 這行好理解,就是用protoc命令生成的go代碼的包名,因爲後面寫客戶端服務器程序都要引用這個包,所以就叫helloworld了,也就是跟所在路徑同名
- service 這塊呢,就是定義了這個遠程調用的服務,服務請求者調用SayHello方法,請求中攜帶HelloRequest格式的信息,服務提供者則返回HelloReply格式的回覆
- message 這個就是定義請求和回覆的格式,這個例子裏請求和格式都是簡單的string
歐克,這個proto大概理解了,接下來就是生成對應的go語言代碼,這裏終於要用到我們最開始安裝的protoc了,命令如下:
protoc --go_out=plugins=grpc:. helloworld.proto
然鵝,失敗了。。。
這報錯信息說找不到,protoc-gen-go,看這個命令,有一個--go_out,這個應該就是生成go代碼的意思,看來,生成go代碼還需要裝這個protoc-gen-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的go語言包拉了下來,因爲裏面有protoc-gen-go。
接下來就是安裝,安裝其實就是編譯包裏面的文件,生成一個exe文件,然後放到$GOPATH/bin下面,安裝命令如下:
go install github.com/golang/protobuf/protoc-gen-go
go install執行成功不會有什麼安裝成功之類的信息,我們可以直接去$GOPATH/bin下看看有沒有生成對應的exe文件。
看到有這個,那就是裝成功了。
使用protoc生成go語言代碼
剛纔失敗了,說是找不到這個proto-gen-go,現在我們裝上了,可以再試一把了。
這回,沒有報錯,而且成功生成了一個go文件。
打開生成的代碼一看,媽呀,剛纔20行的proto文件,竟然生成了一個兩百多行的go程序,好厲害,不得不說,這些rpc框架確實是很方便,如果沒有這個,這些代碼就要我們手動去寫。
體驗2
行了,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跑起!
這一大堆錯嚇了我一跳。。。好在定睛一看,只是缺少依賴包而已。。。
安裝grpc依賴包
根據上面的報錯,先把這個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
體驗3
依賴裝好了,再來!
先服務器跑起。
這回沒報錯,還彈出個windows防火牆,說明開始監聽端口了,嘿嘿。
接下來,客戶端走起,注意這裏客戶端和服務器分別跑在兩個terminal裏。
哈哈,成功了,客戶端收到了來自服務器的回覆,正是我們熟悉的Hello world!
再瞅一眼服務器。
也打印到了接收到的信息,完美!
到這裏,go語言grpc初體驗就算是成功收尾了。
總結
這一趟做下來,也算是對grpc有個基本認識了,總結一下,當我們需要增加一個grpc接口的時候,其實就是以下幾步
- 修改proto文件,新增一個service,以及相應的請求和回覆的message
- 使用protoc生成go代碼
- 在服務器調用者和服務提供者的代碼裏分別調用生成的go包裏的方法
這麼一看,還蠻簡單的,哈哈。
一個小補充
最後還想簡單記錄一個點,之前實習的時候在一次會上聽到的,這個點就是,爲什麼程序不全使用thrift,而是同時使用thrift和grpc?
首先,thrift是Facebook的開源rpc框架,也是一個使用非常廣泛的rpc框架。
那麼grpc和thrift相比優勢在哪裏呢,主要在於,grpc是基於http-2設計的,http-2的一個重要特點是在單個TCP連接上可以複用多個請求,這一點thrift是做不到的,也就是說,thrift的rpc,一次調用在收到回覆之前,是會佔用一個TCP連接的,當併發量很高,而一次調用耗時又很長的時候,thrift會出現連接被佔滿的情況,導致調用堆積,延時增大,而grpc,多個調用可以複用一個連接,這樣顯然就比thrift支持的併發調用數量更高。
如果是同機房調用,調用一次的延時是很低的,這個時候grpc的優勢並不能體現,但如果服務請求者和服務提供者在不同機房,一次通信的延時可能很大,這個時候用grpc就是一個更好的選擇。
grpc的官方文檔有一句是,grpc在移動設備上表現更好,其實也就是上面說的這個道理。