1. 前言
之前学习的go的微服务之间还是通过REST API
的方式互相调用的,但既然要学习微服务,gRPC
肯定是一个绕不过去的需要学习的技术, 所以就开搞吧
2. gRPC与Protobuf简介
gRPC
是一款语言中立、平台中立、开源的远程过程调用系统
微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题.
gRPC可以实现将大的项目拆分为多个小且独立的业务模块,也就是服务。各服务间使用高效的protobuf
协议进行RPC调用,gRPC默认使用protocol buffers
,这是google开源的一套成熟的结构数据序列化机制
可以用proto files创建gRPC服务,用message类型来定义方法参数和返回类型
3. 安装
- 第一步:下载grpc通用编译器
如下图,解压出来因平台而异会是一个protoc
或者protoc.exe
- 第二步:把下载的二进制文件路径添加到环境变量中(为了能全局访问protoc)
- 这里以为mac为例子
# 打开存放环境变量的文件
vim ~/.bash_profile
# 添加如下,后面是路径
alias protoc="/Users/emm/others/protoc-3.12.4-osx-x86_64/bin/protoc"
# 刷新环境变量
source ./.bash_profile
- 第三步: 安装go专用的protoc的生成器
安装后会在GOPATH
目录下生成可执行文件,protobuf的编译器插件protoc-gen-go
,等下执行protoc
命令会自动调用这个插件
4. 中间文件演示
4.1 编写中间文件
这里新建一个pbfiles文件夹用于存放protoc
文件
// 这个就是protobuf的中间文件
// 指定的当前proto语法的版本,有2和3
syntax = "proto3";
// 指定等会文件生成出来的package
package service;
// 定义request
message ProductRequest{
int32 prod_id = 1; // 1代表顺序
}
// 定义response
message ProductResponse{
int32 prod_stock = 1; // 1代表顺序
}
4.2 运行protoc命令编译成go中间文件
然后运行以下的命令来生成.go
结尾的文件
- 下面的命令就是我们刚刚下的
protoc
包以及protoc-gen-go
插件的作用
# 编译Product.proto之后输出到service文件夹
protoc --go_out=../service Product.proto
如下就在service文件夹自动生成了一个go文件,并且它提示我们不要去修改它
5. 创建gRPC服务端
5.1 新建Product.protoc
这个protoc文件比上面的多出了一个service的定义和里面的一个方法的定义
// 这个就是protobuf的中间文件
// 指定的当前proto语法的版本,有2和3
syntax = "proto3";
// 指定等会文件生成出来的package
package service;
// 定义request model
message ProductRequest{
int32 prod_id = 1; // 1代表顺序
}
// 定义response model
message ProductResponse{
int32 prod_stock = 1; // 1代表顺序
}
// 定义服务主体
service ProdService{
// 定义方法
rpc GetProductStock(ProductRequest) returns(ProductResponse);
}
5.2 运行protoc命令
注意
- 这里的protoc命令和之前的命令相比有点不一样
protoc --go_out=plugins=grpc:../service Product.proto
然后还是会在service文件夹下生成一个.go
的文件
有两个比较需要注意的
- RegisterProdServiceServer
- ProdServiceServer的接口定义
5.3 实现RegisterProdServiceServer接口
上面我们在protoc
文件中定义了一个ProdService
中包含了一个GetProductStock
的方法
这里我们要实现自动生成的go文件中的接口
package service
import "context"
type ProdService struct {
}
func (ps *ProdService) GetProductStock(ctx context.Context, request *ProductRequest) (*ProductResponse, error) {
return &ProductResponse{ProdStock: request.ProdId}, nil
}
5.4 准备工作完成,创建main函数将服务端跑起来
前面的都是准备工作,这里是真正把服务端跑起来的操作
下面是服务端代码:
package main
import (
"gomicro-quickstart/grpc_demo/service"
"google.golang.org/grpc"
"log"
"net"
)
func main() {
// 1. new一个grpc的server
rpcServer := grpc.NewServer()
// 2. 将刚刚我们新建的ProdService注册进去
service.RegisterProdServiceServer(rpcServer, new(service.ProdService))
// 3. 新建一个listener,以tcp方式监听8082端口
listener, err := net.Listen("tcp", ":8082")
if err != nil {
log.Fatal("服务监听端口失败", err)
}
// 4. 运行rpcServer,传入listener
_ = rpcServer.Serve(listener)
}
排坑:
- 如果遇见类似
undefined: grpc.SupportPackageIsVersion6
和undefined: grpc.ClientConnInterface
的错误,可以修改go.mod将grpc版本改到1.27.0
6. 创建gRPC客户端
- 新建一个
grpc_client
文件夹存放客户端相关的 - 并在
grpc_client
文件夹下再新建一个service
文件夹
6.1 拷贝Product.pb.go到客户端service文件夹下
6.2 编写client的main函数
package main
import (
"context"
"fmt"
"gomicro-quickstart/grpc_client/service"
"google.golang.org/grpc"
"log"
)
func main() {
// 1. 新建连接,端口是服务端开放的8082端口
// 并且添加grpc.WithInsecure(),不然没有证书会报错
conn, err := grpc.Dial(":8082", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
// 退出时关闭链接
defer conn.Close()
// 2. 调用Product.pb.go中的NewProdServiceClient方法
productServiceClient := service.NewProdServiceClient(conn)
// 3. 直接像调用本地方法一样调用GetProductStock方法
resp, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
if err != nil {
log.Fatal("调用gRPC方法错误: ", err)
}
fmt.Println("调用gRPC方法成功,ProdStock = ", resp.ProdStock)
}
6.3 运行并显示结果
- 先把服务端运行起来
- 再把客户端运行起来
然后客户端输出正确的结果,第一个go的gRPC调用运行成功