gRPC-Go 详细讲解和入门
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,此安装方式是最新的,如果在网上看到其他不同的安装方式,请核对版本号。
创建项目
- 创建项目根目录 grpc;
- 执行 go mod init grpc,初始化 go mod;
- 创建 service 目录,代表服务端,即被调用方;
- 创建 client 目录,代表客户端,即调用方;
- 创建 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 上,可供参考。
本文目录