什么是 RPC
RPC
(Remote Procedure Call) :远程过程调用,它是一个计算机通信协议,该协议允许像调用本地方法一样调用远程服务。(一台计算的程序可以调用另一台计算机上的程序)
RPC 的调用过程:
- Client 通过本地调用,调用远程代理类
- 将请求方法、参数序列化(打包)
- 通过网络模块发送给 Server
- Server 接收到消息后,将消息进行反序列化(解包)获取方法以及参数
- 通过代理分发,请求服务端的具体实现,(例如请求 XX 方法)
- Server 端 处理完请求,将结果返回,同样经过 代理,将结果进行序列化,通过网络模块发送给 Client
- Client 收到消息进行反序列化,并最终返回给调用者
什么是 gRPC
A high performance, open source universal RPC framework
GRPC 是一个高性能、开源、通用的 PRC 框架。
特点:
- 多语言:支持多种语言,例如 Go、Java、PHP、C++ 等
- 基于 HTTP/2 标准设计:支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性。这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量。
- IDL:基于文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub
- 支持 Protobuf 和 JSON 序列化数据格式。Protobuf 是一种语言无关的高性能序列化框架,可以减少网络传输流量,提高通信效率。
gRPC 的调用如下图所示:
什么是 Protobuf
Protobuf(Protocol Buffers)是一种与语言、平台无关,且可扩展的序列化结构化数据的数据描述语言,通常称其为IDL,常用于通信协议、数据存储等,与JSON、XML相比,它更小、更快
更多 Protobuf 用法 请参考 Protobuf3语言指南
安装 Protobuf
安装 protoc 编译器
Mac 用户可以直接使用 brew 进行安装
brew install protobuf
或者
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protobuf-all-3.19.4.tar.gz
tar -xzvf protobuf-all-3.19.4.tar.gz
cd protobuf-3.19.4
./configure
make
make install
检测是否安装成功
protoc --version
安装 protoc 插件
不同的语言,安装不同的插件,这里因为要用 Go,因此对应 Go 语言的是 protoc-gen-go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
以上准备工作做完了,我们正式开始 Go 语言的 gRPC 之路
大致分为以下步骤:
- 编写 protobuf 文件
- 生成客户端和服务端代码
- 实现 gRPC 服务端
- 实现 gPRC 客户端
初始化一个 rpc 项目
大致的目录结构如下:
其中 api 目录存放具体的 proto 文件。server 和 client 分别对应服务消费者和服务提供者
mkdir grpc-demo
cd grpc-demo
mkdir api server client
go mod init github.com/hedeqiang/grpc-demo
编写 protobuf 文件
在 api 目录下新建 一个 user 文件夹,并创建 user.proto 文件,写入如下内容:
syntax = "proto3";
option go_package = "api/user";
package user;
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse) {}
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {}
}
message GetUserRequest {
string user_id = 1;
}
message GetUserResponse {
string user_id = 1;
string name = 2;
string email = 3;
string password = 4;
string created_at = 5;
string updated_at = 6;
}
message CreateUserRequest {
string name = 1;
string email = 2;
string password = 3;
}
message CreateUserResponse {
string user_id = 1;
string name = 2;
string email = 3;
string password = 4;
string created_at = 5;
string updated_at = 6;
}
生成客户端和服务端代码
使用 protoc 工具编译 protobuf 文件
protoc -I. --go_out=plugins=grpc:. ./api/user/user.proto
运行以上命令就会在 api/user 目录下生成 user.pb.go
文件,我们打开看下,发现 Golang 编辑器满屏报错
那是因为生成的文件需要导入一些包,我们 使用 go mod tidy 进行加载下
go mod tidy
go: finding module for package google.golang.org/protobuf/runtime/protoimpl
go: finding module for package google.golang.org/grpc/status
go: finding module for package google.golang.org/grpc/codes
go: finding module for package google.golang.org/protobuf/reflect/protoreflect
go: finding module for package google.golang.org/grpc
go: downloading google.golang.org/grpc v1.45.0
go: found google.golang.org/grpc in google.golang.org/grpc v1.45.0
go: found google.golang.org/grpc/codes in google.golang.org/grpc v1.45.0
go: found google.golang.org/grpc/status in google.golang.org/grpc v1.45.0
go: found google.golang.org/protobuf/reflect/protoreflect in google.golang.org/protobuf v1.28.0
go: found google.golang.org/protobuf/runtime/protoimpl in google.golang.org/protobuf v1.28.0
go: downloading google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
go: downloading golang.org/x/net v0.0.0-20200822124328-c89045814202
go: downloading golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
go: downloading golang.org/x/text v0.3.0
ok。报错没有了,接下来编写 gRPC 服务端
服务端代码
在 server 文件夹下新建 server.go 文件
package main
import (
"context"
"fmt"
pb "github.com/hedeqiang/grpc-demo/api/user"
"google.golang.org/grpc"
"log"
"net"
)
type User struct {
pb.UnimplementedUserServiceServer
}
func (u *User) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
// 这里可以做一些业务逻辑处理。为了演示方便,这里直接返回一个用户信息
return &pb.GetUserResponse{
UserId: "1",
Name: "hedeqiang",
Email: "hedeqiang@88.com",
CreatedAt: "2019-01-01",
UpdatedAt: "2019-01-01",
}, nil
}
func (u *User) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
fmt.Println(req)
// 创建用户,理论上应该是把用户信息写入数据库,这里为了演示方便直接返回一个用户信息
return &pb.CreateUserResponse{
UserId: "1",
Name: req.Name,
Email: req.Email,
}, nil
}
func main() {
fmt.Println("start server")
listen, err := net.Listen("tcp", ":8012")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &User{})
err = s.Serve(listen)
if err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
以上代码最为关键的其实就是四步就可以启动一个 gRPC 服务:
- 通过
net.Listen(...)
监听客户端的请求 - 通过
grpc.NewServer()
创建一个 gRPC Server 实例 - 通过
pb.RegisterUserServiceServer(s, &User{})
将该服务注册到 gRPC 框架中 - 通过
s.Serve(listen)
启动 gRPC 服务
那么接下来开始编写 客户端的代码
客户端代码
在 client 文件夹下 创建 client.go 文件
package main
import (
"context"
"fmt"
pb "github.com/hedeqiang/grpc-demo/api/user"
"google.golang.org/grpc"
"log"
"time"
)
func main() {
fmt.Println("client start")
conn, err := grpc.Dial("127.0.0.1:8012", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
c := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 创建用户
createUserRequest := &pb.CreateUserRequest{
Name: "hedeqiang",
Email: "hedeqiang@88.com",
Password: "123456",
}
user, err := c.CreateUser(ctx, createUserRequest)
if err != nil {
log.Fatalf("create user failed: %v", err)
}
log.Printf("create user success: %v", user)
// 获取用户信息 GetUser
getUserRequest := &pb.GetUserRequest{
UserId: "1",
}
getUser, err := c.GetUser(ctx, getUserRequest)
if err != nil {
log.Fatalf("get user failed: %v", err)
}
fmt.Printf("user: %v", getUser)
}
- 通过
grpc.Dial(xxx)
创建一个 gRPC 连接,用来跟服务端进行通信 - 通过
pb.NewUserServiceClient(conn)
创建 客户端对象 - 最后就是调用各种方法 处理请求
启动服务
go run server/server.go
go run client/client.go
以上就是一次简单的 gRPC 通信。
更多内容请参考如下链接: https://colobu.com/2017/03/16/Protobuf3-language-guide/
https://grpc.io/docs/languages/go/quickstart/
关于极客返利
极客返利 是由我个人开发的一款网课返利、返现平台。包含 极客时间返现、拉勾教育返现、掘金小册返现、GitChat返现。目前仅包含这几个平台。后续如果有需要可以考虑其他平台。 简而言之就是:你买课,我返现。让你花更少的钱,就可以买到课程。
版权许可
本作品采用 知识共享署名 4.0 国际许可协议 进行许可。转载无需与我联系,但须注明出处,注明文章来源 Go 语言 gRPC 的简单使用
联系我
