gRPC 是一個高性能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多複用請求等特。這些特性使得其在移動設備上表現更好,更省電和節省空間佔用。
安裝 protobuf & protoc
grpc
使用protobuf
作爲IDL
,且要求protobuf
3.0 以上,這裏我們直接選用當前最新版本 3.8,git下載地址。選擇操作系統對應的版本下載,這裏我們直接使用已經編譯好的protoc
可執行文件(或者下載安裝包編譯安裝)。
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0-rc1/protoc-3.8.0-rc-1-linux-x86_64.zip
tar -zxvf protoc-3.8.0-rc-1-linux-x86_64.zip
mv protoc-3.8.0-rc-1-linux-x86_64 /usr/local/protoc
ln -s /usr/local/protoc/bin/protoc /usr/local/bin/protoc
#查看 protoc
protoc --version
安裝 grpc-go
grpc-go
包含了golang
的grpc
庫,我們還要安裝golang
的protoc
編譯插件protoc-gen-go
(protoc
自帶的支持很多語言,唯獨沒有go
的)。
git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
cd $GOPATH/src/
go install google.golang.org/grpc
protoc-gen-go
是protobuf
編譯插件系列中的Go
版本,原生的protoc
並不包含Go
版本的插件。
# 運行 protoc -h 命令可以發現內置的只支持以下語言
protoc -h
...
--cpp_out=OUT_DIR Generate C++ header and source.
--csharp_out=OUT_DIR Generate C# source file.
--java_out=OUT_DIR Generate Java source file.
--js_out=OUT_DIR Generate JavaScript source.
--objc_out=OUT_DIR Generate Objective C header and source.
--php_out=OUT_DIR Generate PHP source file.
--python_out=OUT_DIR Generate Python source file.
--ruby_out=OUT_DIR Generate Ruby source file.
...
protoc-gen-go
是Go
寫的,只需要運行go get -u github.com/golang/protobuf/protoc-gen-go
,便可以在$GOPATH/bin
目錄下發現這個工具。
grpc
藉口的類型分爲一下四種: A
爲接受參數,B
爲返回參數
- rpc GetFeature(Point) returns (Feature) {} 普通調用:A-B
- rpc ListFeatures(Rectangle) returns (stream Feature) {} 單向流:A - B(流)
- rpc RecordRoute(stream Point) returns (RouteSummary) {} 單向流:A(流) - B
- rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 雙向流:A(流) - B(流)
我們只演示最基礎的普通調用,開始擼。
Go實例
下面我們將創建一個提供CURD
操作的User
服務。
#創建一個 grpc 服務庫
mkdir $GOPATH/src/grpc/user -p && cd $GOPATH/src/grpc
定義服務接口
使用proto3 IDL
編寫如下的用戶服務。vi user.proto
syntax = "proto3";
// user 包
package user;
// User 服務及服務接口的定義
service User {
rpc UserIndex(UserIndexRequest) returns (UserIndexResponse) {}
rpc UserView(UserViewRequest) returns (UserViewResponse) {}
rpc UserPost(UserPostRequest) returns (UserPostResponse) {}
rpc UserDelete(UserDeleteRequest) returns (UserDeleteResponse) {}
}
// 用戶實體模型
message UserEntity {
string name = 1;
int32 age = 2;
}
// User 服務的各個接口的請求/響應結構
message UserIndexRequest {
int32 page = 1;
int32 page_size = 2;
}
message UserIndexResponse {
int32 err = 1;
string msg = 2;
// 返回一個 UserEntity 對象的列表數據
repeated UserEntity data = 3;
}
message UserViewRequest {
int32 uid = 1;
}
message UserViewResponse {
int32 err = 1;
string msg = 2;
// 返回一個 UserEntity 對象
UserEntity data = 3;
}
message UserPostRequest {
string name = 1;
string password = 2;
int32 age = 3;
}
message UserPostResponse {
int32 err = 1;
string msg = 2;
}
message UserDeleteRequest {
int32 uid = 1;
}
message UserDeleteResponse {
int32 err = 1;
string msg = 2;
}
生成接口庫
# 將 User 服務的所屬包 user package 存放在 $GOPATH/src/grpc/user 下
protoc -I. --go_out=plugins=grpc:./user user.proto
編寫服務端
vi server.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
user "grpc/user"
)
const (
port = ":50051"
)
type UserService struct {
// 實現 User 服務的業務對象
}
// UserService 實現了 User 服務接口中聲明的所有方法
func (userService *UserService) UserIndex(ctx context.Context, in *user.UserIndexRequest) (*user.UserIndexResponse, error) {
log.Printf("receive user index request: page %d page_size %d", in.Page, in.PageSize)
return &user.UserIndexResponse{
Err: 0,
Msg: "success",
Data: []*user.UserEntity{
{Name: "big_cat", Age: 28},
{Name: "sqrt_cat", Age: 29},
},
}, nil
}
func (userService *UserService) UserView(ctx context.Context, in *user.UserViewRequest) (*user.UserViewResponse, error) {
log.Printf("receive user view request: uid %d", in.Uid)
return &user.UserViewResponse{
Err: 0,
Msg: "success",
Data: &user.UserEntity{Name: "james", Age: 28},
}, nil
}
func (userService *UserService) UserPost(ctx context.Context, in *user.UserPostRequest) (*user.UserPostResponse, error) {
log.Printf("receive user post request: name %s password %s age %d", in.Name, in.Password, in.Age)
return &user.UserPostResponse{
Err: 0,
Msg: "success",
}, nil
}
func (userService *UserService) UserDelete(ctx context.Context, in *user.UserDeleteRequest) (*user.UserDeleteResponse, error) {
log.Printf("receive user delete request: uid %d", in.Uid)
return &user.UserDeleteResponse{
Err: 0,
Msg: "success",
}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 創建 RPC 服務容器
grpcServer := grpc.NewServer()
// 爲 User 服務註冊業務實現 將 User 服務綁定到 RPC 服務容器上
user.RegisterUserServer(grpcServer, &UserService{})
// 註冊反射服務 這個服務是CLI使用的 跟服務本身沒有關係
reflection.Register(grpcServer)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
編寫客戶端
vi client.go
package main
import (
"context"
"fmt"
"log"
"time"
"google.golang.org/grpc"
user "grpc/user"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
//建立鏈接
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
userClient := user.NewUserClient(conn)
// 設定請求超時時間 3s
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
// UserIndex 請求
userIndexReponse, err := userClient.UserIndex(ctx, &user.UserIndexRequest{Page: 1, PageSize: 12})
if err != nil {
log.Printf("user index could not greet: %v", err)
}
if 0 == userIndexReponse.Err {
log.Printf("user index success: %s", userIndexReponse.Msg)
// 包含 UserEntity 的數組列表
userEntityList := userIndexReponse.Data
for _, row := range userEntityList {
fmt.Println(row.Name, row.Age)
}
} else {
log.Printf("user index error: %d", userIndexReponse.Err)
}
// UserView 請求
userViewResponse, err := userClient.UserView(ctx, &user.UserViewRequest{Uid: 1})
if err != nil {
log.Printf("user view could not greet: %v", err)
}
if 0 == userViewResponse.Err {
log.Printf("user view success: %s", userViewResponse.Msg)
userEntity := userViewResponse.Data
fmt.Println(userEntity.Name, userEntity.Age)
} else {
log.Printf("user view error: %d", userViewResponse.Err)
}
// UserPost 請求
userPostReponse, err := userClient.UserPost(ctx, &user.UserPostRequest{Name: "big_cat", Password: "123456", Age: 29})
if err != nil {
log.Printf("user post could not greet: %v", err)
}
if 0 == userPostReponse.Err {
log.Printf("user post success: %s", userPostReponse.Msg)
} else {
log.Printf("user post error: %d", userPostReponse.Err)
}
// UserDelete 請求
userDeleteReponse, err := userClient.UserDelete(ctx, &user.UserDeleteRequest{Uid: 1})
if err != nil {
log.Printf("user delete could not greet: %v", err)
}
if 0 == userDeleteReponse.Err {
log.Printf("user delete success: %s", userDeleteReponse.Msg)
} else {
log.Printf("user delete error: %d", userDeleteReponse.Err)
}
}
啓動服務/請求服務
go run server.go
# 新建一個窗口
go run client.go
運行結果
#server
[root@localhost rpc]# go run server.go
2019/05/16 00:28:01 receive user index request: page 1 page_size 12
2019/05/16 00:28:01 receive user view request: uid 1
2019/05/16 00:28:01 receive user post request: name big_cat password 123456 age 29
2019/05/16 00:28:01 receive user delete request: uid 1
#client
[root@localhost rpc]# go run client.go
2019/05/16 00:28:01 user index success: success
big_cat 28
sqrt_cat 29
2019/05/16 00:28:01 user view success: success
james 28
2019/05/16 00:28:01 user post success: success
2019/05/16 00:28:01 user delete success: success
grpc
面向服務接口編程大致就是這樣啦,你可以更好的組織業務庫,我這裏全放在package main
裏了,後面的工作就是把數據庫拉進來了。
第二篇我們將以PHP
作爲客戶端請求Go
服務端。