grpc学习笔记

news2024/11/28 4:34:16

目录

  • gRPC原理
  • 网络传输效率问题
  • 基本概念概览
    • Client
    • Server
  • 异步相关概念
    • 异步 Client
    • 异步 Server

gRPC原理

RPC 即远程过程调用协议(Remote Procedure Call Protocol),可以让我们像调用本地对象一样发起
远程调用。
在这里插入图片描述
在这里插入图片描述

网络传输效率问题

HTTP1.1核心问题在于:在同一个TCP连接中,没办法区分response是属于哪个请求,一旦多个请求返
回的文本内容混在一起,则没法区分数据归属于哪个请求,所以请求只能一个个串行排队发送。这直接
导致了TCP资源的闲置。

HTTP2为了解决这个问题,提出了 流 的概念,每一次请求对应一个流,有一个唯一ID,用来区分不同的
请求。基于流的概念,进一步提出了 帧 ,一个请求的数据会被分成多个帧,方便进行数据分割传输,每
个帧都唯一属于某一个流ID,将帧按照流ID进行分组,即可分离出不同的请求。这样同一个TCP连接中
就可以同时并发多个请求,不同请求的帧数据可穿插在一起,根据流ID分组即可。HTTP2.0基于这种二
进制协议的乱序模式 (Duplexing),直接解决了HTTP1.1的核心痛点,通过这种复用TCP连接的方式,不
用再同时建多个连接,提升了TCP的利用效率。

在这里插入图片描述

基本概念概览

在这里插入图片描述

Client

Client是对 Stub 封装;通过 Stub 可以真正的调用 RPC 请求。

class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(Greeter::NewStub(channel)) {}
std::string SayHello(const std::string& user) {
...
private:
std::unique_ptr<Greeter::Stub> stub_;
};

Channel 提供一个与特定 gRPC server 的主机和端口建立的连接。
Stub 就是在 Channel 的基础上创建而成的。

target_str = "localhost:50051";
auto channel =
grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials());
GreeterClient greeter(channel);
std::string user("world");
std::string reply = greeter.SayHello(user);

Server

Server 端需要实现对应的 RPC,所有的 RPC 组成了 Service。

class GreeterServiceImpl final : public Greeter::Service {
Status SayHello(ServerContext* context, const HelloRequest* request,
HelloReply* reply) override {
std::string prefix("Hello ");
reply->set_message(prefix + request->name());
return Status::OK;
}
};

Server 的创建需要一个 Builder,添加上监听的地址和端口,注册上该端口上绑定的服务,最后构建出
Server 并启动

ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());

不管是哪种类型 RPC,都是由 Client 发起请求。

异步相关概念

不管是 Client 还是 Server,异步 gRPC 都是利用 CompletionQueue API 进行异步操作。基本的流程:
1、绑定一个 CompletionQueue 到一个 RPC 调用
2、利用唯一的 void* Tag 进行读写
3、调用 CompletionQueue::Next() 等待操作完成,完成后通过唯一的 Tag 来判断对应什么请求/返回进行后续操作

异步 Client

greeter_async_client.cc 中是异步 Client 的 Demo,其中只有一次请求,逻辑简单。
1、创建 CompletionQueue
2、创建 RPC (既 ClientAsyncResponseReader ),这里有两种方式:
-----stub_->PrepareAsyncSayHello() + rpc->StartCall()
-----stub_->AsyncSayHello()
3、调用 rpc->Finish() 设置请求消息 reply 和唯一的 tag 关联,将请求发送出去
4、使用 cq.Next() 等待 Completion Queue 返回响应消息体,通过 tag 关联对应的请求


class GreeterClient {
 public:
  explicit GreeterClient(std::shared_ptr<Channel> channel)
      : stub_(Greeter::NewStub(channel)) {}

  std::string SayHello(const std::string& user) {
    HelloRequest request;
    request.set_name(user);
    HelloReply reply;
    ClientContext context;
    Status status;

	//创建 CompletionQueue
    CompletionQueue cq;

	//创建 RPC (既 ClientAsyncResponseReader<HelloReply> ):stub_->PrepareAsyncSayHello() + rpc->StartCall()  
    stub_->PrepareAsyncSayHello(&context, request, &cq));
    rpc->StartCall();

