gRPC是什么
gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。
目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持。
gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。
这些特性使得其在移动设备上表现更好,更省电和节省空间占用。
安全
HTTP2 规范当使用 TLS 时强制使用 TLS 1.2 及以上的版本,并且在部署上对允许的密码施加一些额外的限制以避免已知的比如需要 SNI 支持的问题。并且期待 HTTP2 与专有的传输安全机制相结合,这些传输机制的规格说明不能提供有意义的建议。
gRPC使用
使用gRPC, 我们可以一次性的在一个.proto文件中定义服务并使用任何支持它的语言去实现客户端和服务端,反过来,它们可以应用在各种场景中,从Google的服务器到你自己的平板电脑—— gRPC帮你解决了不同语言及环境间通信的复杂性。使用protocol buffers还能获得其他好处,包括高效的序列号,简单的IDL以及容易进行接口更新。
总之一句话,使用gRPC能让我们更容易编写跨语言的分布式代码。
- 通过一个 protocol buffers 模式,定义一个简单的带有 Hello World 方法的 RPC 服务。
- 用你最喜欢的语言(如果可用的话)来创建一个实现了这个接口的服务端。
- 用你最喜欢的(或者其他你愿意的)语言来访问你的服务端。
gRPC与Protobuf实战
微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题.
gRPC可以实现将大的项目拆分为多个小且独立的业务模块,也就是服务。
各服务间使用高效的protobuf协议进行RPC调用,gRPC默认使用protocol buffers,这是google开源的一套成熟的结构数据序列化机制,当然也可以使用其他数据格式如JSON。
工具安装
1. 安装 grpc通用编译器
windows 选择如下图 ,解压后有protoc.exe。
D:\soft\protoc-3.19.1-win64\bin 把所在目录添加到环境变量中path中
mac
# 打开存放环境变量的文件
vim ~/.bash_profile
# 添加如下,后面是路径
alias protoc="/Users/emm/others/protoc-3.19.1-osx-x86_64/bin/protoc"
# 刷新环境变量
source ./.bash_profile
测试
C:\Users\ueumd>protoc --version
libprotoc 3.19.1
2.安装go专用的protoc的生成器
go get github.com/golang/protobuf/protoc-gen-go
安装后会在GOPATH目录下生成可执行文件,protobuf的编译器插件protoc-gen-go,等下执行protoc命令会自动调用这个插件。
代码实现
编写中间文件
新建一个pbfiles文件夹用于存放protoc文件
syntax = "proto3";
// 指定等会文件生成出来的package 需要加/
option go_package="service/";
// 定义request
message ProductRequest{
int32 prod_id = 1; // 1代表顺序
}
// 定义response
message ProductResponse{
int32 prod_stock = 1; // 1代表顺序
}
// 定义服务主体
service ProdService{
// 定义方法
rpc GetProductStock(ProductRequest) returns(ProductResponse);
}
运行protoc命令编译成go中间文件
然后运行以下的命令来生成.go结尾的文件
下面的命令就是我们刚刚下的protoc包以及protoc-gen-go插件的作用
如下就在service文件夹自动生成了一个go文件,并且它提示我们不要去修改它
protoc -I . Product.proto --go_out=plugins=grpc:../
说明
- -I . 当前路径下
- Product.proto 编写的proto文件
- —go_out=plugins=grpc: 必须要加上这个,不然不会生成RegisterProdServiceServer方法
- ../ 生成目录 根据option go_package=”service/“; 相当于../service
service目录新建ProdService.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
}
server目录下新建 server.go
package main
import (
"google.golang.org/grpc"
"net"
"ueumd/grpc/server/service"
)
func main() {
rpcServer := grpc.NewServer()
service.RegisterProdServiceServer(rpcServer, &service.ProdService{})
// service.RegisterProdServiceServer(rpcServer, new(service.ProdService))
lis, err := net.Listen("tcp", "0.0.0.0:8081")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = rpcServer.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
client目录新建client.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"ueumd/grpc/server/service"
)
func main() {
// 1. 新建连接,端口是服务端开放的8082端口
// 并且添加grpc.WithInsecure(),不然没有证书会报错
conn, err := grpc.Dial("127.0.0.1:8081", grpc.WithInsecure())
if err != nil {
panic(err)
}
// 退出时关闭链接
defer conn.Close()
// 2. 调用Product.pb.go中的NewProdServiceClient方法
client := service.NewProdServiceClient(conn)
// 3. 直接像调用本地方法一样调用GetProductStock方法
resp, err := client.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 777})
//ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
//resp, er := client.GetProductStock(ctx, &service.ProductRequest{ProdId: 777})
//
//if er != nil {
// st, ok := status.FromError(er)
//
// if !ok{
// panic("解析error失败")
// }
// fmt.Println(st.Message())
// fmt.Println(st.Code())
//}
if err != nil {
panic("调用gRPC方法错误: " + err.Error())
}
fmt.Println("调用gRPC方法成功,ProdStock = ", resp.ProdStock) // 调用gRPC方法成功,ProdStock = 777
}
启动服务
go run server.go
go run client.go
参考: