一个恋爱小故事告诉你什么是gRPC?!

2023-11-10

RPC

对RPC不了解的人,或许会纠结其与TCP、HTTP等的关系。后者是网络传输中的协议,而RPC是一种设计、实现框架,通讯协议只是其中一部分,RPC不仅要解决协议通讯的问题,还有序列化与反序列化,以及消息通知。

一个完整的RPC架构里面包含了四个核心的组件,分别是Client ,Server,ClientOptions以及ServerOptions,这个Options就是RPC需要设计实现的东西。

  • 客户端(Client):服务的调用方。

  • 服务端(Server):真正的服务提供方。

  • 客户端存根(ClientOption):socket管理,网络收发包的序列化。

  • 服务端存根(ServerOption):socket管理,提醒server层rpc方法调用,以及网络收发包的序列化。

RPC的逻辑示意图如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zsHlMmU3-1612838576573)(./image-20210208113707741.png)]

什么是gRPC

gRPC是RPC的一种,它使用Protocol Buffer(简称Protobuf)作为序列化格式,Protocol Buffer是来自google的序列化框架,比Json更加轻便高效,同时基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。用protoc就能使用proto文件帮助我们生成上面的option层代码。

在gRPC中,客户端应用程序可以直接在另一台计算机上的服务器应用程序上调用方法,就好像它是本地对象一样,从而使您更轻松地创建分布式应用程序和服务。

gRPC的调用模型如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGtHTgq9-1612838576577)(./grpc_concept_diagram_00.png)]

适用场景

  • 分布式场景 :gRPC设计为低延迟和高吞吐量通信,非常适用于效率至关重要的轻型微服务。
  • 点对点实时通信: gRPC对双向流媒体提供出色的支持,可以实时推送消息而无需轮询。
  • 多语言混合开发 :支持主流的开发语言,使gRPC成为多语言开发环境的理想选择。
  • 网络受限环境 : 使用Protobuf(一种轻量级消息格式)序列化gRPC消息。gRPC消息始终小于等效的JSON消息。

四种调用方式

学习gRPC使用之前,先介绍一下RPC中的客户端与服务端。在RPC中,服务端会开启服务供客户端调用,每一句RPC调用都是一次客户端发请求到服务器获得相应的过程,中间过程被封装了,看起来像本地的一次调用一样,一次RPC调用也就是一次通讯过程。

RPC调用通常根据双端是否流式交互,分为了单项RPC、服务端流式RPC、客户端流式RPC、双向流PRC四种方式。为了便于大家理解四种grpc调用的应用场景,这里举一个例子,假设你是小超,有一个女朋友叫婷婷,婷婷的每种情绪代表一个微服务,你们之间的每一次对话可以理解为一次PRC调用,为了便于画流程图,RPC请求被封装成client.SayHello,请求包为HelloRequest,响应为HelloReply。

1. 单项 RPC

当你在等婷婷回去吃饭,婷婷在加班时,你们之间的rpc调用可能是这样的:

小超:回来吃饭吗

婷婷:还在加班

这就是单项 RPC,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i2VIawkZ-1612838576579)(./image-20210208113746442.png)]

  • client层调用SayHello接口,把HelloRequest包进行序列化
  • client option将序列化的数据发送到server端
  • server option接收到rpc请求
  • 将rpc请求返回给server端,server端进行处理,将结果给server option
  • server option将HelloReply进行序列化并发给client
  • client option做反序列化处理,并返回给client层
2. 服务端流式 RPC

当你比赛输了给婷婷发消息时:

小超:今天比赛输了

婷婷:没事,一次比赛而已

婷婷:晚上带你去吃好吃的

这就是服务端流式 RPC,即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-notxon7k-1612838576581)(./image-20210208114053248.png)]

  • client层调用SayHello接口,把HelloRequest包进行序列化
  • client option将序列化的数据发送到server端
  • server option接收到rpc请求
  • 将rpc请求返回给server端,server端进行处理,将将数据流给server option
  • server option将HelloReply进行序列化并发给client
  • client option做反序列化处理,并返回给client层
3. 客户端流式 RPC

当你惹婷婷生气的时候:

小超:怎么了,宝贝

小超:别生气了,带你吃好吃的

婷婷:滚

