RPC 的定义这里就不再说,看文章的同学都是成熟的开发。gRPC 是 Google 开源的高性能跨语言的 RPC 方案,该框架的作者 Louis Ryan 阐述了设计这款框架的动机,有兴趣的同学可以看看: gRPC的动机和设计原则

另一个值得一提的问题是,众所周知 RPC 框架基本都是直接基于 TCP 协议自研数据结构和编解码方式,但是 gRPC 却完全不是这样,它使用 HTTP/2 协议来传输数据。基于这一点来说, yRPC 肯定就不是性能最佳的那一款 RPC 框架。但是在不追求顶格 QPS 的前提下,通用性和兼容性也是不可忽略的要素。

如果要探究为什么 gRPC 要选择使用 HTTP/2 作为底层协议,这个其实也很好解释。HTTP 协议作为 Web 端标准协议在 Google 内被大规模广泛使用。为了解决 1.x 的问题,Google 将自研的 SPDY 协议公开并推动基于 SPDY 的 HTTP/2 协议。所以 gRPC 从兼容性和推广 HTTP/2 两个角度来说有充足理由选择 HTTP/2,何况基于 HTTP/2 的一些新特性也会让实现方案上少写很多代码。

这里衍生出另一个基础问题:既然底层使用 HTTP/2,那为啥还要用 RPC,不直接用 Restful 的方式更直接吗。RPC 通常使用二进制编码来压缩消息的内容,Restful 更多的使用 JSON 格式,消息体中的冗余数据比较多,性能不如 RPC。

说了这么多题外话下面我们还是看一下作为业内使用率比较高的一款 RPC 框架是如何跑起来的。

开始前的准备

因为 gRPC 使用 Protocol Buffers 做为协议传输编码格式,我们先安装 Protocol Buffers 。具体安装大家可以自行搜索教程,我这里使用 mac 的 brew 来安装。

因为原生的 Protobuf 并不支持将 .proto 文件编译为 Go 源码,后面 Go 官方单独开发了一款编译插件:

go get -u github.com/golang/protobuf/protoc-gen-go

无论你是通过 go get 的方式安装还是通过别的方式,确保它在 $GOPATH/bin 中以便编译器protoc能够找到它。通过这个插件你可以将 .proto 文件编译为 Go 文件。并且在 protoc-gen-go 插件中还提供了专门的 gRPC 编译相关的支持,可以将 pb 文件中定义的 rpc service 方法渲染成 Go 对象。

这里对于安装过程简单介绍过去,因为它并不是本文介绍的要点,但是对于大家来说肯定不是那么好绕过去的,安装过程出现问题搜索一下即可,已经有人替你们踩过坑!

声明:以下代码都可以在 Github 仓库中找到。


Hello World 入门

我们先定义一个用于本次测试的 pb 格式:

syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本
option go_package = "/"; package models.pb; // 包名 service Calculate {
rpc Sum (stream SumRequest) returns (SumResponse) {}
} message SumRequest {
int64 num = 1;
} message SumResponse {
int64 result = 1;
}

执行如下命令生成对应的 pb 文件:

 protoc --go_out=plugins=grpc:. *.proto

执行完成之后就会在当前目录下生成 HelloWorld.pb.go 文件。

接下来开始编写 服务端和客户端相关的代码。首先引入 gRPC 的包:

go get -u google.golang.org/grpc

服务端代码如下:

package grpcTest

import (
"fmt"
"net"
"testing" pb "gorm-demo/models/pb"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
) type server struct{} func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
} func TestGrpcServer(t *testing.T) {
// 监听本地的8972端口
lis, err := net.Listen("tcp", ":8972")
if err != nil {
fmt.Printf("failed to listen: %v", err)
return
}
s := grpc.NewServer() // 创建gRPC服务器
pb.RegisterGreeterServer(s, &server{}) // 在gRPC服务端注册服务 reflection.Register(s) //在给定的gRPC服务器上注册服务器反射服务
// Serve方法在lis上接受传入连接,为每个连接创建一个ServerTransport和server的goroutine。
// 该goroutine读取gRPC请求,然后调用已注册的处理程序来响应它们。
err = s.Serve(lis)
if err != nil {
fmt.Printf("failed to serve: %v", err)
return
}
}

接下来是客户端:

package grpcTest

import (
"fmt"
"testing" pb "gorm-demo/models/pb"
"golang.org/x/net/context"
"google.golang.org/grpc"
) func TestGrpcClient(t *testing.T) {
// 连接服务器
conn, err := grpc.Dial(":8972", grpc.WithInsecure())
if err != nil {
fmt.Printf("faild to connect: %v", err)
}
defer conn.Close() c := pb.NewGreeterClient(conn)
// 调用服务端的SayHello
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: "CN"})
if err != nil {
fmt.Printf("could not greet: %v", err)
}
fmt.Printf("Greeting: %s !\n", r.Message)
}

