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 grpc
2. 安装的是protocol编译器
brew install protobuf
3. 各个语言的代码生成工具,对于 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,返回值为UserInfoResponse
rpc 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 main
import (
"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.Name
if name == "zs" {
resp = &pb.UserInfoResponse{
Id: 1,
Name: name,
Age: 18,
Hobbies: []string{"swimming", "running"},
}
}
err = nil
return
}
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. 实例化gRPC
s := grpc.NewServer()
// 3. 在gRPC上注册微服务,第二个参数要接口类型的变量
pb.RegisterUserInfoServiceServer(s, &UserInfoService{})
// 4. 启动gRPC服务端
s.Serve(lis)
}
3.4 client客户端
package main
import (
"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.proto
syntax = "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 main
import (
"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 main
import (
"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()
}