grpc - 使用 golang 帶你從頭擼一套 RPC 服務(一)

gRPC 是一個高性能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 鏈接上的多複用請求等特。這些特性使得其在移動設備上表現更好,更省電和節省空間佔用。php

安裝 protobuf

grpc使用protobuf做爲IDL(interface descriton language),且要求protobuf 3.0以上,這裏咱們直接選用當前最新版本 3.8,git下載地址java

選擇操做系統對應的版本下載,這裏咱們直接使用已經編譯好的protoc可執行文件(或者下載安裝包編譯安裝)。python

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

安裝 protoc-gen-go

protoc-gen-goGoprotoc編譯插件,protobuf內置了許多高級語言的編譯器,但沒有Go的。linux

# 運行 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編譯生成Go版的grpc時,須要先安裝此插件。git

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

安裝 grpc-go 庫

grpc-go包含了Gogrpc庫。github

go get google.golang.org/grpc 可能會被牆掉了,使用以下方式手動安裝。golang

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
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto

cd $GOPATH/src/
go install google.golang.org/grpc

Grpc的四種模式

grpc藉口的類型分爲一下四種: A爲接受參數,B爲返回參數數據庫

  1. rpc GetFeature(Point) returns (Feature) {} 普通調用:A-B
  2. rpc ListFeatures(Rectangle) returns (stream Feature) {} 單向流:A - B(流)
  3. rpc RecordRoute(stream Point) returns (RouteSummary) {} 單向流:A(流) - B
  4. rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 雙向流:A(流) - B(流)

咱們只演示最基礎的普通調用,開始擼。編程

Go實例

下面咱們將建立一個提供CURD操做的User服務。segmentfault

#建立一個 grpc 服務庫
mkdir $GOPATH/src/grpc/user -p && cd $GOPATH/src/grpc

定義服務接口

使用proto3 IDL 編寫以下的用戶服務,proto3語法文檔

proto2相比,proto3更爲簡潔,再也不須要使用required/optional來區分必須/可選參數,各參數根據自身數據類型自行設定默認值。

proto3的標量數據類型(repeated emun 複合數類型沒有在其中說起):

clipboard.png

vi user.proto

syntax = "proto3";
 
// user 包
package user;

// 指定 go 的包路徑及包名
// option go_package="app/services;services";
// 指定 php 的命名空間
// option php_namespace="App\\Services";

// User 服務及服務接口的定義
service User {
    rpc UserIndex(UserIndexRequest) returns (UserIndexResponse) {}
    rpc UserView(UserViewRequest) returns (UserViewResponse) {}
    rpc UserPost(UserPostRequest) returns (UserPostResponse) {}
    rpc UserDelete(UserDeleteRequest) returns (UserDeleteResponse) {}
}

// 枚舉類型
emun EnumUserSex {
    SEX_INIT = 0; // 枚舉類型必須以 0 起始
    SEX_MALE = 1;
    SEX_FEMALE = 2;
}

// 用戶實體模型
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;
}

生成接口庫

# 加載 protoc-gen-go 插件 生成 grpc 的 go 服務端/客戶端
# 注意要安裝好 protoc-gen-go 插件
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"
)

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服務端

相關文章
相關標籤/搜索