GO gRPC

什麼是grpc

詳細文檔:

https://doc.oschina.net/grpc?t=58008

  • gRpc 是一個高性能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
  • gRPC 基於 HTTP/2 標準設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多複用請求等特。這些特性使得其在移動設備上表現更好,更省電和節省空間佔用。
  • gRPC 默認使用 protocol buffers
  • 參考:grpc官方文檔英文版grpc官方文檔中文版
  • github : https://github.com/grpc/grpc-go

爲什麼我們要用grpc

  • 生態好:背靠Google。還有比如nginx也對grpc提供了支持,參考鏈接
  • 跨語言:跨語言,且自動生成sdk
  • 性能高:比如protobuf性能高過json, 比如http2.0性能高過http1.1
  • 強類型:編譯器就給你解決了很大一部分問題
  • 流式處理(基於http2.0):支持客戶端流式,服務端流式,雙向流式

grpc 的優點是怎麼實現的

grpc性能高:protobuf爲什麼比json性能高?

什麼是protobuf?

  • Protobuf是由Google開發的二進制格式,用於在不同服務之間序列化數據。是一種IDL(interface description language)語言

他比json快多少?

爲什麼protobuf比json快?

  • protobuf的二進制數據流如下圖

image-20230206152359575

json格式

{"content":"test","user":"test","user_id":"test"}
  • 對比json數據和protobuf數據格式可以知道
  • 體積小-無需分隔符:TLV存儲方式不需要分隔符(逗號,雙引號等)就能分隔字段,減少了分隔符的使用
  • 體積小-空字段省略:若字段沒有被設置字段值,那麼該字段序列化時的數據是完全不存在的,即不需要進行編碼,而json會傳key和空值的value
  • 體積小-tag二進制表示:是用字段的數字值然後轉換成二進制進行表示的,比json的key用字符串表示更加省空間
  • 編解碼快:tag的裏面存儲了字段的類型,可以直接知道value的長度,或者當value是字符串的時候,則用length存儲了長度,可以直接從length後取n個字節就是value的值,而如果不知道value的長度,我們就必須要做字符串匹配
  • 細化了解protobuf的編碼可以去看:varint 和 zigzag編碼方式

grpc性能高:http2.0爲什麼比http1.1性能高?

多路複用

  • http2.0和http 1.* 還有 http1.1pipling的對比
  • 示意圖

image-20230206152006833

  • http/1.* :一次請求,一個響應,建立一個連接用完關閉,每一個請求都要建立一個連接

  • http1.1 pipeling: Pipeling解決方式爲,若干個請求排隊串行化單線程處理,後面的請求等待前面請求的返回才能獲得執行機會,一旦有某請求超時等,後續請求只能被阻塞,毫無辦法,也就是人們常說的線頭阻塞

  • http2: 多個請求可同時在一個連接上並行執行。某個請求任務耗時嚴重,不會影響到其它連接的正常執行

  • grpc 多路複用還有哪些優點

    • 減少了tcp的連接,降低了服務端和客戶端對於內存,cpu等的壓力
    • 減少了tcp的連接,保證了不頻繁觸發tcp重新建立,這樣就不會頻繁有慢啓動
    • 減少了tcp的連接,使網絡擁塞情況得以改善
  • 爲什麼http/1.1不能實現多路複用而http2.0可以?

    • 因爲http/1.1傳輸是用的文本,而http2.0用的是二進制分幀傳輸

2. 頭部壓縮

  • 固定字段壓縮:http可以通過http對body進行gzip壓縮,這樣可以節省帶寬,但是報文中header也有很多字段沒有進行壓縮,比如cookie, user agent accept,這些有必要進行壓縮

  • 避免重複:大量請求和響應的報文裏面又很多字段值是重複的,所以有必要避免重複性

  • 編碼改進:字段是ascii編碼,效率低,改成二進制編碼可以提高

  • 以上通過HPACK算法來進行實現,算法主要包含三個部分

    • 靜態字典:將常用的header字段整成字典,比如{":method":"GET"} 就可以用單個數字 2來表示
    • 動態字典:沒有在靜態字典裏面的一些頭部字段,則用動態字典
    • Huffman 編碼: 壓縮編碼

3. 二進制分幀

  • 在二進制分幀層上,HTTP 2.0 會將所有傳輸的信息分割爲更小的消息和幀,並對它們採用二進制格式的編碼 ,其中HTTP1.x的首部信息會被封裝到Headers幀,而我們的request body則封裝到Data幀裏面。

  • 這樣分幀以後這些幀就可以亂序發送,然後根據每個幀首部的流標識符號進行組裝

  • 對比http/1.1因爲是基於文本以換行符分割每一條key:value則會有以下問題:

    • 一次只能處理一個請求或者響應,因爲這種以分隔符分割消息的數據,在完成之前不能停止解析
    • 解析這種數據無法預知需要多少內存,會給服務端有很大壓力

4,. 服務器主動推送資源

  • 由於支持服務器主動推送資源,則可以省去一部分請求。比如你需要兩個文件1.html,1.css,如果是http1.0則需要請求兩次,服務端返回兩次。但是http2.0則可以客戶端請求一次,然後服務端直接回吐兩次

