日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

RPC 笔记(03)— gRPC 概念、安装、编译、客户端和服务端示例

發布時間:2023/11/28 生活经验 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 RPC 笔记(03)— gRPC 概念、安装、编译、客户端和服务端示例 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. gRPC 概念

gRPCGoogle 開源的一款高性能的 RPC 框架。GitHub 上介紹如下:

gRPC is a modern, open source, high-performance remote procedure call (RPC) framework that can run anywhere.

市面上的 RPC 框架數不勝數,包括 Alibaba Dubbo 和微博的 Motan 等。gRPC 能夠在眾多的框架中脫穎而出,是跟其高性能是密切相關的。
?

1.1 接口設計

對一個遠程服務 Service 的調用,gRPC 約定 ClientServer 首先需要約定好 Service 的結構。包括一系列方法的組合,每個方法定義、參數、返回體等。對這個結構的描述,gRPC 默認是用 Protocol Buffer 去實現的。

1.2 Streaming

StreamingHTTP/1.x 已經出現了,HTTP2 實現了 Streaming 的多路復用。gRPC 是基于 HTTP2 實現的。所以 gRPC 也實現了 Streaming 的多路復用,所以 gRPC 的請求有四種模式:

  • Simple RPC
  • Client-side Streaming RPC
  • Server-side Streaming RPC
  • Bidirectional Streaming RPC

也就是說同時支持單邊流和雙向流。

1.3 Protocol

gRPC 的協議層是基于 HTTP2 設計的,所以你如果想了解 gRPC 的話,可以先深入了解 HTTP2

1.4 Flow Control

gRPC 的協議支持流量控制,這里也是采用了 HTTP2Flow Control 機制。
通過上面的介紹可以看到,gRPC 的高性能很大程度上依賴了 HTTP2 的能力,所以要了解 gRPC 之前,我們需要先了解一下 HTTP 2 的特性。
?

1.5 HTTP2 特性

  1. 二進制協議

眾所周知,二進制協議比文本形式的協議,發送的數據量肯定是更小,傳輸效率更高的。所以 HTTP2HTTP/1.x 更高效,因為二進制是不可讀的,所以會損失部分可讀性。

  1. 多路復用的流

HTTP/1.x 一個 Stream 是需要一個 TCP 連接的,其實從性能上來說是比較浪費的。HTTP2 可以復用 TCP 連接,實現一個 TCP 連接可以處理多個 Stream,同時可以為每一個 Stream 設置優先級,可以用來告訴對端哪個流更重要。當資源有限的時候,服務器會根據優先級來選擇應該先發送哪些流。

  1. 頭部壓縮

由于 HTTP 協議是一個無狀態的協議,導致了很多相同或者類似的 HTTP 請求重復發送時,帶的頭部信息很多時候是完全一樣的。HTTP2 對頭部進行壓縮,可以減少數據包大小,提高協議性能。

  1. 請求 Reset

HTTP/1.x 中,當一個含有確切值的 Content-LengthHTTP 消息發出之后,需要斷開 TCP 連接才能中斷。這樣會導致需要通過三次握手來重新建立一個新的 TCP 連接,代價是比較大的。在 HTTP2 里面,我們可以通過發送 RST_STREAM 幀來終止當前消息發送,從而避免了浪費帶寬和中斷已有的連接。

  1. 服務器推送

如果一個 Client 請求資源 A,而 Server 知道 Client 可能也會需要資源 B, 所以在 Client 發起請求前,Server 提前將 B 推送給 A 緩存起來,從而可以縮短資源 A 這個請求的響應時間。

  1. Flow Control

HTTP2 中,每個 HTTP Stream 都有自己公示的流量窗口,對于每個 Stream 來說,ClientServer 都必須互相告訴對方自己能夠處理的窗口大小,Stream 中的數據幀大小不得超過能處理的窗口值。
?

2. Protobuf 文件編寫

2.1 安裝 protoc 編譯器

Proto 協議文件轉換為多種語言對應格式的工具,根據對應平臺選擇對應的安裝包,安裝包下載地址https://github.com/protocolbuffers/protobuf/releases

cd ~/tmp
# 下載
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
# 解壓后得到 bin 目錄下的 protoc
unzip protoc-3.11.4-linux-x86_64.zip
# 創建存放 protoc 目錄
sudo mkdir /usr/local/protobuf
# 復制 protoc 到剛剛創建的目錄下
sudo cp bin/protoc /usr/local/protobuf/

添加 protoc 環境變量

vim /etc/profile
# 在文件末尾修改
PATH=$PATH:/usr/local/php/bin:/usr/local/protobuf
# 使其修改生效
source /etc/profile

