浅谈RPC协议

RPC协议

  • RPC简介
  • 为啥需要RPC
  • RPC的调用过程
  • gRPC
  • ProtoBuffer
  • gRPC实战

RPC简介

RPC(Remote Procedure Call Protocol)远程过程调用协议,目标就是让远程服务调用更加简单、透明。RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节,服务调用者可以像调用本地接口一样调用远程的服务提供的接口,而不需要关心底层通信细节和调用过程。
在这里插入图片描述
其大致过程如上图所示。

为啥需要RPC

当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。我们平时写的项目大多数所有的模块都在一起,部署一台服务器上来,但是这也有几个问题:

  • 单台服务器的硬件资源有限,能够承受的并发量并不高
  • 任意模块的修改,都会导致整个项目代码重写编译,有可能你你只修改了只行代码并且至少一个模块的,当然你肯定只想编译这一个模块的代码就行,但是事实上整个项目需要重新编译。
  • 系统当中有些模块属于CPU密集型,有些模块是I/O密集型的,造成各模块对硬件资源的需求不一样的.当我们把这些模块打包发在同一台机器上,我们就只能综合整个模块选择内存和CPU以及网路带宽,没有办法针对每个模块选择其合适的硬件资源。

此时可以将公共业务逻辑抽离出来以及将服务进行模块拆分,将之组成独立的服务 Service 应用,而原有的、新增的应用都可以与那些独立的 Service 应用 交互,以此来完成完整的业务功能,所以我们急需一种高效的应用程序之间的通讯手段来完成这种需求,RPC 大显身手的时候来了。
但是这也有一些我们需要思考的点:

  • 大软件的模块怎么进行划分,各个模块之间可能会实现大量重复的代码
  • 各个模块之间改怎么访问?这使用的就是RPC来实现。

RPC的调用过程

要让网络通信细节对使用者透明,我们需要对通信细节进行封装,我们先看下一个 RPC 调用的流程涉及到哪些通信细节:
在这里插入图片描述

  • 服务消费方(client)调用以本地调用方式调用服务;
  • client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
  • client stub找到服务地址,并将消息发送到服务端;
  • server stub收到消息后进行解码;
  • server stub根据解码结果调用本地的服务;
  • 本地服务执行并将结果返回给 server stub;
  • server stub将返回结果打包成消息并发送至消费方;
  • client stub接收到消息,并进行解码;
  • 服务消费方得到最终结果。

而RPC的目标就是要 2~8(上面的2-8) 这些步骤都封装起来,让用户对这些细节透:
在这里插入图片描述

gRPC

1.grpc 的相关概念

什么是gRPC? gRPC 是一个高性能、通用的开源 RPC 框架,其由 Google 2015 年主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf 序列化协议开发,且支持众多开发语言。
由于是开源框架,通信的双方可以进行二次开发,所以客户端和服务器端之间的通信会更加专注于业务层面的内容,减少了对由 gRPC 框架实现的底层通信的关注。

2.gRPC的特点

  • 跨语言使用,支持 C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP 等编程语言;
  • 基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
  • 通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
  • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性 能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能;

其交互过程大概如下:
在这里插入图片描述
1.交换机在开启 gRPC 功能后充当 gRPC 客户端的角色,采集服务器充当 gRPC 服务器角色;

2.交换机会根据订阅的事件构建对应数据的格式(GPB/JSON),通过 Protocol Buffers 进行编写 proto 文件,交换机与服务器建立 gRPC 通道,通过 gRPC 协议向服务器发送请求消息;

3.服务器收到请求消息后,服务器会通过 Protocol Buffers 解译 proto 文件,还原出最先定义好格式的数据结构,进行业务处理;

4.数据处理完后,服务器需要使用 Protocol Buffers 重编译应答数据,通过 gRPC 协议向交换机发送应答消息;

5.交换机收到应答消息后,结束本次的 gRPC 交互。

ProtoBuffer

ProtoBuffer 是一种更加灵活、高效的数据格式,与 XML、JSON 类似,在一些高性能且对响应速度有要求的数据传输场景非常适用。
ProtoBuffer 在 gRPC 的框架中主要有三个作用:

  • 定义数据结构和字段
  • 定义服务的接口以及生成客户端的Stub(个人理解就是一个客户端类)
  • 通过序列化和反序列化方式提升传输效率。

