gRPC 入门(一)

前言

在学习 gRPC 之前,先学习 protobufu 协议,简单的来理解,我们可以使用他来定义 消息服务。然后你只需要实现服务即可,剩下的东西,gRPC 会帮你自动完成。

protobufu 协议

protobuf 协议,可以适用于十几种开发语言,并且允许你使用同一种框架,每秒支持百万级以上的 RPC 调用

mac 中安装 gRPC 需要的环境

方法和 linux 中安装 gRPC 基本一样

cd ~/software/protobuf            #创建一个 software 文件,当时你也可以按照自己的习惯,放到 usr 文件夹中。

wget https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protoc-3.12.0-osx-x86_64.zip   # 在下载 grpc 的安装包
unzip   protoc-3.12.0-osx-x86_64.zip    # 解压安装包

mv bin/protoc  /usr/local/protoc        #  通过将 bin 文件放到 protoc 中的方法,将 protoc 命令添加到 path 中

查看 protoc 的版本信息

protoc --version

# 输出 libprotoc 3.7.1

上面安装步骤完成以后,只是安装了 protobuf 的基础功能包。我们想要使用 go 语言中的 gprc 功能,还需要安装 grpc的包。

在任意的有mod 的文件下,执行如下命令

go get google.golang.org/grpc

如果想对此包做更多理解,可以查询 gPRC 的中文官方文档: http://doc.oschina.net/grpc?t=60133

一个小 demo

通过 grpc 协议,定义一个客户端,实现以下两个功能,并使用 grpc 的 client 端来调用

  1. 添加产品

  2. 根据产品 id 来查询产品信息

定义一个能添加产品和查询产品的 protobuf 文件

syntax = "proto3";

package ecommerce;

option go_package="./econFileName"; // 文件名和包名,此两个文件保持一致

service ProductInfo {
    rpc addProduct(Product) returns (ProductID);
    rpc getProduct(ProductID) returns (Product);
}

message Product {
    string id = 1;
    string name = 2;
    string description = 3;
    float price = 4;
}

message ProductID {
    string value = 1;
}

在声明 protobuf 的文件夹下,执行如下命令,会自动生成对应的 go 文件,切记,此文件只能查看,不可更改

protoc --go_out=plugins=grpc:. *.proto

服务端

1. 找到自动生成的 go 文件中 grpc 服务端的接口

打开自动生成的 go 文件,可以看到 server 接口。注意和客户端的接口区分,此处是 Server 结尾的
gRPC 入门(一)

2. 重载此接口

根据 go 语言非入侵的接口实现方式,声明一个结构体,只要实现了某接口的所有方法,那么他就实现了这个接口

type server struct {
	products []*pb.Product
}

func (s *server) AddProduct(ctx context.Context, product *pb.Product) (*pb.ProductID, error) {
	product.Id = uuid.New().String()
	s.products = append(s.products, product)
	return &pb.ProductID{
		Value: product.Id,
	}, status.New(codes.OK, "").Err()
}
func (s *server) GetProduct(ctx context.Context, proId *pb.ProductID) (*pb.Product, error) {
	for _, prod := range s.products {
		if prod.Id == proId.Value {
			return prod, status.New(codes.OK, "").Err()
		}
	}
	return nil, status.Errorf(codes.NotFound, "product not exist", proId.Value)
}

3.启动服务,监听 5001 端口

package main

import (
	"context"
	"fmt"
	"net"
	pb "zhao/grpc/pb/econFileName"

	"github.com/google/uuid"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const (
	port = ":5001"
)

type server struct {
	products []*pb.Product
}

func (s *server) AddProduct(ctx context.Context, product *pb.Product) (*pb.ProductID, error) {
	product.Id = uuid.New().String()
	s.products = append(s.products, product)
	return &pb.ProductID{
		Value: product.Id,
	}, status.New(codes.OK, "").Err()
}
func (s *server) GetProduct(ctx context.Context, proId *pb.ProductID) (*pb.Product, error) {
	for _, prod := range s.products {
		if prod.Id == proId.Value {
			return prod, status.New(codes.OK, "").Err()
		}
	}
	return nil, status.Errorf(codes.NotFound, "product not exist", proId.Value)
}

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		panic(err)
	}
	fmt.Printf("net service is starting at :%s\n", port)
	s := grpc.NewServer()
	pb.RegisterProductInfoServer(s, &server{})
	if err = s.Serve(lis); err != nil {
		panic(fmt.Sprintf("tpc web service port:【%s】 launch fail %v", port, err))
	}
}
  1. 注意,grpc 方法的传入和传出,都是结构体,即使是 productId 这种简单的 string 类型的形参,也应该使用自动生成的 proto.go 文件中定义好的结构体

  2. grpc 的方法调用中,也会有状态码,使用 google.golang.org/grpc/codes 包引用

完整代码如下

package main

import (
	"context"
	"fmt"
	"time"
	pb "zhao/grpc/pb/econFileName"

	"google.golang.org/grpc"
)

const (
	address = "localhost:5001"
)

func main() {
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		fmt.Printf("connect grpc service fail at  %s", address)
	}
	defer conn.Close()
	client := pb.NewProductInfoClient(conn)
	product1 := pb.Product{
		Name:        "Apple iPhone 11",
		Description: "Meet Apple iPhone 11. All-new dual-camera system with Ultra Wide and Night mode.",
		Price:       float32(699.00),
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// 添加产品
	id, err := client.AddProduct(ctx, &product1)
	if err != nil {
		fmt.Printf("client productid [%s] add product fail err: %v\n", product1.Id, err)
	}
	fmt.Printf("grpc req response product id [%s]\n", id.Value)

	// 查询产品
	req_id := pb.ProductID{Value: id.Value}
	res, err := client.GetProduct(ctx, &req_id)
	if err != nil {
		fmt.Printf("client get product fail id is %s\n", req_id.Value)
	}
	fmt.Printf("get product successful %s\n", res.String())
}

客户端

1. 声明连接使用的字符串

conn, err := grpc.Dial("localhost:5001", grpc.WithInsecure())

2.使用自动生成的 go 文件中的方法,创建 productInfo 对应的是实例

client := pb.NewProductInfoClient(conn)

3. 此时,你就可以拿着这个客户端实例,像本地方法调用一样使用远端方法了

res, err := client.GetProduct(ctx, &req_id)

完整代码如下

package main

import (
	"context"
	"fmt"
	"time"
	pb "zhao/grpc/pb/econFileName"

	"google.golang.org/grpc"
)

const (
	address = "localhost:5001"
)

func main() {
	conn, err := grpc.Dial("localhost:5001", grpc.WithInsecure())
	if err != nil {
		fmt.Printf("connect grpc service fail at  %s", address)
	}
	defer conn.Close()
	client := pb.NewProductInfoClient(conn)
	product1 := pb.Product{
		Name:        "Apple iPhone 11",
		Description: "Meet Apple iPhone 11. All-new dual-camera system with Ultra Wide and Night mode.",
		Price:       float32(699.00),
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// 添加产品
	id, err := client.AddProduct(ctx, &product1)
	if err != nil {
		fmt.Printf("client productid [%s] add product fail err: %v\n", product1.Id, err)
	}
	fmt.Printf("grpc req response product id [%s]\n", id.Value)

	// 查询产品
	req_id := pb.ProductID{Value: id.Value}
	res, err := client.GetProduct(ctx, &req_id)
	if err != nil {
		fmt.Printf("client get product fail id is %s\n", req_id.Value)
	}
	fmt.Printf("get product successful %s\n", res.String())
}

上一篇:Go的Protobuf与GRPC


下一篇:Golang |标准输入&输出