分别启动服务端和客户端程序可以看到能够正常的收发消息。

更高端的技能

gRPC 主要有 4 种请求和响应模式,分别是简单模式(Simple RPC)服务端流式(Server-side streaming RPC)客户端流式(Client-side streaming RPC)、和双向流式(Bidirectional streaming RPC)

  • 简单模式(Simple RPC):客户端发起请求并等待服务端响应,就是普通的 Ping-Pong 模式。
  • 服务端流式(Server-side streaming RPC):服务端发送数据,客户端接收数据。客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。
  • 客户端流式(Client-side streaming RPC):与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。
  • 双向流式(Bidirectional streaming RPC):双方使用读写流去发送一个消息序列,两个流独立操作,双方可以同时发送和同时接收。

上面演示的代码就是简单模式,客户端发起请求等待服务器回应。下面的三种数据流模式第一眼看上去的时候感觉很难理解,HTTP 协议在我们的认识中就是 Ping-Pong 模式,请求和应答。流式处理是怎么发生的。

建立在 HTTP 基本原理的基础上, gRPC 对请求处理做了一些包装。当一次请求的数据量过大会有两个问题,第一是超时,第二可能超过网络请求最大包长度限制。对于这种情况下的问题要么业务侧做分解将数据拆分成多次请求返回,要么就是通过关键字的方式返回关键字由业务侧根据关键字二次查询详细数据。

流式处理这个概念相当于逆天改命,跳过上面两种基本的上层处理方案,从底层提供一次请求,多次返回的功能。客户端发起一次流式处理请求,服务端分多次将数据分包返回给客户端。

流式处理不是空中花园还是需要有底层支持,因为 gRPC 是基于 HTTP/2 来传输,HTTP/2 本身就有二进制分帧的概念。通常一个请求或响应会被分为一个或多个帧传输,流则表示已建立连接的虚拟通道,可以传输多次请求或响应。每个帧中包含 Stream Identifier,标志所属流。HTTP/2 通过流与帧实现多路复用,对于相同域名的请求,通过 Stream Identifier 标识可在同一个流中进行,从而减少连接开销。

了解了这些理论之后我们先来实践一下,先看接口定义:

syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本
option go_package = "/"; package models.pb; // 包名 service BaseService { //计算求和的方式来测试服务端流
rpc Sum (stream SumRequest) returns (SumResponse) {}
// 服务端流式响应
rpc ServerStream (StreamRequest) returns (stream StreamResponse){}
// 客户端流式请求
rpc ClientStream (stream StreamRequest) returns (StreamResponse){}
// 双向流式
rpc Streaming (stream StreamRequest) returns (stream StreamResponse){}
} message StreamRequest{
string input = 1;
} message StreamResponse{
string output = 1;
} message SumRequest {
int64 num = 1;
} message SumResponse {
int64 result = 1;
}

上面这个 pb 里面定义了 4 个接口,其中第一个求和的接口就是让你更好理解流式请求的概念,只有读完整个流数据之后才会结束计算。我们来看一下实现。

客户端流式响应测试 - Sum 求和:

客户端代码:

package normal

import (
"fmt"
"testing" "golang.org/x/net/context"
"google.golang.org/grpc"
pb "gorm-demo/models/pb"
) func TestGrpcClient(t *testing.T) {
// 连接服务器
conn, err := grpc.Dial(":8972", grpc.WithInsecure())
if err != nil {
fmt.Printf("faild to connect: %v", err)
}
defer conn.Close() c := pb.NewBaseServiceClient(conn)
// 调用Sum
sumCli, err := c.Sum(context.Background())
if err != nil {
panic("sum cli err")
}
sumCli.Send(&pb.SumRequest{Num: int64(1)})
sumCli.Send(&pb.SumRequest{Num: int64(2)})
sumCli.Send(&pb.SumRequest{Num: int64(3)})
sumCli.Send(&pb.SumRequest{Num: int64(4)}) recv, err := sumCli.CloseAndRecv()
if err != nil {
fmt.Printf("send sum request err: %v", err)
} fmt.Printf("sum = : %v !\n", recv.Result)
}

这里是客户端调用 Sum 方法后分批多次发送请求数据给服务端,等发送完成服务端会返回一个最终的结果。

服务端代码:

package normal

