oldme 博客

五色令人目盲,五音令人耳聋

gRPC-Go 详细讲解和入门

oldme create: 2023-08-06

gRPC 是 Google 发起的一个开源远程过程调用(RPC)系统,主要使用 C++ 开发。她基于 HTTP/2 和 Protobuf,支持多种编程语言: C#/.NET, C++, Dart, Go, Java, Kotlin, Node, Objective-C, PHP, Python, Ruby。

什么是 RPC 和 gRPC

RPC,英文全称:Remote Procedure Call,中文称远程调用 。RPC 是一种协议,说明白了,她描述不同计算机之间交互数据的规范。

在微服务大行其道的今天,服务器之间的数据交互尤为重要,服务器之间怎么进行数据交互呢?我们规定一个 API:统一使用 HTTP 做为传输协议 ,JSON 做为接口描述语言。Restful API 就是基于 HTTP + JSON 的。那如果我们使用 HTTP/2 做为传输协议,Protobuf 做为接口描述语言,这种 API 该称之为什么呢?想必你已经知道了,她就是 gRPC

那么在微服务中,既然已经有了Restful,我们为什么还要折腾 gRPC 呢?主要的原因就是 gRPC 拥有更好的性能和双向流式处理。当然,在微服务中使用 Restful 也是可以的,甚至,你可以自己定义 API 来做为微服务之间的数据交互。只不过,gRPC 成为了微服务事实上的标准之一。

舞台准备

在继续之前,你需要简单的了解一下 Protocol,英语强的可以直接去看官方文档:https://protobuf.dev/programming-guides/proto3 。如果看不懂可以看一下翻译的文档,这里简单介绍一下 Protobuf:

  • Protobuf 的文件后缀是 .proto
  • .proto 文件需要使用 Protobuf 编译器,用来编译不同编程语言所需的文件
  • 在 Go 中,编译后的文件是 *.pb.go *_grpc.pb.go

安装 Protobuf

Linux、Windows、MAC 可以参考:protocol buffers(protobuf)安装教程 。安装完成后在控制台输入命令查看版本:

protoc --version
// 结果
libprotoc 24.0-rc3

安装 Protobuf-Go 插件

版本要求:Go > 1.16,支持 go install 命令,旧版本的 Go 使用的是 go get 命令安装。

安装 protoc-gen-go 插件, 用于生成 *.pb.go 文件:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

安装 protoc-gen-go-grpc 插件:用于生成 *_grpc.pb.go 文件:

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

截至本文发布时间 2023-08-06,此安装方式是最新的,如果在网上看到其他不同的安装方式,请核对版本号。

创建项目

  1. 创建项目根目录 grpc;
  2. 执行 go mod init grpc,初始化 go mod;
  3. 创建 service 目录,代表服务端,即被调用方;
  4. 创建 client 目录,代表客户端,即调用方;
  5. 创建 protobuf 目录,保存接口文件。

最终项目结构:

生成接口文件

在 protobuf 下创建目录 goods,新建 goods.proto 文件:

// 使用proto3
syntax = "proto3";

package goods;

// 指定 Go 代码的包名称和导入路径
option go_package = "./goods";

service GoodsRpc {
  rpc GetGoods (GoodsReq) returns (GoodsRes);
}

message GoodsReq {
  uint32 id = 1;
}

message GoodsRes {
  string name = 1;
  uint64 price = 2;
}

其定义的功能如下:

  • 定义一个 GetGoods 服务,输入参数 GoodsReq,输出参数 GoodsRes;
  • GoodsReq 消息体,id 代表商品 id;
  • GoodsRes 消息体,name 代表商品名词,price 代表商品价格。

之后在控制台输入此命令即可生成 goods.pb.go 和 goods_grpc.pb.go 文件:

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative .\protobuf\goods\goods.proto
  • --go_out=. --go_opt=paths=source_relative 用以在 .proto 文件同目录下生成 goods.pb.go
  • --go-grpc_opt=paths=source_relative 用以在 .proto 文件同目录下生成 goods_grpc.pb.go

Ok,此时我们的结构看上去应该是这样:

当你打开 .pb.go 和 _grpc.pb.go 时会发现两个文件报错,这是因为依赖还没有引入,执行 go mod tidy 即可。

启用服务端

让我们来编写服务端代码,实现传入商品 id 获取商品信息的功能,先获取 grpc 包:

go get google.golang.org/grpc

在 service 目录下创建 main.go,编写以下代码:

package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
	pb "service/protobuf/goods"
)

// Goods 定义 Goods, 实现接口
type Goods struct {
	pb.UnimplementedGoodsRpcServer
}

// GetGoods 实现获取商品的功能
func (g *Goods) GetGoods(ctx context.Context, req *pb.GoodsReq) (*pb.GoodsRes, error) {
	var (
		name string
		err  error
	)
	if req.Id == 0 {
		err = errors.New("商品不存在")
	} else {
		name = fmt.Sprintf("%d号商品", req.Id)
	}
	return &pb.GoodsRes{
		Name:  name,
		Price: 20,
	}, err
}

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", ":10001")
	if err != nil {
		log.Fatalf("监听失败: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGoodsRpcServer(s, &Goods{})
	log.Printf("正在监听端口: %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("启用服务失败: %v", err)
	}
}

执行程序,即可在控制台看见效果:

2023/08/06 23:22:59 正在监听端口: [::]:10001

启用客户端

在 client 目录下创建 main.go,编写以下代码:

package main

import (
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
	pb "service/protobuf/goods"
	"time"
)

func main() {
	conn, err := grpc.Dial("localhost:10001", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("连接失败: %v", err)
	}
	defer conn.Close()
	c := pb.NewGoodsRpcClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.GetGoods(ctx, &pb.GoodsReq{Id: 1})
	if err != nil {
		log.Fatalf("无法调用: %v", err)
	}
	log.Printf("商品名称: %s", r.GetName())
	log.Printf("商品价格: %d", r.GetPrice())
}

执行程序,即可看到结果:

2023/08/06 23:26:57 商品名词: 1号商品
2023/08/06 23:26:57 商品价格: 20

最终结构如下,源码放在了 github 上,可供参考。

评论

欢迎您的回复 取消回复

您的邮箱不会显示出来,*必填

本文目录