protobuf 使用

上面我們也說protobuf 的各種優勢,但其實他在我們實際應用到底怎麼用呢?

微服務架構中,由於每個服務對應的代碼庫是獨立運行的,無法直接調用,彼此間的通信就是個大問題.

gRPC可以實現將大的項目拆分爲多個小且獨立的業務模塊,也就是服務。各服務間使用高效的protobuf協議進行RPC調用,gRPC默認使用protocol buffers,這是google開源的一套成熟的結構數據序列化機制

可以用proto files創建gRPC服務,用message類型來定義方法參數和返回類型,相當於提前定義好接口,並且這個接口一但更改,客戶端和服務端都要改

當然也可以使用其他數據格式如JSON,甚至不用gRpc 直接用http 也可以,之所以用gRpc和protobuf是因爲他的這些優點
當然如果服務不是特別多,直接http 反而更爲方便

protoc 是什麼

protoc是 protocol compiler,是protocol 編譯器。
當我們從 https://github.com/protocolbu...官網安裝protobuf後,在命令行,就會有一個 protoc shell腳本命令。
目前 protoc 編譯器支持的語言有以下幾種。
image.png
我們使用protoc 命令行可以把.proto 文件轉譯成各種編程語言對應的源碼,包含數據類型定義、調用接口等。

protoc 命令把.proto文件轉成go的過程有兩步

  1. 解析.proto文件,轉譯成protobuf的原生數據結構在內存中保存;
  2. 把protobuf相關的數據結構傳遞給相應語言的編譯插件,由插件負責根據接收到的protobuf原生結構渲染輸出特定語言的模板。(對應go的protoc-gen-go)

安裝

到github 地址,下載即可,這裏使用win64

https://github.com/protocolbuffers/protobuf/releases

image-20230206162904269

下載解壓後如下圖

image-20230206164557696

bin目錄添加到環境變量中,使其可以全局使用

image-20230206164841171

測試

隨便那個路徑開個CMD, 輸入

protoc

可以看到如下圖

image-20230206165327217

同時也有protoc編譯工具的各個選項含義

protoc-gen-go

在官方文檔裏,
https://pkg.go.dev/github.com...裏說明到
protoc-gen-go 是 Google protobuf編譯器生成 Go 代碼的插件。

因爲protoc編譯器,默認沒有包含go語言代碼生成器,所以需要單獨安裝插件

安裝後會在GOPATH目錄下生成可執行文件,protobuf的編譯器插件protoc-gen-go,等下執行protoc命令會自動調用這個插件

安裝

執行命令

go get -u github.com/golang/protobuf/protoc-gen-go

image-20230206163254279

會自動下載到,你的$GOPATH/pkg/mod裏,自動在go.mod文件中添加依賴

image-20230206163644273

proto 語法

詳細參考:

https://developers.google.cn/protocol-buffers/docs/proto3?hl=zh-cn

https://segmentfault.com/a/1190000007917576

https://developers.google.com/protocol-buffers/docs/gotutorial

hello World

寫一個最基本的hello world gRpc 服務,

創建項目的基本步驟

# 創建項目目錄
mkdir gRpc
# 切換到項目目錄
cd gRpc
# 創建RPC協議目錄
mkdir proto
# 初始化go模塊配置,用來管理第三方依賴, 本例子,項目模塊名是:gRpc
go mod init gRpc
# 添加依賴
go get -u github.com/golang/protobuf/protoc-gen-go

image-20230206171551903

image-20230206171630177

由於之前已經已經下載過,所有這個命令只會在go.mod文件中增加依賴

image-20230206171650247

此時的目錄結構

image-20230206171838480

定義服務接口

定義服務,其實就是通過protobuf語法定義語言平臺無關的接口。

文件:gRpc/proto/helloworld.proto

// 指定的當前proto語法的版本,有2和3,這裏用 3
// 這個必須在第一行
syntax = "proto3";

// 指定包名和目錄
// 這種不會 在 xxx.pb.go 文件插入包名,並且目錄是當前目錄不會創建文件夾
// 需要自己修改包名
// option go_package = "./";
// 這種會 在 xxx.pb.go 文件插入包名,並且會創建helloworld 文件夾
option go_package = "./gRpc";

// 定義Greeter服務
service Greeter {
  // 定義SayHello方法,接受HelloRequest消息, 並返回HelloResponse消息
  rpc SayHello (HelloRequest) returns (HelloResponse) {};
}

// 定義HelloRequest消息
message HelloRequest {
  // name字段
  string name = 1;
}

// 定義HelloResponse消息
message HelloResponse {
  // message字段
  string message = 1;
}

Greeter服務提供了一個SayHello接口,請求SayHello接口,

需要傳遞一個包含name字段的HelloRequest消息,

返回包含message字段的HelloResponse消息。

編譯proto協議文件生成 xxx.pb.go 文件

上面通過proto定義的接口,沒法直接在代碼中使用,因此需要通過protoc編譯器,將proto協議文件,編譯成go語言代碼。

