什么是 RPC

RPC (Remote Procedure Call) :远程过程调用,它是一个计算机通信协议,该协议允许像调用本地方法一样调用远程服务。(一台计算的程序可以调用另一台计算机上的程序)

RPC 的调用过程:

  • Client 通过本地调用,调用远程代理类
  • 将请求方法、参数序列化(打包)
  • 通过网络模块发送给 Server
  • Server 接收到消息后,将消息进行反序列化(解包)获取方法以及参数
  • 通过代理分发,请求服务端的具体实现,(例如请求 XX 方法)
  • Server 端 处理完请求,将结果返回,同样经过 代理,将结果进行序列化,通过网络模块发送给 Client
  • Client 收到消息进行反序列化,并最终返回给调用者

file

什么是 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 的调用如下图所示:

file

什么是 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 编辑器满屏报错

file

那是因为生成的文件需要导入一些包,我们 使用 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 服务:

  1. 通过 net.Listen(...) 监听客户端的请求
  2. 通过 grpc.NewServer() 创建一个 gRPC Server 实例
  3. 通过 pb.RegisterUserServiceServer(s, &User{}) 将该服务注册到 gRPC 框架中
  4. 通过 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)

}
  1. 通过 grpc.Dial(xxx) 创建一个 gRPC 连接,用来跟服务端进行通信
  2. 通过 pb.NewUserServiceClient(conn) 创建 客户端对象
  3. 最后就是调用各种方法 处理请求

启动服务

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返现。目前仅包含这几个平台。后续如果有需要可以考虑其他平台。 简而言之就是:你买课,我返现。让你花更少的钱,就可以买到课程。

https://geekfl.com

https://geek.laravelcode.cn

版权许可

本作品采用 知识共享署名 4.0 国际许可协议 进行许可。

转载无需与我联系,但须注明出处,注明文章来源 Go 语言 gRPC 的简单使用

联系我

编程怪事
暂无回复
0 / 180