使用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.poto,进入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()
}