使用gRPC从零开始构建Go微服务

开发环境准备

工欲善其事必先利其器,在构建Go微服务前准备好开发环境可以提供工作效率和工作的舒心度。

  • Go
    gRPC要求Go语言的版本不低于1.6,建议使用最新稳定版本。如果没有安装Go或Go的版本低于1.6,参考Go安装指南

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

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

    如果网络质量不佳,可以直接去GitHub下载gRPC源码,将源码拷贝到$GOPATH路径下。

  • 安装 Protocol Buffers v3
    安装protoc编译器的目的是生成服务代码,从https://github.com/google/protobuf/releases下载已预编译好的二进制文件是安装protoc最简单的方法。
    1.1 解压文件
    1.2 将二进制文件所在的目录添加到环境变量PATH中。

    安装Go版本的protoc插件

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

    默认编译插件protoc-gen-to安装在$GOPATH/bin目录下,该目录需要添加到环境变量PATH中。

定义服务

在本文中定义一个产品服务ProductService,服务提供两个简单的基本功能

  • 添加产品
  • 删除产品
  • 根据产品Id查询产品详情
  • 查询所有产品详情
    ProductService.poto文件的具体内容如下:
// protocol buffer 语法版本
syntax = "proto3";

// 产品服务定义
service ProductService {
    // 添加产品
    rpc AddProduct (AddProductRequest) returns (AddProductResponse) {
    }

    // 删除产品
    rpc DeleteProduct (DeleteProductRequest) returns (EmptyResponse) {
    }

    // 根据产品Id查询产品详情
    rpc QueryProductInfo (QueryProductRequest) returns (ProductInfoResponse) {

    }

    // 查询所有产品详情
    rpc QueryProductsInfo (EmptyRequest) returns (ProductsInfoResponse) {

    }
}
// 请求/响应结构体定义
// 添加产品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;
}

// 添加产品,服务端响应message
message AddProductResponse {
    string productId = 1;
    string message = 2;
}

// 删除产品message
message DeleteProductRequest {
    string productId = 1;
}

message QueryProductRequest {
    string productId = 1;
}

// 单产品详情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 {

}

一个方法不需要入参或没有返回值时,在gRPC中使用空的message代替,参考stackoverflow

生成客户端和服务端代码

服务定义文件ProductService.poto在工程中的路径为:src/grpc/servicedef/product/ProductService.proto,进入servicedef目录,执行以下命令生成Go版本的客户端和服务端代码:

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

命令执行完成后,会在product目录下生成一个名为ProductService.pb.go的文件,文件的内容为Go版本的客户端和服务端代码。

服务端实现

服务端需要完成两项工作才能对外提供RPC服务:

  • 实现ProductServiceServer接口,ProductServiceServer接口是protoc编译器自动生成。在Go某个对象实现一个接口,只需要实现该接口的所有方法。
  • 启动gRPC Server用来处理客户端请求。
    服务端具体实现代码
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) {
    // 待实现
    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)
    }
}

客户端实现

客户端非常的简单,就像gRPC介绍中一样,可以像调用本地方法一样调用远程gRPC服务,一个详细的例子如下:

package main

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

const (
    address = "localhost:5230"
)

func main()  {
    // 建立一个与服务端的连接.
    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()
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容

  • (一) 我刚做班主任的时候,接待过的第一对家长,让我至今难忘。 那是一个刚放了学的傍晚,就只见到一对约摸三十左右的...
    江左梅娘阅读 285评论 0 0
  • 不知从何时起,我开始变得很焦虑,情绪时起时伏,我很讨厌这样的自己,很讨厌。 我总是不太敢面对自己,去想自己是个什么...
    走走婷婷阅读 438评论 2 1
  • 1 “气死我了,我们单位新来的小张,天天在群里说让我请他吃饭,我跟他很熟吗,还是我要找他办事啊,凭啥要请他吃饭?....
    丁小喵治愈说阅读 1,314评论 0 1
  • 10月7日晚观影 在全场不绝于耳的欢笑声里,在艾迪生将冲上台的马小拥入怀中那刻,我眼底蓦地泛起潮湿。 是啊,从起初...
    左清晨阅读 288评论 0 0