为什么 ProtoBuf 会提高传输效率呢?
我们知道使用 XML、JSON 进行数据编译时,数据文本格式更容易阅读,但进行数据交换时,设备就需要耗费大量的 CPU 在 I/O 动作上,自然会影响整个传输速率。
Protocol Buffers 不像前者,它会将字符串进行序列化后再进行传输,即二进制数据。在这里不详细说明,有兴趣的可以看博客的另外一篇博客.

为什么 ProtoBuf 会提高传输效率呢?

我们知道使用 XML、JSON 进行数据编译时,数据文本格式更容易阅读,但进行数据交换时,设备就需要耗费大量的 CPU 在 I/O 动作上,自然会影响整个传输速率。

Protocol Buffers 不像前者,它会将字符串进行序列化后再进行传输,即二进制数据
gRPC 能够做到跨平台,多语言,很大一部分得益于Protobuffer自动的编译器。前面提到的 proto 文件就是通过编译器进行编译的,proto 文件需要编译生成一个类似库文件,基于库文件才能真正开发数据应用。
具体用什么编程语言编译生成这个库文件呢?由于现网中负责网络设备和服务器设备的运维人员往往不是同一组人,运维人员可能会习惯使用不同的编程语言进行运维开发,那么 Protocol Buffers 其中一个优势就能发挥出来——跨语言。

gRPC实战

在这里博主选择go语言进行演示grpc的使用,其他铁子也可以自行选择提前语言进行使用。在使用grpc之前各位铁子要安装好这个protobuf。
安装grpc最核心的库

go get google.golang.org/grpc 

上面安装是安装了这个protocol的编译器,它可以生成各种语言因此除了这个编译器我们还需要配合各个语言的代码生成工具,对于golang来说我们叫做protoc-gen-go

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

安装好之后我们就可以开始编写proto文件

syntax="proto3";
option go_package=".;service";//这部分内容是关于生成的go代码,在那个目录的包中,service代表生成的go文件的package是servicemessage  HelloRequest{bytes requestName=1;
}
message  HelloResponse{bytes responseMsg=1;
}
service SayHello{rpc SayHello(HelloRequest)returns(HelloResponse);
}

编写完成proto文件之后我们开始使用这个命令生成代码

protoc --go_out=. hello.proto  
protoc --go-grpc_out=. hello.proto 

在这里插入图片描述
下面我们开始编写客户端的代码

package mainimport ("context""errors""fmt""google.golang.org/grpc""google.golang.org/grpc/metadata"service "http/gRpc/server/proto""net"
)type server struct {*service.UnimplementedSayHelloServer //包含定义的sayHello方法的结构体
}func (s *server) SayHello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) {//获取源数据信息md, ok := metadata.FromIncomingContext(ctx)if !ok {return nil, errors.New("未传输token")}var appId stringvar appKey stringif v, ok := md["appid"]; ok {appId = v[0]}if v, ok := md["appkey"]; ok {appKey = v[0]}fmt.Println("the appkey is" + appKey)//这里应该要去数据库里面查询,用户id appidif appId != "ksy" || appKey != "122" {return nil, errors.New("错误" + appKey + appId)}return &service.HelloResponse{ResponseMsg: []byte(req.RequestName),}, nil
}func Test() {//开启监听listen, err := net.Listen("tcp", "127.0.0.1:8909")if err != nil {panic(err)}//创建grpc服务器grpcServer := grpc.NewServer()//注册进来,在grpc 服务端当中注册我们编写的服务service.RegisterSayHelloServer(grpcServer, &server{})//启动服务if err := grpcServer.Serve(listen); err != nil {panic(err)}
}func main() {Test()
}

这个客户端对应代码