	//调用 rpc->Finish() 设置请求消息 reply 和唯一的 tag =1关联,将请求发送出去
    rpc->Finish(&reply, &status, (void*)1);

    void* got_tag;
    bool ok = false;

	//使用 cq.Next() 等待 Completion Queue 返回响应消息体,通过 tag 关联对应的请求
    GPR_ASSERT(cq.Next(&got_tag, &ok));

    // Act upon the status of the actual RPC.
    if (status.ok()) {
      return reply.message();
    } else {
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<Greeter::Stub> stub_;
};

异步 Server

1、创建一个 CallData,初始构造列表中将状态设置为 CREATE
2、构造函数中,调用 Process()成员函数,调用 service_->RequestSayHello() 后,状态变更为PROCESS:
传入 ServerContext ctx_
传入 HelloRequest request_
传入 ServerAsyncResponseWriter responder_
传入 ServerCompletionQueue* cq_
将对象自身的地址作为 tag 传入
该动作,能将事件加入事件循环,可以在 CompletionQueue 中等待
3、收到请求, cq->Next() 的阻塞结束并返回,得到 tag,既上次传入的 CallData 对象地址
4、用 tag 对应 CallData 对象的 Proceed() ,此时状态为 Process
创建新的 CallData 对象以接收新请求
处理消息体并设置 reply_
将状态设置为 FINISH
调用 responder_.Finish() 将返回发送给客户端
该动作,能将事件加入到事件循环,可以在 CompletionQueue 中等待
5、发送完毕, cq->Next() 的阻塞结束并返回,得到 tag。现实中,如果发送有异常应当有其他相关的处理
6、调用 tag 对应 CallData 对象的 Proceed() ,此时状态为 FINISH, delete this 清理自己,一条消息处理完成。


class ServerImpl final {
private:
  class CallData {
   	public:
    // 创建一个 CallData,初始构造列表中将状态设置为 CREATE
    CallData(Greeter::AsyncService* service, ServerCompletionQueue* cq)
        : service_(service), cq_(cq), responder_(&ctx_), status_(CREATE) {
	//构造函数中,调用 Process()成员函数,调用 service_->RequestSayHello() 后,状态变更为PROCESS
      Proceed();}

    void Proceed() {
      if (status_ == CREATE) {
      //构造函数中,调用 Process()成员函数,调用 service_->RequestSayHello() 后,状态变更为PROCESS
        status_ = PROCESS;
        // 传入 ServerContext ctx_
		// 传入 HelloRequest request_
		// 传入 ServerAsyncResponseWriter<HelloReply> responder_
		// 传入 ServerCompletionQueue* cq_
		// 将对象自身的地址作为 tag 传入
		//该动作,能将事件加入事件循环,可以在 CompletionQueue 中等待
        service_->RequestSayHello(&ctx_, &request_, &responder_, cq_, cq_,this);
      } 
      //收到请求, cq->Next() 的阻塞结束并返回,得到 tag,既上次传入的 CallData 对象地址
	  //调用 tag 对应 CallData 对象的 Proceed() ,此时状态为 Process
      else if (status_ == PROCESS) {
      	//创建新的 CallData 对象以接收新请求
        new CallData(service_, cq_);

        //处理消息体并设置 reply_
        std::string prefix("Hello ");
        reply_.set_message(prefix + request_.name());

        // 将状态设置为 FINISH
        status_ = FINISH;
        
        //调用 responder_.Finish() 将返回发送给客户端
        //该动作,能将事件加入到事件循环,可以在 CompletionQueue 中等待
        responder_.Finish(reply_, Status::OK, this);
      }//发送完毕, cq->Next() 的阻塞结束并返回,得到 tag。现实中,如果发送有异常应当有其他相关的处理
	   else {
        GPR_ASSERT(status_ == FINISH);
        // 调用 tag 对应 CallData 对象的 Proceed() ,此时状态为 FINISH, delete this 清理自己,一条消息处理完成
        delete this;
      }
    }