查看是否安裝成功,

protoc  --versionlibprotoc 3.11.4

2.2 編寫 Protocol Buffers 文件

無論使用何種語言創建客戶端和服務端,都依照相同的 Proto 文件定義的協議接口和數據格式,客戶端和服務器端都會使用由服務定義產生的接口代碼。
?
user.proto 文件

syntax = "proto3";    // 指定proto版本
package user_proto;        // 指定包名// 定義 User 服務
service User {// 定義 GetUser 方法 - 獲取某個 user 數據rpc GetUser(GetUserRequest) returns (GetUserResponse) {}// 定義 GetUserList 方法 - 獲取 user 所有數據rpc GetUserList(GetUserListRequest) returns (UserListResponse) {}
}// 枚舉類型第一個字段必須為 0
enum UserSex {MEN   = 0;WOMEN = 1;
}// GetUser 請求結構
message GetUserRequest {int64 userid = 1;
}// GetUser 響應結構
message GetUserResponse {int64   userid   = 1;string  username = 2;UserSex sex      = 3;
}// GetUserList 請求結構
message GetUserListRequest {}// 響應結構
message UserListResponse {// repeated 重復(數組)repeated GetUserResponse list = 1;
}

2.3 Protobuf 文件生成于 Go 文件

要讓 user.proto 生成 Go 文件,需要 protoc-gen-go 所以要下載:

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

bin 目錄下會生成一個 protoc-gen-go 可執行文件,就是用于生成 Go 文件的。
?

3. 編寫 Go gRPC 服務端流程

首先創建一個為 go-grpc 的項目:

mkdir ~/go-grpc 

設置 Go 模塊代理,因為我們要使用 Go modules 第三方包的依賴管理工具,當然了你的 Go 環境最好是 1.13 以上。

go env -w GOPROXY=https://goproxy.io,direct

3.1 初始化這個項目

我們使用 Go modules 來初始化(創建)這個項目,畢竟是以后的主流了

cd ~/go-grpc 
go mod init go-grpc 

下載項目所使用的包,它們之間的依賴由 Go modules 幫我們完成了,記住一定要在項目下打開命令行下執行:

go get github.com/golang/protobuf 
go get google.golang.org/grpc 

創建 user_proto 目錄,將剛剛編寫的 user.proto 放進來:

go-grpc
├── go.mod
├── go.sum
└── user_proto└── user.proto

生成 Go 文件,這里用了 plugins 選項,提供對 gRPC 的支持,否則不會生成 Service 的接口,方便編寫服務器和客戶端程序:

cd ~/go-grpc/user_proto 
protoc --go_out=plugins=grpc:. user.proto

根據編譯指令,編譯成對應語言的代碼文件:

protoc -I=$SRC_DIR --xxx_out=$DST_DIR $SRC_DIR/xxx.proto 
  • $SRC_DIR:存放協議源文件的目錄地址;
  • $DST_DIR:輸出代碼文件的目錄地址;
  • xxx.proto:協議源文件名稱;
  • –xxx_out:根據自己的需要,選擇對應的語言,例如(Java:–java_out,C++:–cpp_out等);
  • 可通過在命令提示符中輸入 protoc --help 查看更多幫助。

查看目錄:

go-grpc
├── go.mod
├── go.sum
└── user_proto├── user.pb.go└── user.proto

3.2 創建服務端

UserServer 服務工作有兩個部分:

  1. 實現我們服務定義的生成服務接口,做我們服務的實際工作
  2. 運行一個 gRPC 服務器,監聽來自客戶端的請求并返回服務的響應
mkdir ~/go-grpc/server
cd ~/go-grpc/server

我們首先實現 user.pb.go 中的 UserServer,即我們服務的實際工作接口:

// UserServer is the server API for User service.
type UserServer interface {// 定義 GetUser 方法GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)// 定義 GetUserList 方法GetUserList(context.Context, *GetUserListRequest) (*UserListResponse, error)
}

創建 user.go 來實現 UserServer 接口,即我們實際的工作服務實現:

package main
import ("context""strconv"// 引入 proto 編譯生成的包pb "go-grpc/user_proto"
)// 定義 User 并實現約定的接口
type User struct {UserId   int64  `json:"user_id"`UserName string `json:"user_name"`
}// 獲取某個 user 數據
func (this *User) GetUser(ctx context.Context, ut *pb.GetUserRequest) (*pb.GetUserResponse, error) {// 待返回數據結構resp := new(pb.GetUserResponse)resp.Userid = ut.Useridresp.Username = "laixhe"resp.Sex = pb.UserSex_MENreturn resp, nil
}// 獲取 user 所有數據
func (this *User) GetUserList(ctx context.Context, ut *pb.GetUserListRequest) (*pb.UserListResponse, error) {list := make([]*pb.GetUserResponse, 0, 3)for i := 1; i <= 3; i++ {list = append(list, &pb.GetUserResponse{Userid: int64(i), Username: "laiki"+strconv.Itoa(i), Sex: pb.UserSex_MEN})}// 待返回數據結構resp := new(pb.UserListResponse)resp.List = listreturn resp, nil
}