# 切換到helloworld項目根目錄,執行命令
protoc -I proto/ --go_out=plugins=grpc:proto proto/helloworld.proto

如果出現問題:參考文章最後的解決辦法

protoc命令參數說明:

  • -I 指定代碼輸出目錄,忽略服務定義的包名,否則會根據包名創建目錄
  • --go_out 指定代碼輸出目錄,格式:--go_out=plugins=grpc:目錄名
  • 命令最後面的參數是proto協議文件

編譯成功後在proto目錄生成了/helloworld/helloworld.pb.go文件,裏面包含了,我們的服務和接口定義。

image-20230207102531949

添加依賴

可以看到上面有很多依賴錯誤,使用

go mod tidy

更新下依賴

image-20230207104052144

實現服務端代碼並啓動

文件: server.go

package main

import (
	"log"
	"net"

	"golang.org/x/net/context"
	// 導入grpc包
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"

	// 導入剛纔我們生成的代碼所在的proto包。
	pb "gRpc/proto/gRpc"
)

// 定義server,用來實現proto文件,裏面實現的Greeter服務裏面的接口
type server struct{}

// 實現SayHello接口
// 第一個參數是上下文參數,所有接口默認都要必填
// 第二個參數是我們定義的HelloRequest消息
// 返回值是我們定義的HelloResponse消息,error返回值也是必須的。
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
	// 創建一個HelloResponse消息,設置Message字段,然後直接返回。
	return &pb.HelloResponse{Message: "Hello " + in.Name}, nil
}

func main() {
	// 監聽127.0.0.1:50051地址
	lis, err := net.Listen("tcp", "127.0.0.1:50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	// 實例化grpc服務端
	s := grpc.NewServer()

	// 註冊Greeter服務
	pb.RegisterGreeterServer(s, &server{})

	// 往grpc服務端註冊反射服務
	reflection.Register(s)

	// 啓動grpc服務
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

啓動服務端

# 切換到項目根目錄,運行命令
go run server.go

實現客戶端代碼

文件:client.go

package main

import (
	"log"
	"time"

	"golang.org/x/net/context"
	// 導入grpc包
	"google.golang.org/grpc"
	// 導入剛纔我們生成的代碼所在的proto包。
	pb "gRpc/proto/gRpc"
)

const (
	defaultName = "makalo"
)

func main() {
	// 連接grpc服務器
	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	// 延遲關閉連接
	defer conn.Close()

	// 初始化Greeter服務客戶端
	c := pb.NewGreeterClient(conn)

	// 初始化上下文,設置請求超時時間爲1秒
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	// 延遲關閉請求會話
	defer cancel()

	// 調用SayHello接口,發送一條消息
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "makalo"})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}

	// 打印服務的返回的消息
	log.Printf("Greeting: %s", r.Message)
}

運行

# 切換到項目根目錄,運行命令
go run client.go

運行結果,至此我們已經完成 rpc 通信

image-20230207105813578

目錄結構

PS F:\go\src\gRpc> tree /F
文件夾 PATH 列表
卷序列號爲 8DFC-EB47
F:.
│  client.go			客戶端代碼
│  go.mod				go mod 文件
│  go.sum				go mod 依賴關係文件
│  server.go			服務端代碼
│
└─proto
    │  helloworld.proto	rpc協議文件
    │
    └─gRpc
            helloworld.pb.go	rpc協議go版本的代碼

中間出現的錯誤

'protoc-gen-go' 不是內部或外部命令,也不是可運行的程序

protoc -I proto/ --go_out=plugins=grpc:proto proto/helloworld.proto

image-20230206174127446

去看看你的 $GOPATH/bin有沒有 protoc-gen-go.exe,文件如果沒有,就說雖然它下載了代碼庫但是,並沒有編譯

我們上面添加依賴的鏈接是

github.com/golang/protobuf/protoc-gen-go

我們到 $GOPATH/pkg/mod,按照上面目錄依次往下找即可,找到源碼庫

image-20230206174728089

找到之後進入這代碼庫,重新編譯生成可執行文件名稱爲protoc-gen-go.exe

go build -o protoc-gen-go.exe main.go

image-20230206175036936

將這個可執行文件剪切到$GOPATH/bin(其他目錄也可以,只要能全局調用,只不過,這個目錄我們一開始最開始搭環境的時候就配置了環境變量)

image-20230206175323711

vscode 中無法識別 protoc 命令

image-20230206175750727

重啓vscode 終端

protoc-gen-go: unable to determine Go import path for "helloworld.proto"

protoc-gen-go: unable to determine Go import path for "helloworld.proto"

Please specify either:
        • a "go_package" option in the .proto source file, or
        • a "M" argument on the command line.

See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

--go_out: protoc-gen-go: Plugin failed with status code 1.

image-20230207095733584

需要指定 go_package 參數

option go_package = "./helloworld";

image-20230207101805307

參考:

https://developers.google.cn/protocol-buffers/docs/gotutorial?hl=zh-cn

https://blog.csdn.net/m0_61472414/article/details/127445617

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章