1、gRPC 与 Protobuf 介绍
- 微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间
的通信就是个大问题 - gRPC 可以实现微服务, 将大的项目拆分为多个小且独立的业务模块, 也就是服务,
各服务间使用高效的protobuf 协议进行RPC 调用, gRPC 默认使用protocol buffers,
这是 google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格
式如JSON) - 可以用 proto files 创建 gRPC 服务,用 message 类型来定义方法参数和返回类型
参考文章:gRPC教程
2、Mac下安装Protobuf和gRPC
参考文章:https://cloud.tencent.com/developer/article/2163004
用brew
命令安装
1. 安装的是 gRPC 的核心库
brew install grpc2. 安装的是protocol编译器
brew install protobuf3. 各个语言的代码生成工具,对于 Golang 来说,称为 protoc-gen-go
brew install protoc-gen-go
brew install protoc-gen-go-grpc
查看安装是否成功
apple@appledeMacBook-Pro ~ % protoc --version
libprotoc 25.1
apple@appledeMacBook-Pro ~ % protoc-gen-go --version
protoc-gen-go v1.31.0
apple@appledeMacBook-Pro ~ % protoc-gen-go-grpc --version
protoc-gen-go-grpc 1.3.0
如果查不到指令,检查一下环境变量
export GOPATH="/Users/apple/go"
export PATH=$PATH:$GOPATH/bin
3、Demo1
3.1 目录结构
实现服务端和客户端的数据传输
├── client
│ └── client.go
├── go.mod
├── go.sum
├── proto
│ └── user.proto
└── server└── server.go
3.2 user.proto编写
// 编译指令:protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/user.proto// 版本号
syntax = "proto3";// 生成文件所在的目录
option go_package="/proto";// 制定生成 user.pb.go 的包名
package proto;// 定义message服务端响应的数据格式,相当于结构体,
message UserInfoResponse {// 定义字段,相当于结构体的属性int32 id = 1;string name = 2;int32 age = 3;// 字段修饰符,repeated表示可以重复出现,就是可变数组,类似于切片类型repeated string hobbies = 4;
}// 定义message客户端请求的数据格式,相当于结构体,
message UserInfoRequest {// 定义字段,相当于结构体的属性string name = 1;
}// 定义一个service服务,相当于接口
service UserInfoService {// 定义一个rpc方法,相当于接口方法// 定义请求参数为UserInfoRequest,返回值为UserInfoResponserpc GetUserInfo(UserInfoRequest) returns (UserInfoResponse);
}
执行编译指令
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/user.proto
这是一个使用 Protocol Buffers(protobuf)和 Go gRPC 插件生成代码的示例命令。该命令根据 proto/user.proto 文件生成对应的 Go 代码。
这个命令的参数含义如下:
--go_out=.:指定生成的 Go 代码输出目录为当前目录。
--go_opt=paths=source_relative:设置生成的 Go 代码中的导入路径为相对于源文件的相对路径。
--go-grpc_out=.:指定生成的 Go gRPC 代码输出目录为当前目录。
--go-grpc_opt=paths=source_relative:设置生成的 Go gRPC 代码中的导入路径为相对于源文件的相对路径。
proto/user.proto:指定要生成代码的 protobuf 文件路径。
最后会在proto目录下生成user.pb.go和user_grpc.pb.go
- user.pb.go:这个文件包含了用户自定义的消息类型的定义,它描述了在通信过程中需要传输的数据结构,比如用户信息、请求参数等。
- user_grpc.pb.go:这个文件包含了用户自定义的服务接口的定义,它描述了可以远程调用的方法和参数,以及返回值等。
3.3 server服务端
package mainimport ("context""fmt"pb "main/proto""net""google.golang.org/grpc"
)// 定义服务端实现约定的接口
type UserInfoService struct {pb.UnimplementedUserInfoServiceServer
}// 实现服务端需要实现的接口
func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserInfoRequest) (resp *pb.UserInfoResponse, err error) {// 服务端接收参数name,再进行业务操作name := req.Nameif name == "zs" {resp = &pb.UserInfoResponse{Id: 1,Name: name,Age: 18,Hobbies: []string{"swimming", "running"},}}err = nilreturn
}func main() {// 1. 监听addr := "127.0.0.1:8080"lis, err := net.Listen("tcp", addr)if err != nil {fmt.Println("监听异常, err = ", err)return}fmt.Println("开始监听:", addr)// 2. 实例化gRPCs := grpc.NewServer()// 3. 在gRPC上注册微服务,第二个参数要接口类型的变量pb.RegisterUserInfoServiceServer(s, &UserInfoService{})// 4. 启动gRPC服务端s.Serve(lis)
}
3.4 client客户端
package mainimport ("context""fmt"pb "main/proto""google.golang.org/grpc"//"google.golang.org/grpc/credentials/insecure"
)func main() {// 1. 创建与gRPC服务器的连接addr := "127.0.0.1:8080"conn, err := grpc.Dial(addr, grpc.WithInsecure())if err != nil {fmt.Println("连接异常, err = ", err)return}defer conn.Close()// 2. 实例化gRPC客户端client := pb.NewUserInfoServiceClient(conn)// 3. 组装参数req := new(pb.UserInfoRequest)req.Name = "zs"// 4. 调用接口resp, err := client.GetUserInfo(context.Background(), req)if err != nil {fmt.Println("响应异常, err = ", err)return}fmt.Println("响应结果, resp = ", resp)
}
3.5 编译
先执行服务端,再执行客户端
4、Demo2
4.1 目录结构
├── client
│ └── client.go
├── go.mod
├── go.sum
├── proto
│ └── hello.proto
└── server└── server.go
4.2 hello.proto
// 编译指令:protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/hello.protosyntax = "proto3";option go_package="/proto";package Business;service Hello {rpc Say (SayRequest) returns (SayResponse);
}message SayResponse {string Message = 1;
}message SayRequest {string Name = 1;
}
4.3 server服务端
package mainimport ("context""fmt""google.golang.org/grpc""main/proto""net"
)type server struct {proto.UnimplementedHelloServer
}func (s *server) Say(ctx context.Context, req *proto.SayRequest) (*proto.SayResponse, error) {fmt.Println("request:", req.Name)return &proto.SayResponse{Message: "Hello " + req.Name}, nil
}func main() {listen, err := net.Listen("tcp", ":8001")if err != nil {fmt.Printf("failed to listen: %v", err)return}s := grpc.NewServer()proto.RegisterHelloServer(s, &server{})//reflection.Register(s)defer func() {s.Stop()listen.Close()}()fmt.Println("Serving 8001...")err = s.Serve(listen)if err != nil {fmt.Printf("failed to serve: %v", err)return}
}
4.4 client客户端
package mainimport ("bufio""context""fmt""main/proto""os""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)func main() {var serviceHost = "127.0.0.1:8001"conn, err := grpc.Dial(serviceHost, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {fmt.Println(err)}defer conn.Close()client := proto.NewHelloClient(conn)rsp, err := client.Say(context.TODO(), &proto.SayRequest{Name: "BOSIMA",})if err != nil {fmt.Println(err)}fmt.Println(rsp)fmt.Println("按回车键退出程序...")in := bufio.NewReader(os.Stdin)_, _, _ = in.ReadLine()
}