使用gRPC從零開始構(gòu)建Go微服務(wù)

開發(fā)環(huán)境準(zhǔn)備

工欲善其事必先利其器,在構(gòu)建Go微服務(wù)前準(zhǔn)備好開發(fā)環(huán)境可以提供工作效率和工作的舒心度。

  • Go
    gRPC要求Go語言的版本不低于1.6,建議使用最新穩(wěn)定版本。如果沒有安裝Go或Go的版本低于1.6,參考Go安裝指南

    $ go version 
    
  • 安裝gRPC
    可以使用如下命令安裝gRCP:

    $ go get -u google.golang.org/grpc
    

    如果網(wǎng)絡(luò)質(zhì)量不佳,可以直接去GitHub下載gRPC源碼,將源碼拷貝到$GOPATH路徑下。

  • 安裝 Protocol Buffers v3
    安裝protoc編譯器的目的是生成服務(wù)代碼,從https://github.com/google/protobuf/releases下載已預(yù)編譯好的二進(jìn)制文件是安裝protoc最簡單的方法。
    1.1 解壓文件
    1.2 將二進(jìn)制文件所在的目錄添加到環(huán)境變量PATH中。

    安裝Go版本的protoc插件

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

    默認(rèn)編譯插件protoc-gen-to安裝在$GOPATH/bin目錄下,該目錄需要添加到環(huán)境變量PATH中。

定義服務(wù)

在本文中定義一個(gè)產(chǎn)品服務(wù)ProductService,服務(wù)提供兩個(gè)簡單的基本功能

  • 添加產(chǎn)品
  • 刪除產(chǎn)品
  • 根據(jù)產(chǎn)品Id查詢產(chǎn)品詳情
  • 查詢所有產(chǎn)品詳情
    ProductService.poto文件的具體內(nèi)容如下:
// protocol buffer 語法版本
syntax = "proto3";

// 產(chǎn)品服務(wù)定義
service ProductService {
    // 添加產(chǎn)品
    rpc AddProduct (AddProductRequest) returns (AddProductResponse) {
    }

    // 刪除產(chǎn)品
    rpc DeleteProduct (DeleteProductRequest) returns (EmptyResponse) {
    }

    // 根據(jù)產(chǎn)品Id查詢產(chǎn)品詳情
    rpc QueryProductInfo (QueryProductRequest) returns (ProductInfoResponse) {

    }

    // 查詢所有產(chǎn)品詳情
    rpc QueryProductsInfo (EmptyRequest) returns (ProductsInfoResponse) {

    }
}
// 請求/響應(yīng)結(jié)構(gòu)體定義
// 添加產(chǎn)品message
message AddProductRequest {
    enum Classfication {
        FRUIT = 0;
        MEAT = 1;
        STAPLE = 2;
        TOILETRIES = 3;
        DRESS = 4;
    }
    string productName = 1;
    Classfication classification = 2;
    string manufacturerId = 3;
    double weight = 4;
    int64 productionDate = 5;
}

// 添加產(chǎn)品,服務(wù)端響應(yīng)message
message AddProductResponse {
    string productId = 1;
    string message = 2;
}

// 刪除產(chǎn)品message
message DeleteProductRequest {
    string productId = 1;
}

message QueryProductRequest {
    string productId = 1;
}

// 單產(chǎn)品詳情message
message ProductInfoResponse {
    string productName = 1;
    string productId = 2;
    string manufacturerId = 3;
    double weight = 4;
    int64 productionDate = 5;
    int64 importDate = 6;
}

message ProductsInfoResponse {
    repeated ProductInfoResponse infos = 1;
}

message EmptyRequest {

}

message EmptyResponse {

}

一個(gè)方法不需要入?yún)⒒驔]有返回值時(shí),在gRPC中使用空的message代替,參考stackoverflow

生成客戶端和服務(wù)端代碼

服務(wù)定義文件ProductService.poto在工程中的路徑為:src/grpc/servicedef/product/ProductService.proto,進(jìn)入servicedef目錄,執(zhí)行以下命令生成Go版本的客戶端和服務(wù)端代碼:

   $ protoc -I product/ ProductService.proto --go_out=plugins=grpc:product

命令執(zhí)行完成后,會(huì)在product目錄下生成一個(gè)名為ProductService.pb.go的文件,文件的內(nèi)容為Go版本的客戶端和服務(wù)端代碼。

服務(wù)端實(shí)現(xiàn)