客户端流式 RPC,即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6EP2uFmD-1612838576583)(./image-20210208114110968.png)]

  • client层调用SayHello接口,把HelloRequest包进行序列化
  • client option将序列化的数据流发送到server端
  • server option接收到rpc请求
  • 将rpc请求返回给server端,server端进行处理,将结果给server option
  • server option将HelloReply进行序列化并发给client
  • client option做反序列化处理,并返回给client层
4. 双向流 RPC

当你哄好婷婷时:

小超:今天看了一个超好看的视频

婷婷:什么视频

小超:发给你看看

婷婷:这也叫好看?

双向流 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iyp22RQm-1612838576585)(./image-20210208114138744.png)]

这幅图就不做流程介绍了,读者可以自己试着看图能不能理解过程,相信理解了客户端流RPC和服务端流RPC俩种方式,这里一定可以理解的。

gPRC代码实现

gRPC 使用 Protobuf 作为序列化格式,Protobuf 比 Json 更加轻便高效。与Json一样,它与开发语言和平台无关,具有良好的可扩展性。关于Protobuf 使用请参考官网地址 https://developers.google.com/protocol-buffers 。

下面我们实现Go语言版的四种gRPC调用方式。

1. 单向RPC实现
编写proto
//proto3标准
syntax = "proto3";

//包名
package helloworld;

//定义rpc接口
service Greets {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

//HelloReply协议内容
message HelloReply {
  string name = 1;
  string message = 2;
}

//HelloRequest协议
message HelloRequest {
  string name = 1;
  string message = 2;
}
  1. Greet为定义rpc服务的类名,rpc SayHello (HelloRequest) returns (HelloReply) {}表示定义rpc方法SayHello,传入HelloRequest,返回HelloReply
  2. 进入proto文件夹,运行命令protoc -I . --go_out=plugins=grpc:. ./helloworld.proto在.目录中生成helloworld.pb.go文件
编写server
type Server struct {
}

//实现SayHello接口
func (s *Server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Println(in.Name, in.Message)
	return &pb.HelloReply{Name: "婷婷", Message: "不回来了"}, nil
}

func main() {
	//协议类型以及ip,port
	lis, err := net.Listen("tcp", ":8002")
	if err != nil {
		fmt.Println(err)
		return
	}

	//定义一个rpc的server
	server := grpc.NewServer()
	//注册服务,相当与注册SayHello接口
	pb.RegisterGreetsServer(server, &Server{})
	//进行映射绑定
	reflection.Register(server)

	//启动服务
	err = server.Serve(lis)
	if err != nil {
		fmt.Println(err)
		return
	}
}
  1. pb为proto文件生成的文件别名

  2. 定义server结构体作为rpc调用的结构体,这个结构体必须实现SayHello这个接口

  3. listen -> grpc.NewServer() -> pb.RegisterGreetsServer(server, &Server{}) -> s.Serve(lis)

编写client
func main() {
	//创建一个grpc连接
	conn, err := grpc.Dial("localhost:8002", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()

	//创建RPC客户端
	client := pb.NewGreetsClient(conn)
	//设置超时时间
	_, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// 调用方法
	reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "小超", Message: "回来吃饭吗"})
	if err != nil {
		log.Fatalf("couldn not greet: %v", err)
	}
	log.Println(reply.Name, reply.Message)
}
  1. grpc.Dial(“localhost:8002”, grpc.WithInsecure())连接到服务器,grpc.WithInsecure()取消明文检测
  2. context.WithTimeout(context.Background(), time.Second)设置超时时间
  3. c := pb.NewGreetsClient(conn)创建rpc调用的客户端
  4. c.SayHello(context.Background(), &pb.HelloRequest{Name: name})进行rpc调用
抽象接口

其实也就是要实现这个接口,因为俩边都是单项调用,所以调用和实现的接口都是这个

