Go gRPC 入门与实践

一. gRPC 是什么

  1. 官网简介: A high-performance, open-source universal RPC framework
    • gRPC 是一个高性能的, 开源统一的 RPC 框架
  2. RPC (Remote Procedure Calls), 是指远程过程调用
    • 包含了传输协议和编码 (对象序列号) 协议
    • 服务的程序 A 调用另一台服务的程序 B
  3. 除了 gRPC 框架之外, 还有 net/rpc (go 标准库), Thrift (C++), Motan, Dubbo (Java) 等 RPC 框架.
    • 衍生出来的框架一般都具备简单, 通用, 安全, 效率等特点
  4. 是基于 http 开发的, 不同的是 RPC:
    • 是长链接, 不必每次通信都要像 HTTP 一样去 3 次握手什么的, 减少了网络开销
    • 对数据进行深度编码和解码, 减少了对网络带宽的压力
    • 一般都有注册中心, 有丰富的监控管理
    • 发布、下线接口、动态扩展等, 对调用方来说是无感知、统一化的操作
    • 参考: 有了HTTP,为什么还要RPC?

二. gRPC 的用途

  1. 一般应用于大型的系统, 提高性能和效率
  2. gRPC 是微服务架构中, 首选的 RPC 框架之一

三. gRPC 的特点有哪些特点, 为什么要使用它

  1. 支持多种语言
  2. 轻量级, 高性能
    • 序列化支持 Protocol Buffer 和 Json
    • Json 是由 JavaScript 动态语言推广出来的, 特性是随着 JavaScript, 字段和数据类型是可以随意定义的
    • Protocol Buffer 的字段和数据类型是预定义的, 更适合静态语言, 更安全. 另外 Protocol Buffer 是一种语言无关的高性能序列化框架
    • Protocol Buffer 可以加速站点之间数据传输速度, 解决数据传输不规范的问题
  3. 可插拔
  4. IDL
    • 基于文件定义服务, 通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub
  5. 基于标准的 HTTP2 设计
    • 支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性
    • 这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量
  6. 服务而非对象、消息而非引用
    • 有利于解耦
    • 促进微服务的系统间, 粗粒度的消息交互
  7. 负载无关
    • 不同的服务需要使用不同的消息类型和编码
  8. 阻塞式和非阻塞式
    • 支持异步和同步处理在客户端和服务端间交互的消息序列
  9. 元数据交换
    • 常见的横切关注点, 依赖数据交换
  10. 标准化状态码
    • 客户端通常以有限的方式响应 API 调用返回的错误
  11. 良好的设计理念
    • 生态好

四. gRPC 实践

目标: 一步一步实现一个简单的 gRPC 示例

1. 开发环境

  • macOS
  • go 1.15

2. 安装依赖和环境配置

  • 安装 protobuf 工具
# 使用 brew 命令
# 安装 protobuf, 即安装 protoc 可执行文件/命令
brew install protobuf
  • 安装 go-gRPC 工具, 配置环境

参考: Quick start | Go | gRPC

# 安装 Go-gRPC 的编译器插件
# 用于生成 *.pb.go 和 *_grpc.pb.go 两个文件所需的 (在 $GOPATH/bin 下)
go get google.golang.org/protobuf/cmd/protoc-gen-go \
         google.golang.org/grpc/cmd/protoc-gen-go-grpc

# go mod
export GO111MODULE=on  # Enable module mode
# protoc 编译器可以找到的插件路径
export PATH="$PATH:$(go env GOPATH)/bin"

3. 编写和编译 proto

  • sayhello.proto
// 指定使用 proto3 版本
syntax = "proto3";

// 随便定义一个报名
package grpc.service.sayhello;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
  • 编译
# --go_out 参数是指定生成 *.pb.go 文件的路径
# --go-grpc_out 参数是指定生成 *_grpc.pb.go 文件的路径
# -I, 相当于 -IPATH 和 --proto_path, 指定 proto 文件依赖的包所要搜索的路径 (这里没有依赖, 可以不写)
protoc -I. --go_out=. --go-grpc_out=. *.proto
  • 编译报错
protoc-gen-go: unable to determine Go import path for

Please specify either:
    • a "go_package" option in the .proto source file, or
    • a "M" argument on the command line.

See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

--go_out: protoc-gen-go: Plugin failed with status code 1.

要我们定义 go_package 或者 M 命令行参数

  • 选择修改 proto, 定义 go_package
syntax = "proto3";

// ./ 是文件生成的路径
// sayhello_proto 是生成的 .go 文件的包名
// 两者之间, 用 ; 号分隔
// 参考: https://blog.csdn.net/raoxiaoya/article/details/109533734
option go_package = "./;sayhello_proto";

package grpc.service.sayhello;

// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}
  • 重新编译 proto
protoc -I. --go_out=. --go-grpc_out=. *.proto

编译成功

4. 实现服务端

package main

import (
    "context"
    "log"
    "net"

    proto "github.com/-/sayhellogrpc/proto" // 此处隐姓埋名
    "google.golang.org/grpc"
)

const (
    // 绑定的端口
    port = ":8080"
)

type server struct {
    proto.UnimplementedGreeterServer
}

// 实现定义的 SayHello API
// 参数和返回类型, 参考生成的 sayhello_grpc.pb.go 的
func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    msg := in.GetName() + " say hello for gRPC"
    reply := &proto.HelloReply{
        Message: msg,
    }
    return reply, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    srv := grpc.NewServer()
    // 注册服务
    proto.RegisterGreeterServer(srv, &server{})

    log.Println("gRPC server is running...")
    // 起服务
    if err := srv.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

5. 实现客户端

package main

import (
    "context"
    "log"
    "os"
    "time"

    proto "github.com/-/sayhellogrpc/proto" // 此处隐姓埋名
    "google.golang.org/grpc"
)

const (
    address = "localhost:8080"
    content = "xxxx"
)

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()

    c := proto.NewGreeterClient(conn)

    // Contact the server and print out its response.
    name := content
    if len(os.Args) > 1 {
        // 通过命令行获取发送的内容
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &proto.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

go.mod

如果报 undefined: grpc.SupportPackageIsVersion7 这样的错误, 则要修改 google.golang.org/grpc 的版本, v1.32.0 及以上

module github.com/-/sayhellogrpc // 此处隐姓埋名

go 1.15

require (
    github.com/golang/protobuf v1.5.0
    google.golang.org/grpc v1.37.0
    google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 // indirect
    google.golang.org/protobuf v1.26.0
)

文件结构

.
├── client
│   └── main.go
├── go.mod
├── go.sum
├── proto
│   ├── sayhello.pb.go
│   ├── sayhello.proto
│   └── sayhello_grpc.pb.go
└── server
    └── main.go

6. 验证

go run server/main.go

go run client/main.go

go run client/main.go abc

实现参考: https://github.com/grpc/grpc-go/tree/master/examples

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • 写在前面 首先声明这篇文章只是介绍gRPC在Go语言中的使用入门级的文章,不包含多少深入的内容,读者对象是g...
    foundwei阅读 2,625评论 0 5
  • 原文出处:gRPC gRPC分享 概述 gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远...
    小波同学阅读 7,212评论 0 18
  • 原文连接: 一文了解RPC以及gRPC基于Golang和Java的简单实现 一:什么是RPC 简介:RPC:Re...
    贾顺阅读 8,890评论 2 12
  • 1.简介 在gRPC中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,使您...
    第八共同体阅读 1,875评论 0 6
  • gRPC(Remote Procedure Calls) 概述 GRPC是一个高性能、通用的开源RPC框架,基于底...
    鬼鸮阅读 17,910评论 0 1