現在我們開始編寫對外服務 main.go,以便客戶端可以實際使用我們的服務:

  1. 創建監聽 listener
  2. 創建 gRPC 的服務
  3. 將我們的服務注冊到 gRPCServer
  4. 啟動 gRPC 服務,將我們自定義的監聽信息傳遞給 gRPC 客戶端
package main
import ("log""net"// 引入 proto 編譯生成的包pb "go-grpc/user_proto""google.golang.org/grpc"
)func main() {// 監聽地址和端口listen, err := net.Listen("tcp", ":50051")if err != nil {log.Fatalf("監聽端口失敗: %v", err)}// 實例化 grpc ServerserverGrpc := grpc.NewServer()// 注冊 User servicepb.RegisterUserServer(serverGrpc, &User{})log.Println("開始監聽 Grpc 端口 0.0.0.0:50051")// 啟動服務err = serverGrpc.Serve(listen)if err != nil {log.Println("啟動 Grpc 服務失敗")}
}

查看目錄:

go-grpc
├── go.mod
├── go.sum
├── server
│   ├── main.go
│   └── user.go
└── user_proto├── user.pb.go└── user.proto

我們回顧下:

  1. 首先要實現 UserServer 接口
  2. 創建 gRPC Server 對外端口
  3. 注冊我們實現的 UserServer 接口的實例
  4. 最后調用 Serve() 啟動我們的服務

4. 編寫 Go gRPC 客戶端流程

首先創建我們所需的目錄:

mkdir ~/go-grpc/client
cd ~/go-grpc/client

4.1 初始化客戶端

首先在連接我們建立好的服務端的 IP 和端口 main.go,通過把服務器地址和端口號傳遞給 grpc.Dial() 來創建通道:

package main
import ("log""net/http"// 引入 proto 編譯生成的包pb "go-grpc/user_proto""google.golang.org/grpc"
)const (// Address gRPC 服務地址Address = "127.0.0.1:50051"
)var UClient pb.UserClient// 初始化 Grpc 客戶端
func initGrpc() {// 連接 GRPC 服務端conn, err := grpc.Dial(Address, grpc.WithInsecure())if err != nil {log.Fatalln(err)}// 初始化 User 客戶端UClient = pb.NewUserClient(conn)log.Println("初始化 Grpc 客戶端成功")
}// 啟動 http 服務
func main() {initGrpc()http.HandleFunc("/user/get", GetUser)http.HandleFunc("/user/list", GetUserList)log.Println("開始監聽 http 端口 0.0.0.0:8080")err := http.ListenAndServe(":8080", nil)if err != nil {log.Printf("http.ListenAndServe err:%v", err)}
}

對外 HTTP 的兩接口的實現 user.go

  1. 創建 gRPC 連接器
  2. 創建 gRPC 客戶端,并將連接器賦值給客戶端
  3. gRPC 服務端發起請求
  4. 獲取 gRPC 服務端返回的結果
package main
import ("context""encoding/json""net/http""strconv"// 引入 proto 編譯生成的包pb "go-grpc/user_proto"
)func GetUser(w http.ResponseWriter, r *http.Request) {// 獲取 GET 的參數userid := r.FormValue("userid")id, err := strconv.ParseInt(userid, 10, 0)if err != nil {w.Write([]byte("userid The parameters must be integers"))return}// 調用 Grpc 的遠程接口data, err := UClient.GetUser(context.Background(), &pb.GetUserRequest{Userid: id})if err != nil {w.Write([]byte("Grpc: " + err.Error()))return}// json 格式化js, _ := json.Marshal(data)w.Write(js)
}func GetUserList(w http.ResponseWriter, r *http.Request) {// 調用 Grpc 的遠程接口data, err := UClient.GetUserList(context.Background(), &pb.GetUserListRequest{})if err != nil {w.Write([]byte("Grpc: " + err.Error()))return}// json 格式化js, _ := json.Marshal(data.List)w.Write(js)
}

我們回顧下:

  1. 通于 grpc.Dial() 連接服務端
  2. 持有某個 gRPC 的服務連接 NewUserClient

總結

以上是生活随笔為你收集整理的RPC 笔记(03)— gRPC 概念、安装、编译、客户端和服务端示例的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。