type GreetsClient interface {
   SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
2. 服务端流RPC
编写proto
//proto3标准
syntax = "proto3";

//包名
package helloworld;

//定义rpc接口

service Greet{
  rpc SayHello (HelloRequest) returns(stream HelloReply) {}
}

//HelloReply协议内容
message HelloReply {
  string name = 1;
  string message = 2;
}

//HelloRequest协议
message HelloRequest {
  string name = 1;
  string message = 2;
}

相比于单项RPC调用,因为是客户端流,所以在HelloRequest多了一个stream

编写server
type Server struct {
}

//实现rpc接口
func (*Server) SayHello(request *pb.HelloRequest, server pb.Greet_SayHelloServer) error {
	fmt.Println(request)
	var err error
	for i := 0; i < 2; i++ {
		if i == 0 {
			err = server.Send(&pb.HelloReply{Name: "小超", Message: "没事,一次比赛而已"})
		} else {
			err = server.Send(&pb.HelloReply{Name: "小超", Message: "晚上带你去吃好吃的"})
		}
		if err != nil {
			fmt.Println(err)
			return err
		}
	}
	return nil
}

func main() {
	//协议类型以及ip,port
	listen, err := net.Listen("tcp", ":8002")
	if err != nil {
		fmt.Println(err)
		return
	}

	//定义一个rpc的server
	s := grpc.NewServer()
	//注册服务,相当与注册SayHello接口
	pb.RegisterGreetServer(s, &Server{})
	//进行映射绑定
	reflection.Register(s)

	//启动服务
	err = s.Serve(listen)
	if err != nil {
		fmt.Println(err)
	}
}
编写client

client发送的是一个流,与单项RPC方式不同,他通过rpc调用获得的是一个流传输对象greetClient,可以用流传输对象不停的往对端发送数据

func main() {
	//创建一个grpc的连接
	grpcConn, err := grpc.Dial("127.0.0.1"+":8002", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		return
	}

	//创建grpc的client
	client := pb.NewGreetClient(grpcConn)
	//设置超时时间
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	//调用rpc方法,获得流接口
	res, err := client.SayHello(ctx, &pb.HelloRequest{Name: "小超", Message: "今天比赛输了"})
	if err != nil {
		fmt.Println(err)
		return
	}

	//循环接收数据
	for {
		recv, err := res.Recv()
		if err != nil {
			fmt.Println(err)
			break
		}
		fmt.Println(recv)
	}
}
抽象接口

服务器要实现的接口

// GreetsServer is the server API for Greets service.
type GreetsServer interface {
   SayHello(Greets_SayHelloServer) error
}

客户端调用的接口

type GreetsClient interface {
   SayHello(ctx context.Context, opts ...grpc.CallOption) (Greets_SayHelloClient, error)
}
3. 服务端流RPC
编写proto
//proto3标准
syntax = "proto3";

//包名
package helloworld;

//定义rpc接口
service Greets{
  rpc SayHello (stream HelloRequest) returns (HelloReply) {}
}

//HelloReply协议内容
message HelloReply {
  string name = 1;
  string message = 2;
}

//HelloRequest协议
message HelloRequest {
  string name = 1;
  string message = 2;
}
编写服务器
type Server struct{}

//实现rpc方法,直到对端调用CloseAndRecv就会读到EOF
func (*Server) SayHello(in pb.Greets_SayHelloServer) error {
	for {
		recv, err := in.Recv()
		//接收完数据之后发送响应
		if err == io.EOF {
			err := in.SendAndClose(&pb.HelloReply{Name: "婷婷", Message: "滚"})
			if err != nil {
				return err
			}
			return nil
		} else if err != nil {
			return err
		}
		fmt.Println(recv)
	}
}

func main() {
	//绑定协议,ip以及端口
	lis, err := net.Listen("tcp", ":8002")
	if err != nil {
		fmt.Println("failed to listen: %v", err)
		return
	}

	//创建一个grpc服务对象
	server := grpc.NewServer()
	//注册rpc服务
	pb.RegisterGreetsServer(server, &Server{})
	//注册服务端反射
	reflection.Register(server)

	//启动服务器
	err = server.Serve(lis)
	if err != nil {
		fmt.Println(err)
		return
	}
}
编写客户端
func main() {
	//创建一个grpc的连接
	grpcConn, err := grpc.Dial("127.0.0.1"+":8002", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		return
	}

	//创建grpc的client
	client := pb.NewGreetsClient(grpcConn)

	//设置超时时间
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	//调用rpc方法,得到一个客户端用于循环发送数据
	greetClient, err := client.SayHello(ctx)

	if err != nil {
		fmt.Println("sayHello error")
		fmt.Println(err)
		return
	}

	maxCount := 2
	curCount := 0

	//循环发送
	//调了CloseAndRecv()服务端就会读到EOF,server端可根据是否读到EOF来判断客户端是否将数据发完
	for {
		if curCount == 0 {
			err = greetClient.Send(&pb.HelloRequest{Name: "小超", Message: "怎么了,宝贝"})
		} else {
			err = greetClient.Send(&pb.HelloRequest{Name: "小超", Message: "别生气了,带你吃好吃的"})
		}

		if err != nil {
			fmt.Println("send error")
			fmt.Println(err)
			return
		}
		curCount += 1
		if curCount >= maxCount {
			res, err := greetClient.CloseAndRecv()
			if err != nil {
				fmt.Println(err)
				break
			}
			fmt.Println(res)
			break
		}
	}
}

抽象接口

客户端接口

type GreetsClient interface {
   SayHello(ctx context.Context, opts ...grpc.CallOption) (Greets_SayHelloClient, error)
}

服务器接口

// GreetsServer is the server API for Greets service.
type GreetsServer interface {
   SayHello(Greets_SayHelloServer) error
}

双向流RPC

双向流RPC就交给读者自己练习吧,相信理解了单项RPC,客户端流RPC,服务端流RPC三种传输方式,写出双向流RPC应该没任何问题。

实现总结

其实弄懂了单项RPC、服务端流式RPC、客户端流式RPC、双向流PRC四种grpc应用场景,实现起来非常容易

  1. 根据应用场景选择好哪种gRPC服务
  2. 写好proto文件,用protoc生成.pb.go文件
  3. 服务端实现接口->listen -> grpc.NewServer() -> pb.RegisterGreetsServer(server, &Server{}) -> s.Serve(lis)
  4. 客户端grpc.Dial->pb.NewGreetsClient->context.WithTimeout->client.SayHello(调用接口)->如果是流传输则循环读取数据

欢迎关注我的公众号,查看超超后续内容更新!
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一个恋爱小故事告诉你什么是gRPC?! 的相关文章

随机推荐

  • 了解JVM(JavaEE初阶系列19)

    目录 前言 1 JVM是如何运行的 2 JVM中的内存区域划分 3 JVM的类加载机制 3 1JVM加载机制的五大步骤 3 1 1加载 3 1 1验证 3 1 1准备 3 1 1解析 3 1 1初始化 3 2总结 3 3JVM启动时机 3
  • Chapter 12 贝叶斯网络

    1 概率公式 条件概率 全概率公式 贝叶斯公式 Bayes 2 贝叶斯公式 2 1 贝叶斯公式带来的思考 给定某些样本 在这些样本中计算某结论出现的概率 即 贝叶斯公式 样本给定 则对于任何是常数 仅为归一化因子 忽略 若这些结论的先验概率
  • 在 Windows 操作系统上安装和配置

    1 下载安装包以获取最新版本 stable 的 Flutter SDK https storage flutter io cn flutter infra releases stable windows flutter windows 1
  • Pycharm修改python解释器

    Pycharm修改python解释器 在python学习过程中 遇到了这样的一个问题 早先通过pip安装的库在pycharm中无法使用 例如之前学习的numpy库在pycharm中无法调用 下面给出两个解决办法 1 通过pycharm自带的
  • 还在为不知道怎么学习网络安全而烦恼吗?这篇文带你从入门级开始学习网络安全—认识网络安全

    随着网络安全被列为国家安全战略的一部分 这个曾经细分的领域发展提速了不少 除了一些传统安全厂商以外 一些互联网大厂也都纷纷加码了在这一块的投入 随之而来的吸引了越来越多的新鲜血液不断涌入 不同于Java C C 等后端开发岗位有非常明晰的学
  • [转]笔试面试中问到的常见问题总结

    面试的三大重点 第一个是项目 项目这个应该挺好说的 只要自己有这方面的准备 第二个是数据结构和算法 这个无论在笔试还是在面试中都很重要 第三个如果面C 方向的话 C 基础很重要 接下来谈一下后二者各自的一些常见问题 一 数据结构和算法 链表
  • 基于Matlab的图像加噪滤波处理和图像边缘检测

    目录 1 1 原始图像展示 1 2 灰度图展示 1 3 高斯加噪图展示 1 4 均值滤波图展示 1 5 中值滤波图展示 1 6 高斯滤波图展示 对比三种滤波效果 2 1 Sobel边缘检测图展示 2 2 Canny边缘检测图展示 对比两种边
  • JAVA8 十大新特性浅谈

    本教程将Java8的新特新逐一列出 并将使用简单的代码示例来指导你如何使用默认接口方法 lambda表达式 方法引用以及多重Annotation 之后你将会学到最新的API上的改进 比如流 函数式接口 Map以及全新的日期API Java
  • matlab中plot函数用法

    线条 颜色等参数 1 简单的2维直线图 plot x y 同一坐标显示n条线 plot x y1 x y2 x 0 pi 10 2 pi y sin x figure hold on plot x y 2 plot X X是矩阵 表示矩阵的
  • 导入Excel文件的各种常见方法

    1 为了简单起见 可以考虑将包括扩展名为xls xlsx的各种Excel文件在Excel WPS表格中另存为CSV格式 更为方便和易于读取 直接使用pandas的read csv方法即可读取 如另存为 读取方法为 2 直接读取Excel文件
  • 在 QSS 中设置 Qt Widget 属性

    在 QSS 中设置 Qt Widget 属性 默认样式 QSS 自定义属性与 Qt 类型对应 使用枚举 使用 QSS 属性选择器 代码实例 在 QSS 中设置 Qt Widget 属性 Q OBJECT 添加自定义属性到 Qt动态属性系统
  • 使用maven模板快速生成项目

    1 Archetype介绍 Archetype是一个Maven项目的模板工具包 它定义了一类项目的基本架构 Archetype为开发人员提供了创建Maven项目的模板 同时它也可以根据已有的Maven项目生成参数化的模板 通过archety
  • Linux/Mac go版本升级

    文章目录 背景 卸载当前版本 安装最新版本 解压下载的文件 验证生效 背景 Mac上go版本为1 10 在1 11以后加入了go mod等特性 所以要更新到最新的go版本 此方法适用于Mac Linux 卸载当前版本 只需要删除 usr l
  • STM32F103C8T6使用备忘录

    1 STM32端口配置寄存器 CRH寄存器 用于高位I O口 即GPIOX8 GPIOX15 X可以是A B C D E等 每个IO口有两个寄存器 分别是CNFxx 1 0 和MODExx 1 0 共占四位二进制or一位十六进制 1 CNF
  • Nginx下开启php-fpm的错误提示

    Nginx下开启php fpm的错误提示 1 php fpm的作用 nginx本身不能处理PHP 它只是个web服务器 当接收到请求后 如果是php请求 则发给php解释器处理 并把结果返回给客户端 nginx一般是把请求发fastcgi管
  • hausdorff matlab,matlab练习程序(Hausdorff距离)

    Hausdorff距离是根据Hausdorff 1868 1942 命名的 Hausdorff距离是指某一集合中离另一集合最近点的所有距离的最大值 通常用如下公式表示 需要注意的是h A B 和h B A 通常不相等 所以可以定义更一般的H
  • React Context源码是怎么实现的呢

    目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api 大多数项目不会直接使用 createContext 然后向下面传递数据 而是采用第三方库 react redux 想想项目中是不是经常会用到 connect Com
  • 什么是数据湖技术数据湖和数据仓库的区别(好文转载)

    原文链接 什么是数据湖技术 xuzhujack 博客园 什么是数据湖 有什么用 终于有人讲明白了 大数据 CSDN博客 数据湖 Data Lake 是Pentaho公司创始人及CTO James Dixon于2010年10月在2010年10
  • Verilog文件的读取(fscanf)和写入(fwrite)方法

    在写testbench时 经常会用到文件的读取 下面示例了文件读取和写入的方法 文件读取 图中第一行定义一个文件句柄 由于打开的文件中一行中有两个10bit的十进制数据 所以定义了2个reg变量 第6行到12行就是文件的读取过程 使用的系统
  • 一个恋爱小故事告诉你什么是gRPC?!

    RPC 对RPC不了解的人 或许会纠结其与TCP HTTP等的关系 后者是网络传输中的协议 而RPC是一种设计 实现框架 通讯协议只是其中一部分 RPC不仅要解决协议通讯的问题 还有序列化与反序列化 以及消息通知 一个完整的RPC架构里面包