import (
"fmt"
"io"
"net"
"strconv"
"testing" "google.golang.org/grpc"
"google.golang.org/grpc/reflection"
pb "gorm-demo/models/pb"
) type server struct{} func TestGrpcServer(t *testing.T) {
// 监听本地的8972端口
lis, err := net.Listen("tcp", ":8972")
if err != nil {
fmt.Printf("failed to listen: %v", err)
return
}
s := grpc.NewServer() // 创建gRPC服务器
pb.RegisterBaseServiceServer(s, &server{}) // 在gRPC服务端注册服务 reflection.Register(s) //在给定的gRPC服务器上注册服务器反射服务
// Serve方法在lis上接受传入连接,为每个连接创建一个ServerTransport和server的goroutine。
// 该goroutine读取gRPC请求,然后调用已注册的处理程序来响应它们。
err = s.Serve(lis)
if err != nil {
fmt.Printf("failed to serve: %v", err)
return
}
} //sum案例--客户端流式处理
func (*server) Sum(req pb.BaseService_SumServer) (err error) {
var sum int64 = 0
for {
reqObj, err := req.Recv()
if err == io.EOF {
fmt.Printf("Recv Sum err: %v", err)
req.SendAndClose(&pb.SumResponse{Result: sum})
return nil
} else if err == nil {
fmt.Printf("get client request param = %v", reqObj.Num)
sum += reqObj.Num
} else {
return err
}
}
} // 服务端流式处理
func (s *server) ServerStream(in *pb.StreamRequest, stream pb.BaseService_ServerStreamServer) error {
input := in.Input
for _, s := range input {
stream.Send(&pb.StreamResponse{Output: string(s)})
}
return nil
} // 客户端流式响应 - 服务端逻辑
func (s *server) ClientStream(stream pb.BaseService_ClientStreamServer) error {
output := ""
for {
r, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.StreamResponse{Output: output})
}
if err != nil {
fmt.Println(err)
}
output += r.Input
}
} // 双向流式处理
func (s *server) Streaming(stream pb.BaseService_StreamingServer) error {
for n := 0; ; {
res, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
v, _ := strconv.Atoi(res.Input)
n += v
stream.Send(&pb.StreamResponse{Output: strconv.Itoa(n)})
}
}

Sum 方法中服务端等待获取客户端的请求数据,直到遇到最后一个 EOF 之后将计算的结果返回给客户端,本次请求结束。

服务端流式响应测试

与客户端流式响应相反,服务端流式响应就是服务端持续发送数据流,客户端接收并最终结束流。

客户端逻辑:

package normal

import (
"fmt"
"testing" "golang.org/x/net/context"
"google.golang.org/grpc"
pb "gorm-demo/models/pb"
) func TestGrpcClient(t *testing.T) {
// 连接服务器
conn, err := grpc.Dial(":8972", grpc.WithInsecure())
if err != nil {
fmt.Printf("faild to connect: %v", err)
}
defer conn.Close() c := pb.NewBaseServiceClient(conn)
clientStream(c, "我收到了服务端的请求数据拉")
} // 客户端
func clientStream(client pb.BaseServiceClient, input string) error {
stream, _ := client.ClientStream(context.Background())
for _, s := range input {
fmt.Println("Client Stream Send:", string(s))
err := stream.Send(&pb.StreamRequest{Input: string(s)})
if err != nil {
return err
}
}
res, err := stream.CloseAndRecv()
if err != nil {
fmt.Println(err)
}
fmt.Println("Client Stream Recv:", res.Output)
return nil
}

客户端现在变为接收数据方。

服务端现在从客户端收到请求之后只管去处理,处理的结果分多次发给客户端即可:

//服务端流式处理
serverStream(c, &pb.StreamRequest{Input: "我是一只小老虎"}) // 服务端流式处理
func serverStream(client pb.BaseServiceClient, r *pb.StreamRequest) error {
fmt.Println("Server Stream Send:", r.Input)
stream, _ := client.ServerStream(context.Background(), r)
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
fmt.Println("Server Stream Recv:", res.Output)
}
return nil
}

大家可以运行一下看看效果。

双向流处理

顾名思义就是服务端和客户端都可以发送和接收消息。

服务端代码:


// 双向流式处理
func (s *server) Streaming(stream pb.BaseService_StreamingServer) error {
for n := 0; ; {
res, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
v, _ := strconv.Atoi(res.Input)
n += v
stream.Send(&pb.StreamResponse{Output: strconv.Itoa(n)})
}
}

客户端代码:


// 双向流式处理
func streaming(client pb.BaseServiceClient) error {
stream, _ := client.Streaming(context.Background())
for n := 0; n < 10; n++ {
fmt.Println("Streaming Send:", n)
err := stream.Send(&pb.StreamRequest{Input: strconv.Itoa(n)})
if err != nil {
return err
}
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
fmt.Println("Streaming Recv:", res.Output)
}
stream.CloseSend()
return nil
}

可以看到双方都能接收和发送消息。

入门篇就先说这么多,都是实操案例。大家先上手看看如何玩起来再去慢慢了解更深层次的东西吧。