package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"service "http/gRpc/server/proto"
)func Test() {//链接server端,此处禁用安全链接conn, err := grpc.Dial(":8909", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {panic(err)}defer conn.Close()//建立链接client := service.NewSayHelloClient(conn)//发起远程过程调用resp, err := client.SayHello(context.Background(), &service.HelloRequest{RequestName: []byte("ksy")})fmt.Println(resp)
}type ClientTokenAuth struct {}// GetRequestMetadata 重写接口
func (c *ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {return map[string]string{"appid":  "ksy","appKey": "122",}, nil
}func (c *ClientTokenAuth) RequireTransportSecurity() bool {//不开启安全传输return false
}// TestToke token认证
func TestToke() {var opts []grpc.DialOption//选项opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))conn, err := grpc.Dial(":8909", opts...)if err != nil {panic(err)}defer conn.Close()//建立链接client := service.NewSayHelloClient(conn)//发起远程过程调用resp, err := client.SayHello(context.Background(), &service.HelloRequest{RequestName: []byte("ksy")})fmt.Println(err)fmt.Println(resp)
}
func main() {TestToke()
}

下面我们可以把客户端和服务端启动起来,就可以进行通信了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/13210.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《动手学深度学习》——线性神经网络

参考资料: 《动手学深度学习》 3.1 线性回归 3.1.1 线性回归的基本元素 样本: n n n 表示样本数, x ( i ) [ x 1 ( i ) , x 2 ( i ) , ⋯ , x d ( i ) ] x^{(i)}[x^{(i)}_1,x^{(i)}_2,\cdots,x^{(i)}_d] x(i)[x1(i)​,x2(i)​,⋯,xd(i)​…

特征选择算法 | Matlab实现基于ReliefF特征选择算法的分类数据特征选择 ReliefF

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 特征选择算法 | Matlab实现基于ReliefF特征选择算法的分类数据特征选择 ReliefF 部分源码 %--------------------

写一个starter(spring boot)

前置知识 自动装配 自动装配的一个重要注解就是SpringBootApplication。它是一个复合注解,由四个元注解和另外三个注解组成。这三个注解是: ConfigurationEnableAutoConfigurationComponentScan Configuration Configuration 是 JavaConfig 形式的…

神经网络之VGG

目录 1.VGG的简单介绍 1.2结构图 3.参考代码 VGGNet-16 架构:完整指南 |卡格尔 (kaggle.com) 1.VGG的简单介绍 经典卷积神经网络的基本组成部分是下面的这个序列: 带填充以保持分辨率的卷积层; 非线性激活函数,如ReLU&a…

web安全php基础_php变量命名及其作用域

php变量命名规则 php变量命名规则 变量以 $ 符号开始,后面跟着变量的名称变量名必须以字母或者下划线字符开始变量名只能包含字母数字字符以及下划线(A-z、0-9 和 _ )变量名不能包含空格变量名是区分大小写的($y 和 $Y 是两个不…

SELECT * 会导致查询效率低的原因

SELECT * 会导致查询效率低的原因 前言一、适合SELECT * 的使用场景二、SELECT * 会导致查询效率低的原因2.1、数据库引擎的查询流程2.2、SELECT * 的实际执行过程2.3、使用 SELECT * 查询语句带来的不良影响 三、优化查询效率的方法四、总结 前言 因为 SELECT * 查询语句会查…

【如何成功加载 HuggingFace 数据集】不使用Colab,以ChnSentiCorp数据集为例

【如何成功加载 HuggingFace 数据集】不使用Colab,以ChnSentiCorp数据集为例 前置加载数据集尝试一:标准加载数据库代码尝试二:科学上网尝试三:把 Huggingface 的数据库下载到本地尝试3.5 创建 state.json彩蛋 前置 Huggingface …

MySQL用户管理

目录 用户管理 用户 用户信息 创建用户 删除用户 修改用户密码 数据库的权限 给用户授权 回收权限 用户管理 如果我们只能使用root用户,这样存在安全隐患。这时,就需要使用MySQL的用户管理。 用户 用户信息 MySQL中的用户,都存储…

手写操作系统--进入保护模式的开篇

之前我们讲的主引导扇区以及内核加载器等内容。都是在实模式下运行的。在实模式下寻址范围仅有1M,是远远不够我们用的。我们想要更大的内存空间,就得进入保护模式,实模式是一个历史遗留问题,本身是没有这个名字的。是因为有了保护…

Keil环境下CANopenNode移植到STM32问题记录(一)---printf重定向问题

文章目录 问题描述问题结决思考:相关文章 在直接将CANopenSTM32的示例工程直接移植到Keil环境下。 如果移植工程未实现printf函数重定向,则要注释掉log_printf下面的printf函数,使日志打印失效 /* Printf function of CanOpen app */ #define…

vue3的getCurrentInstance()方法拿到的实例对象中的proxy

getCurrentInstance方法拿到的是当前组件的实例对象 实例对象中的成员proxy是一个代理对象,可以通过访问代理对象来间接访问当前组件的实例对象 这样就不需要this,也可以操作当前组件的实例对象了 proxy对象就相当于当前组件的实例对象 proxy对象会对…

FPGA实验五:信号发生器设计

目录 一、实验目的 二、设计要求 三、实验代码 1.代码原理分析 2.代码设计思路 3.IP核的设计与配置 四、实验结果及分析 1、引脚锁定 2、仿真波形及分析 (1)关于波形一些指标的介绍 (2)对波形转换功能的验证 &#xf…