微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题。
Q:什么是 gRPC
gRPC
可以实现微服务,将大的项目拆分为多个小且独立的业务模块,也就是服务,各服务间使用高效的protobuf
协议进行 RPC 调用。
gRPC 是由 google 开发的一款语言中立、平台中立、开源的远程过程调用系统。gRPC 客户端和服务端可以在多种环境中运行和交互,例如用 java 写一个服务端,可以用 go 语言写客户端调用。gRPC 默认使用 protocol buffers,这是 google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)
Q:什么是 Protobuf ?
Protocol Buffers 是一种与语言、平台无关,可扩展的序列化结构化数据的方法。
常用于通信协议,数据存储等等。相较于 JSON、XML,它更小、更快、更简单,因此也更受开发人员的青睐。
Q:使用 Protocol Buffer 有什么好处?
Protocol buffers 非常适合任何需要以语言中立、平台中立、可扩展的方式序列化结构化、类记录、类型化数据的情况。
它们最常用于定义通信协议(与 gRPC 一起)和数据存储。
使用协议缓冲区的一些优点包括:
-
紧凑的数据存储
-
快速解析
-
可用于多种编程语言
-
通过自动生成的类优化功能
Q:Protobuf 如何使用?
安装
https://github.com/protocolbuffers/protobuf/releases/tag/v25.1
mac 使用 homebrew 安装
brew update
# 使用 brew 安装 protobuf
brew install protobuf
# 安装 go 的支持
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
语法
使用.proto
文件来定义一个消息类型。
syntax = "proto3"; // 指定版本信息,不指定会报错,默认是 proto2
// 指定包名,分号前面的表示生成的文件路径;分号后面表示生成的go文件的包名
option go_package = "./pb;pb";
// 定义结构体
message UserRequest {
string name = 1;
}
// 响应结构体
message UserResponse {
int32 id = 1;
string name = 2;
int32 age = 3;
// repeated修饰符是可变数组,golang中会生成string类型的切片
repeated string hobby = 4;
}
// service 定义方法
service UserInfoService {
// 定义了一个 GetUserInfo 的 rpc 服务
rpc GetUserInfo (UserRequest) returns (UserResponse);
}
- message 每一个字段包含三个属性:类型、字段名称、字段编号
使用
使用 Protocol buffer 编译器生成对应语言的文件
# 将当前文件夹所有的 proto 文件转换为 go 文件
protoc --go_out=./ *.proto
# 生成 grpc 的 go 文件
protoc --go_out=. --go-grpc_out=. *.proto
序列化和反序列化
生成的 go 文件,我们可以引入来使用。
-
使用
proto.Marshal()
可以将消息序列化为二进制文件。 -
使用
proto.Unmarshal()
可以反序列化
package main
import (
"fmt"
"github.com/KINGMJ/go-learning/tutorial6/demo3/proto/pb"
"google.golang.org/protobuf/proto"
)
func main() {
u := &pb.UserResponse{
Name: "张三",
Age: 20,
Hobby: []string{"吃饭", "睡觉", "打豆豆"},
}
fmt.Println(u)
fmt.Println(u.GetName())
// Protobuf序列化
data, _ := proto.Marshal(u)
fmt.Println(data)
// Protobuf 反序列化
user := pb.UserResponse{}
proto.Unmarshal(data, &user)
fmt.Println(user.GetHobby())
}
Q:gPRC 如何使用
-
编写 proto 文件,上面已经实现。我们定义了一个
UserInfoService
微服务,里面有一个GetUserInfo
函数。 -
编译 proto 为 go 文件:
protoc --go_out=. --go-grpc_out=. *.proto
-
服务端实现
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"github.com/KINGMJ/go-learning/tutorial6/demo3/proto/pb"
"google.golang.org/grpc"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// 定义空接口
type UserInfoService struct {
pb.UnimplementedUserInfoServiceServer
}
func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (res *pb.UserResponse, err error) {
// 通过用户名查询用户信息
name := req.Name
// 模拟数据库里查询用户信息
if name == "zs" {
res = &pb.UserResponse{
Id: 1,
Name: name,
Age: 22,
Hobby: []string{"吃饭", "睡觉", "打豆豆"},
}
}
return
}
func main() {
flag.Parse()
// 1. 监听 tcp 端口
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 2. 实例化 grpc
s := grpc.NewServer()
// 3. 在 grpc 上注册微服务
pb.RegisterUserInfoServiceServer(s, &UserInfoService{})
// 4. 启动服务端
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
- 客户端实现
package main
import (
"context"
"flag"
"log"
"time"
"github.com/KINGMJ/go-learning/tutorial6/demo3/proto/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
)
func main() {
flag.Parse()
// 1. 连接服务端
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 2. 实例化 grpc 客户端
client := pb.NewUserInfoServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 3. 组装请求参数
req := new(pb.UserRequest)
req.Name = "zs"
// 4. 调用接口
res, err := client.GetUserInfo(ctx, req)
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("响应结果: %v\n", res)
}