服務(wù)端需要完成兩項(xiàng)工作才能對外提供RPC服務(wù):

  • 實(shí)現(xiàn)ProductServiceServer接口,ProductServiceServer接口是protoc編譯器自動(dòng)生成。在Go某個(gè)對象實(shí)現(xiàn)一個(gè)接口,只需要實(shí)現(xiàn)該接口的所有方法。
  • 啟動(dòng)gRPC Server用來處理客戶端請求。
    服務(wù)端具體實(shí)現(xiàn)代碼
package main

import (
    "log"
    "net"
    "time"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    pb "grpc/servicedef/product"
    "math/rand"
    "strconv"
)

const (
    port = ":5230"
)

var dataBase = make(map[string]*Product, 10)

type Product struct {
    ProductName    string
    ProductId      string
    ManufacturerId string
    Weight         float64
    ProductionDate int64
    ImportDate     int64
}

type server struct{}

func (s *server) AddProduct(ctx context.Context, request *pb.AddProductRequest) (*pb.AddProductResponse, error) {
    log.Printf("get request from client to add product,request is %s", request)
    productId := strconv.FormatInt(rand.Int63(), 10)
    product :=new (Product)
    product.ProductName = request.ProductName
    product.ProductId = productId
    product.ManufacturerId = request.ManufacturerId
    product.Weight = request.Weight
    product.ProductionDate = request.ProductionDate
    product.ImportDate = time.Now().UnixNano()
    dataBase[productId] = product
    return &pb.AddProductResponse{ProductId: productId, Message: "Add product success"}, nil
}

func (s *server) DeleteProduct(ctx context.Context, request *pb.DeleteProductRequest) (*pb.EmptyResponse, error) {
    log.Printf("get request from client to add product,request is %s", request)
    productId := request.ProductId
    delete(dataBase, productId)
    return nil, nil
}

func (s *server) QueryProductInfo(ctx context.Context, request *pb.QueryProductRequest) (*pb.ProductInfoResponse, error) {
    log.Printf("get request from client fro query product info,%v", request)
    productId := request.ProductId
    product := dataBase[productId]
    response:=new(pb.ProductInfoResponse)
    response.ProductName = product.ProductName
    response.ProductId = product.ProductId
    response.ManufacturerId = product.ManufacturerId
    response.Weight = product.Weight
    response.ProductionDate = product.ProductionDate
    response.ImportDate = product.ImportDate
    return response, nil
}

func (s *server) QueryProductsInfo(ctx context.Context, request *pb.EmptyRequest) (*pb.ProductsInfoResponse, error) {
    // 待實(shí)現(xiàn)
    return nil, nil
}

func main() {
    log.Printf("begin to start rpc server")
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterProductServiceServer(s, &server{})
    // Register reflection service on gRPC server.
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

客戶端實(shí)現(xiàn)

客戶端非常的簡單,就像gRPC介紹中一樣,可以像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程gRPC服務(wù),一個(gè)詳細(xì)的例子如下:

package main

import (
    "log"
    "time"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "grpc/servicedef/product"
)

const (
    address = "localhost:5230"
)

func main()  {
    // 建立一個(gè)與服務(wù)端的連接.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    client := pb.NewProductServiceClient(conn)
    
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)

    response,err := client.AddProduct(ctx,&pb.AddProductRequest{ProductName:"phone"})
    if nil != err {
        log.Fatalf("add product failed, %v",err)
    }
    log.Printf("add product success,%s",response)
    productId:=response.ProductId
    queryResp,err :=client.QueryProductInfo(ctx,&pb.QueryProductRequest{ProductId: productId})
    if nil !=err {
        log.Fatalf("query product info failed,%v",err)
    }
    log.Printf("Product info is %v",queryResp)

    defer cancel()
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • (一) 我剛做班主任的時(shí)候,接待過的第一對家長,讓我至今難忘。 那是一個(gè)剛放了學(xué)的傍晚,就只見到一對約摸三十左右的...
    江左梅娘閱讀 318評論 0 0
  • 不知從何時(shí)起,我開始變得很焦慮,情緒時(shí)起時(shí)伏,我很討厭這樣的自己,很討厭。 我總是不太敢面對自己,去想自己是個(gè)什么...
    走走婷婷閱讀 468評論 2 1
  • 1 “氣死我了,我們單位新來的小張,天天在群里說讓我請他吃飯,我跟他很熟嗎,還是我要找他辦事啊,憑啥要請他吃飯?....
    丁小喵治愈說閱讀 1,505評論 0 1
  • 10月7日晚觀影 在全場不絕于耳的歡笑聲里,在艾迪生將沖上臺(tái)的馬小擁入懷中那刻,我眼底驀地泛起潮濕。 是啊,從起初...
    左清晨閱讀 347評論 0 0

友情鏈接更多精彩內(nèi)容