   private:
    Greeter::AsyncService* service_;
    ServerCompletionQueue* cq_;
    ServerContext ctx_;
    HelloRequest request_;
    HelloReply reply_;
    ServerAsyncResponseWriter<HelloReply> responder_;
    enum CallStatus { CREATE, PROCESS, FINISH };
    CallStatus status_;  
  };
  
private:
  std::unique_ptr<ServerCompletionQueue> cq_;
  Greeter::AsyncService service_;
  std::unique_ptr<Server> server_;
};

关系图:
右侧 RPC 为创建的对象中的内存容,左侧使用相同颜色的小块进行代替。
在这里插入图片描述
以下 CallData 并非 gRPC 中的概念,而是异步 Server 在实现过程中为了方便进行的封装,其中的
Status 也是在异步调用过程中自定义的、用于转移的状态。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql---explain详解

explain命令是用来分析查询语句的执行计划的工具。它可以显示查询语句的执行计划&#xff0c;包括查询的顺序、使用的索引、扫描的行数等信息&#xff0c;帮助开发人员优化查询语句的性能。通过分析执行计划&#xff0c;可以找到查询语句的瓶颈&#xff0c;进而优化查询语句的性…

机器学习笔记 - 探索PaddlePaddle框架的对象检测、分割和关键点检测

一、PaddlePaddle简述 PaddlePaddle(PArallel Distributed Deep LEarning)是百度于2016年发布的开源深度学习框架。它为各种机器学习任务提供了统一的平台。 但是最近的基准测试显示 PaddlePaddle 是潜在的领跑者,其基准速度超过了其更知名的竞争对手。 PaddlePaddle 与 PyT…

2.SpringBoot集成Swagger

1.添加依赖 <!--添加Swagger依赖 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version></dependency><!--添加Swagger-UI依赖 --><dependenc…

Python实现操作MySQL【增删改查-示例】

闲话少叙,直接上操作! 一、准备工作 1.本地安装MySQL、Python(以3.6为例) 2.MySQL新建数据库【test】,新建表【user】,新建字段【name】【age】 3.建表方式:navicat工具 字段 二、Python操作—插入数据 #!/usr/bin/env # coding=utf-8import pymysql # Python 连…

Leetcode-每日一题【430.扁平化多级双向链表】

题目 你会得到一个双链表&#xff0c;其中包含的节点有一个下一个指针、一个前一个指针和一个额外的 子指针 。这个子指针可能指向一个单独的双向链表&#xff0c;也包含这些特殊的节点。这些子列表可以有一个或多个自己的子列表&#xff0c;以此类推&#xff0c;以生成如下面…

Java语言程序设计试卷6套

目录 Java语言程序设计试卷1 一、单项选择题 二、多项选择题 三、判断题 Java语言程序设计试卷2 一、单项选择题 二、多项选择题 三、判断题 Java语言程序设计试卷3 一、单项选择题 二、多项选择题 三、判断题 Java语言程序设计试卷4 一、单项选择题 二、多项选…

【7月比赛合集】119场可报名的数据挖掘大奖赛,任君挑选!

CompHub[1] 主页增加了“近两周上新的奖金赛”&#xff0c;更加方便查找最新比赛&#xff0c;欢迎访问和反馈&#xff01; 最新更新的比赛会第一时间在群里通知&#xff0c;欢迎加群交流&#xff01;&#xff08;公众号回复“加群”即可&#xff09; 以下信息仅供参考&#xff…

一种简单的数据库性能测试方法

这两天接到一个任务&#xff0c;要测试一个服务器的性能&#xff0c;客户要求向数据库内 1000/s&#xff08;每插入一千条数据&#xff09; 的处理能力&#xff0c;当时脑子赌赛了&#xff0c;想的是用LR来进行&#xff0c;由于LR接触不深&#xff0c;只知道LR实现参数化的时候…

Java基础---异常

目录 典型回答 什么是Throwable Error和Exception的区别和联系 Java异常处理相关的几个关键字&#xff0c;以及简单用法 什么是自定义异常&#xff0c;如何使用自定义异常 Java异常体系结构 以下关于异常处理的代码有哪些问题 典型回答 Java中的异常&#xff0c;主要可以…

力扣 62. 不同路径

文章目录 题目描述思路代码 题目描述 思路 动态规划 代码 private int UniquePaths(int m, int n) {int[][] dp new int[m][];for (int i 0; i < m; i) {dp[i] new int[n];}for (int i 0; i < m; i) {for (int j 0; j < n; j) {if (i 0 || j 0) dp[i][j] …

LLM - Baichuan7B Tokenizer 生成训练数据

目录 一.引言 二.Tokenizer 原始数据 1.原始数据样例 2.加载并 Token 原始数据 2.1 参数准备 2.2 单条样本处理逻辑 2.3 批量处理逻辑 2.4 主函数与完整代码 三.shell 执行 四.总结 一.引言 前面提到了自己在微调 Baichuan7B Lora 的过程中遇到了一些问题&#xff0c…

Cesium Token申请

一、什么是Cesium ion&#xff1f; Cesium ion是一个提供瓦片图和3D地理空间数据的平台&#xff0c;支持把数据添加到用户自己的应用程序中。 二、为什么需要access token&#xff1f; 使用Cesium ion需要申请access token,当用户将数据添加到自己的账户后&#xff0c;便可以…

Android-jar包方式连接本地sqlite并操作返回数据

背景: 数据库的创建及字段都是后端人员维护,Android端只是映射相关数据库到本地来操作。为了统一管理操作方式方法,所以提出,后端打jar包的方式封装对Android端数据库sqllite的连接、操作。 说明: 因为之前是后端打jar包,JDBC连接的驱动及方法有差异,导致连接Android…

PHP在线拨打电话的代码

这段代码包括一个HTML表单,用于收集用户的姓名,电子邮件和消息。当用户提交表单时,邮件将发送到指定的电子邮件地址,并显示一条消息,指示我们将在不久的将来拨打电话回复。请注意,在上面的代码中,电话号码硬编码为 $phone_number 变量,您需要将其更改为您想要的电话号码…

jmeter使用正则表达式匹配多个中的响应结果

目录 一、背景&#xff1a; 二、例如&#xff1a; 三、接口响应的所有结果&#xff1a; 四、正则表达式的写法&#xff1a; 五、调试的时候添加一个Debug PostProcessor 调试器 六、在Debug PostProcessor中可以查看到获取的参数结果 七、引用方式&#xff1a;${testValue…

【js前端去空格】

javascript字符串去空格 js去除字符串空格的方法 说到去除空格&#xff0c;首先都会想到的就是trim()方法&#xff0c;但是trim()只能去除字符串前后的空格&#xff0c;无法去除字符串中间的空格。 下面总结一下js去除字符串空格的几种方法&#xff1a; 1、trim() trim()是…

你应该知道的C语言干货(4)(strncpy,strncmp,strncat,strstr,strtok)

我们知道包含string.h头文件后&#xff0c;就可以使用strncpy,strncmp,strncat,strstr,strtok这些库函数&#xff0c;接下来让我们了解他们。 目录 #strncpy #strncmp #strncat #strstr #strtok #下期预告 #strncpy 该库函数作用和strcpy很相似&#xff0c;不同点在于 发现了吗…

《面试1v1》Redis分片集群

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

Redis Brpop 命令

目录 一、作用二、demo演示 一、作用 Redis Brpop 命令拥有移出并获取list右边的最后一个元素&#xff0c; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 二、demo演示 向 list1 中插入三个元素 a、b、c lpush list1 a b c查看list1中的元素 lrange lis…

操作系统13:中断处理程序和设备驱动程序

目录 1、中断处理程序 &#xff08;1&#xff09;中断和陷入 &#xff08;2&#xff09;中断处理程序的处理过程 2、设驱动程序 &#xff08;1&#xff09;设备驱动程序的功能 &#xff08;2&#xff09;设备驱动程序的处理过程 &#xff08;4&#xff09;对 I/O 设备的…