Golang gRPC笔记01 gRPC简介与基本使用
一、 关于RPC
-
为什么需要 RPC
使用RPC,目的主要是 像调用本地服务一样远程调用另一台服务器上的服务 来完成需求;使用http的接口也能完成这种需求,但是rpc有这些优势:简单、通用、安全、高效
-
RPC简介
- rpc只是一种概念,一种设计,就是为了解决不同服务之间的调用问题,它一般会包含 传输协议 和 序列化协议 这两个协议。
- rpc与http:RPC是一种思想,Http是一种协议,这是本质区别
- rpc的传输协议和序列化协议:
- 传输协议: 著名的 grpc 使用的 http2 协议,也有如dubbo一类的自定义报文的tcp协议
- 序列化协议: 如基于文本编码的 xml json,也有二进制编码的 protobuf hessian等
- web services: 传输协议=soap 序列化协议=xml
- restful API: 传输协议=http 序列化协议=text
- gRPC 传输协议=http/2 序列化协议=protocol buffer
-
RPC 可以基于 HTTP 吗 ?
- rpc大多是基于tcp实现的,但是也可以基于http实现,grpc的传输协议就是http2协议。
- 为什么大部分RPC通常会采用自定义TCP协议来进行服务调用: http1.1协议的TCP报文包含太多在传输过程中可能无用的信息,自定义TCP协议进行传输就会避免上面这个问题,极大的减轻了传输数据的开销。
二、 gRPC
2.1 gRPC简介
gRPC 是一个高性能、开源、通用的RPC框架,由Google推出,基于HTTP/2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言
2.2 Protocol Buffers v3
Protocol Buffers 是 Google 开源的,一种与语言无关,平台无关,可扩展的,用来将结构化数据序列化成 二进制数据编码格式 的方法,用于通信协议,数据存储等。
RPC 调用过程中客户端和服务端必须基于同一种 基础消息交换格式,才能让双方明白需要交换的数据到底代表什么意思! 不同的 RPC 框架可能选用的编解码协议各不相同,比如 gob、JSON、messagepack 等
gRPC 目前使用 Protocol Buffers V3(简称 proto3),它是 proto2 的升级版,性能更优,并增加了对 iOS 和 Android 等移动设备的支持
语法:新建文件 .proto 后缀文件,如 prod.proto
syntax = "proto3";
package pb;
option go_package=".;pb";
message ProdRequest {
int64 prod_id = 1;
}
message ProdResponse {
int64 prod_stock = 1;
}
service ProdService {
rpc GetProdStock (ProdRequest) returns (ProdResponse);
}
编译: proto文件的编译,需要
- 安装编译器: 例
wget https://github.com/google/protobuf/releases/download/v3.12.3/protobuf-all-3.12.3.zip... - 安装编译器插件:例 golang 插件
go get -u github.com/golang/protobuf/protoc-gen-go
编译命令:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
- golang中的编译参数:
- -I 参数:指定import路径,可以指定多个-I参数,编译时按顺序查找,不指定时默认查找当前目录
- --go_out :golang编译支持,支持以下参数
- plugins=plugin1+plugin2 - 指定插件,目前只支持grpc,即:plugins=grpc
- 这里定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要给出 plugins 参数传递给 protoc-gen-go,告诉它,请支持 RPC(这里指定了 gRPC)
- M 参数 - 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是.proto文件中import语句的路径)
- import_prefix=xxx - 为所有import路径添加前缀,主要用于编译子目录内的多个proto文件,这个参数按理说很有用,尤其适用替代一些情况时的M参数,但是实际使用并不能达到我们预想的效果...
- import_path=foo/bar - 用于指定未声明package或go_package的文件的包名,最右面的斜线前的字符会被忽略
- 末尾 :编译文件路径 .proto文件路径(支持通配符)
- plugins=plugin1+plugin2 - 指定插件,目前只支持grpc,即:plugins=grpc
完整示例:
protoc -I . --go_out=plugins=grpc,Mfoo/bar.proto=bar,import_prefix=foo/,import_path=foo/bar:. ./*.proto
2.3 golang 中的 grpc 库
在 golang 中使用 gRPC,安装 grpc-go 包即可,命令:
go get -u google.golang.org/grpc
三、 gRPC的基本使用
gRPC使用流程:
- 编写.proto描述文件
- 编译生成.pb.go文件
- 服务端实现约定的接口并提供服务
- 客户端按照约定调用方法请求服务
项目目录:
grpc_demo/
|—— demo01/
|—— client/
|—— client.go // 客户端
|—— proto/
|—— prod/
|—— prod.proto
|—— prod.pb.go
|—— server/
|—— server.go // 服务端
.proto文件:prod.proto
syntax = "proto3";
package prodpb;
option go_package="./prod;prodpb";
message ProdRequest {
int64 prod_id = 1;
}
message ProdResponse {
int64 prod_stock = 1;
}
service ProdService {
rpc GetProdStock (ProdRequest) returns (ProdResponse);
}
编译生成.pb.go文件:prod.pb.go
# 编译 prod.proto
# 注意 option go_package 参数,在此处全部以 proto/ 目录为相对路径进行编写
# 此时,只有切换到 demo01/proto 目录下执行命令, 生成的 .pb.go 文件才会与 .proto 文件同目录
protoc.exe -I . --go_out=plugins=grpc:. prod/*.proto
server端:server.go
package main
import (
"context"
prodpb "grpc_demo/demo01/proto/prod"
"log"
"net"
"google.golang.org/grpc"
)
// 定义 ProdService 并实现约定的接口
type ProdService struct{}
func (p ProdService) GetProdStock(_ context.Context, prodReq *prodpb.ProdRequest) (*prodpb.ProdResponse, error) {
log.Printf("ProdRequest.ProdId = %d", prodReq.ProdId)
return &prodpb.ProdResponse{ProdStock: 1008}, nil
}
// 生成ProdService
func CreateProdService() ProdService {
return ProdService{}
}
const (
// Address gRPC服务地址
Address = "127.0.0.1:8899"
)
func main() {
// 1. 创建 gRPC Server 的实例对象
rpcServer := grpc.NewServer()
// 2.gRPC Server 内部服务和路由的注册
prodpb.RegisterProdServiceServer(rpcServer, CreateProdService())
// 3. 监听指定 TCP 端口,用于接受客户端请求
listener, err := net.Listen("tcp", Address)
if err != nil {
panic("net.Listen err: " + err.Error())
}
log.Printf("Listening on %s\n", Address)
// 4. Serve() 调用服务器以执行阻塞等待,直到进程被终止或被 Stop() 调用
log.Fatal(rpcServer.Serve(listener))
}
client端: client.go
package main
import (
"context"
prodpb "grpc_demo/demo01/proto/prod"
"log"
"google.golang.org/grpc"
)
const (
// Address gRPC服务地址
Address = "127.0.0.1:8899"
)
func main() {
// 连接 rpc 服务器
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
panic("grpc.Dial err: " + err.Error())
}
defer conn.Close()
// 初始化客户端
client := prodpb.NewProdServiceClient(conn)
resp, err := client.GetProdStock(context.Background(), &prodpb.ProdRequest{ProdId: 1111111})
if err != nil {
log.Print("调用失败,err=", err)
return
}
log.Printf("%+v \n", resp)
}
验证:
启动 Server:
cd demo01/server
go run server.go
Listening on 127.0.0.1:8899
启动 Client:
cd demo01/client
go run client.go
prod_stock:1008
搞定! 成功连接服务端,并获取数据...
参考文档:
https://github.com/Jergoo/go-grpc-example
https://eddycjy.com/tags/grpc-gateway/
https://www.cnblogs.com/FireworksEasyCool/category/1693727.html

浙公网安备 33010602011771号