跟我一起学Go系列:gRPC 入门必备的更多相关文章

  1. 跟我一起学 Go 系列:gRPC 拦截器

    Go gRPC 学习系列: 跟我一起学Go系列:gRPC 入门必备 第一篇内容我们已经基本了解到 gRPC 如何使用 .对应的三种流模式.现在已经可以让服务端和客户端互相发送消息.本篇仍然讲解功能性的 ...

  2. 跟我一起学Go系列:Go gRPC 安全认证机制-SSL/TLS认证

    Go gRPC 系列: 跟我一起学Go系列:gRPC 拦截器使用 跟我一起学Go系列:gRPC 入门必备 第一篇入门说过 gRPC 底层是基于 HTTP/2 协议的,HTTP 本身不带任何加密传输功能 ...

  3. 跟我一起学Go系列:Go gRPC 安全认证方式-Token和自定义认证

    Go gRPC 系列: 跟我一起学Go系列:gRPC安全认证机制-SSL/TLS认证 跟我一起学 Go 系列:gRPC 拦截器使用 跟我一起学 Go 系列:gRPC 入门必备 接上一篇继续讲 gRPC ...

  4. .net基础学java系列(四)Console实操

    上一篇文章 .net基础学java系列(三)徘徊反思 本章节没啥营养,请绕路! 看视频,不实操,对于上了年龄的人来说,是记不住的!我已经看了几遍IDEA的教学视频: https://edu.51cto ...

  5. 学Android开发,入门语言java知识点

    学Android开发,入门语言java知识点 Android是一种以Linux为基础的开源码操作系统,主要使用于便携设备,而linux是用c语言和少量汇编语言写成的,如果你想研究Android,就去学 ...

  6. 跟着鸟哥学Linux系列笔记3-第11章BASH学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 跟着鸟哥学Linux系列笔记2-第10章VIM学习 认识与学习bash 1. ...

  7. 跟着鸟哥学Linux系列笔记2-第10章VIM学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 常用的文本编辑器:Emacs, pico, nano, joe, vim VI ...

  8. 跟着鸟哥学Linux系列笔记0-如何解决问题

    跟着鸟哥学Linux系列笔记0-扫盲之概念 在发生问题怎么处理: 1.  在自己的主机.网络数据库上查询How-To或FAQ -Linux 自身的文件数据: /usr/share/doc -CLDP中 ...

  9. 跟着鸟哥学Linux系列笔记1

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 装完linux之后,接下来一步就是进行相关命令的学习了 第五章:首次登录与在线求助man page 1. X ...

随机推荐

  1. LZZY高级语言程序设计之输入秒数并用时钟的方式表达

    import java.util.Scanner;public class MQ5 { public static void main(String[] args) { Scanner sc = ne ...

  2. 攻防世界 reverse elrond32

    tinyctf-2014 elrond32 1 int __cdecl main(int a1, char **arg_input) 2 { 3 if ( a1 > 1 && c ...

  3. pycharm在debug时总是报UnicodeDecodeError

    1,原文链接 解决pycharm run 正常 debug 报 UnicodeDecodeError 错误的问题 2,解决方法 首先尝试 如果上面还不行

  4. Windows + Jenkins + .NetFramework + SVN 持续部署

    Windows + Jenkins + .NetFramework + SVN 持续部署 环境准备 服务端环境 安装 Windows 服务器 1.阿里云购买临时服务器 阿里云:https://www. ...

  5. Distributed | MapReduce

    最近终于抽出时间开始学习MIT 6.824,本文为我看MapReduce论文和做lab后的总结. [MapReduce英文论文] lab要用到go语言,这也是我第一次接触.可以参考go语言圣经学习基本 ...

  6. Day1---Java 基本数据类型 - 四类八种 --九五小庞

    一.Java四大数据类型分类 1.整型 byte .short .int .long 2.浮点型 float . double 3.字符型 char 4.布尔型 boolean 二.八种基本数据类型 ...

  7. 死磕Spring之AOP篇 - Spring AOP常见面试题

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  8. 研发效率破局之道 Facebook工作法

    如果你问中国和美国互联网公司都有什么差别,很多人会回答:低效加班文化.最近爆出的996大讨论,通过糙快猛打拼和996加班去抢占市场获得机会的成功案例越来越少.至此,只有提高效能才是出路. 由于软件开发 ...

  9. 通过Dapr实现一个简单的基于.net的微服务电商系统(三)——一步一步教你如何撸Dapr

    目录:一.通过Dapr实现一个简单的基于.net的微服务电商系统 二.通过Dapr实现一个简单的基于.net的微服务电商系统(二)--通讯框架讲解 三.通过Dapr实现一个简单的基于.net的微服务电 ...

  10. 自动化kolla-ansible部署ubuntu20.04+openstack-victoria之ceph部署-07

    自动化kolla-ansible部署ubuntu20.04+openstack-victoria之ceph部署-07 欢迎加QQ群:1026880196 进行交流学习 近期我发现网上有人转载或者复制原 ...