Rpc基本概念

RPC(Remote Procedure Call)远程过程调用,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,简单的理解是一个节点请求另一个节点提供的服务。RPC只是一套协议,基于这套协议规范来实现的框架都可以称为 RPC 框架,比较典型的有 有阿里巴巴的 Dubbo、Google 的 gRPC、Facebook 的 Thrift 和 Twitter 的 Finagle 等。

RPC 机制和实现过程

RPC 是远程过程调用的方式之一,涉及调用方和被调用方两个进程的交互。因为 RPC 提供类似于本地方法调用的形式,所以对于调用方来说,调用 RPC 方法和调用本地方法并没有明显区别。

RPC的机制的诞生和基础概念

1984 年,Birrell 和 Nelson 在 ACM Transactions on Computer Systems 期刊上发表了名为“Implementing remote procedure calls”的论文,该文对 RPC 的机制做了经典的诠释:

RPC 远程过程调用是指计算机 A 上的进程,调用另外一台计算机 B 上的进程的方法。其中A 上面的调用进程被挂起,而 B 上面的被调用进程开始执行对应方法,并将结果返回给 A,计算机 A 接收到返回值后,调用进程继续执行。

发起 RPC 的进程通过参数等方式将信息传送给被调用方,然后被调用方处理结束后,再通过返回值将信息传递给调用方。这一过程对于开发人员来说是透明的,开发人员一般也无须知道双方底层是如何进行消息通信和信息传递的,这样可以让业务开发人员更专注于业务开发,而非底层细节。

RPC 让程序之间的远程过程调用具有与本地调用类似的形式。比如说某个程序需要读取某个文件的数据,开发人员会在代码中执行 read 系统调用来获取数据。

当 read 实际是本地调用时,read 函数由链接器从依赖库中提取出来,接着链接器会将它链接到该程序中。虽然 read 中执行了特殊的系统调用,但它本身依然是通过将参数压入堆栈的常规方式调用的,调用方并不知道 read 函数的具体实现和行为。

当 read 实际是一个远程过程时(比如调用远程文件服务器提供的方法),调用方程序中需要引入 read 的接口定义,称为客户端存根(client-stub)。远程过程 read 的客户端存根与本地方法的 read 函数类似,都执行了本地函数调用。不同的是它底层实现上不是进行操作系统调用读取本地文件来提供数据,而是将参数打包成网络消息,并将此网络消息发送到远程服务器,交由远程服务执行对应的方法,在发送完调用请求后,客户端存根随即阻塞,直到收到服务器发回的响应消息为止。

下图展示了远程方法调用过程中的客户端和服务端各个阶段的操作。
    
在这里插入图片描述

总结下RPC执行步骤:

  1. 调用客户端句柄,执行传递参数。
  2. 调用本地系统内核发送网络消息。
  3. 消息传递到远程主机,就是被调用的服务端。
  4. 服务端句柄得到消息并解析消息。
  5. 服务端执行被调用方法,并将执行完毕的结果返回给服务器句柄。
  6. 服务器句柄返回结果,并调用远程系统内核。
  7. 消息经过网络传递给客户端。
  8. 客户端接受数据。

安装gRPC和Protobuf

gRPC由google开发,是一款语言中立、平台中立、开源的远程过程调用系统
gRPC客户端和服务端可以在多种环境中运行和交互,例如用java写一个服务端,可以用go语言写客户端调用
在gRPC中,我们可以一次性的在一个 proto文件中定义服务并使用任意的支持gRPC的语言去实现客户端和服务端,整个过程操作变得简单,就像调用本地函数一样。

通过 proto生成服务端代码,也就是服务端的骨架,提供低层通信抽象
通过 proto生成客户端代码,也就是客户端的存根,隐藏了不同语言的差异,提供抽象的通信方式,就像调用本地函数一样。

安装

  • go get github.com/golang/protobuf/proto
  • go get google.golang.org/grpc(无法使用,用如下命令代替)
    • git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
    • git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
    • git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
    • go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
    • git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
    • cd $GOPATH/src/
    • go install google.golang.org/grpc
    • go get github.com/golang/protobuf/protoc-gen-go
  • 上面安装好后,会在GOPATH/bin下生成protoc-gen-go.exe
  • 但还需要一个protoc.exe,windows平台编译受限,很难自己手动编译,直接去网站下载一个,地址:https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.0 ,同样放在GOPATH/bin下

proto 服务定义

gRPC 使用protocol buffer 来定义服务接口,protocol buffer和 XML、JSON一样是一种结构化数据序列化的可扩展存储结构,protocol buffer是一种语言中立,结构简单高效,比XML更小更简单,可以通过特殊的插件自动生成代码来读写操作这个数据结构。

import "myproject/other_protos.proto";		// 导入其他 proto文件
syntax = "proto3"; // 指定proto版本
package hello;     // 指定默认包名

// 指定golang包名
option go_package = "hello";

message SearchRequest 
{
  required string query = 1;				// 必须赋值字段
  optional int32 page_number = 2 [default = 10];		// 可选字段
  repeated int32 result_per_page = 3;	// 可重复字段 
}

message SearchResponse 
{
  message Result 		// 嵌套定义
  {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}

message SomeOtherMessage 
{
  optional SearchResponse.Result result = 1;	// 使用其他消息的定义
}

service List{				// 定义gRPC服务接口
	rpc getList(SearchRequest) returns (SearchResponse);
}
// 插件自动生成gRPC骨架和存根
protoc --go_out=plugins=grpc: ./ *.proto

后面需要实现服务端具体的逻辑就行,然后注册到gRPC服务器
客户端在调用远程方法时会使用阻塞式存根,所以gRPC主要使用同步的方式通信,在建立连接后,可以使用流的方式操作。
客户端编排为protocol buffer的格式,服务端再解排执行,以HTTP2 传输

gRPC 优势

  • 更高效的进程通信:使用基于protocol buffer在Http2 中以二进制协议通信,而不是JSON、XML文本格式
  • 简单定义的服务接口、易扩展
  • 强类型、跨语言
  • 一元RPC、服务端流、客户端流、双工流

gRPC入门

简单使用

protocol buffer

syntax = "proto3";
package hello;
// 第一个分割参数,输出路径;第二个设置生成类的包路径

option go_package = "./proto/hello";



// 设置服务名称
service Greeter {
  // 设置方法
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 请求信息用户名.
message HelloRequest {
  string name = 1;
}

// 响应信息
message HelloReply {
  string message = 1;
}

服务端

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "mygrpc/proto/hello"
)

var (
	port = flag.Int("port", 50051, "The server port")
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	// 开启rpc
	s := grpc.NewServer()
	// 注册服务
	pb.RegisterGreeterServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

客户端

package main

import (
	"context"
	"flag"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "mygrpc/proto/hello" // 引入编译生成的包
)

const (
	defaultName = "world"
)

var (
	addr = flag.String("addr", "localhost:50051", "the address to connect to")
	name = flag.String("name", defaultName, "Name to greet")
)

func main() {
	flag.Parse()
	// 与服务建立连接.
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	// 创建指定服务的客户端
	c := pb.NewGreeterClient(conn)

	// 连接服务器并打印出其响应。
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	// 调用指定方法
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.GetMessage())
}

客户端连接gRPC服务器以后,就可以像调用本地函数一样操作远程服务器。

代码仓库

https://github.com/onenewcode/mygrpc.git

也可以直接下载绑定的资源。

相关文章

使用Go Validator在Go应用中有效验证数据

Go Validator是一个开源的包,为Go结构体提供强大且易于使用的数据验证功能。该库允许开发者为其数据结构定义自定义验证规则,并确保传入的数据满足指定的条件。Go Validator支持内置验证器、自定义验证器,甚至允许您链式多个验证规则以满足更复杂的数据验证需求。如果内置验证器无法满足您的需求,您可以通过定义自己的验证函数来创建自定义验证器。这个功能允许您实现特定于应用程序需求的验证逻辑。

C# this关键字的作用

关键字在C#中主要用于引用当前对象,区分字段与局部变量,调用其他构造函数以及传递当前对象给其他方法或构造函数。

C语言中关于#include的一些小知识

如果是你自己编写的头文件,那么如果没加唯一包含标识的话,那么编译器会编译报错的。如果是系统自带的头文件,由于其每个头文件都加了特殊标识,所以即使你包含两遍,也不会有问题。上面的代码片段会首先判断HEADER_FILE_NAME_H是否被定义,若未定义则进行后续操作;#ifndef HEADER_FILE_NAME_H // 定义了一个名为HEADER_FILE_NAME_H的标记符号。#define HEADER_FILE_NAME_H // 当第一次包含该头文件时,将此标记设置为已定义状态。

Go 是否有三元运算符?Rust 和 Python 是怎么做的?

本文主要就 Go 中三元运算符展开讨论,从简单if-else语句、到基于匿名函数的单行表达式、及泛型抽象 If 函数等方式来实现类似的功能。当然,我没有建议使用这些方式,在没有内置支持的情况下,if-else的写法就挺好的。Go 中如何实现三元运算符?Rust 和 Python 是怎么做的?

C语言中的作用域与生命周期

但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用了,其他源文件,即使声明了,也是无法正常使用的。结论:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。extern 是用来声明外部符号的,如果一个全局的符号在A文件中定义的,在B文件中想使用,就可以使用extern进行声明,然后使用。全局变量的生命周期是:整个程序的生命周期。

Python和Java的区别(不断更新)

运行效率:一般来说,Java的运行效率要高于Python,这主要是因为Java是编译型语言,其代码在执行前会进行预编译,而Python是解释型语言,边解释边执行。而Python没有类似的强大虚拟机,但它的核心是可以很方便地使用C语言函数或C++库,这使得Python可以轻松地与底层硬件进行交互。**类型系统:**Java是一种静态类型语言,所有变量需要先声明(类型)才能使用,且类型在编译时就已经确定。总的来说,Python和Java各有其优势和特点,选择哪种语言取决于具体的项目需求、开发环境以及个人偏好。

C#中的浅度和深度复制(C#如何复制一个对象)

接着,我们修改了复制得到的对象及其引用类型字段的属性值,最后输出原始对象和复制对象的属性值。这意味着如果一个类包含引用类型成员,在执行深度复制时,不仅复制这些引用,还会递归地复制引用所指向的对象,直到所有的引用都指向全新的对象实例。当进行浅复制时,系统会创建一个新的对象实例,但这个新对象的字段将与原始对象中的值类型字段具有相同的值,而对于引用类型字段,则仅仅是复制了。也就是说,如果一个类中有引用类型的成员变量(比如数组、其他自定义类的对象等),那么浅复制后,新对象和原对象的这些引用类型成员仍然指向。

C++ STL精通之旅:向量、集合与映射等容器详解

STL 作为一个封装良好,性能合格的 C++ 标准库,在算法竞赛中运用极其常见。灵活且正确使用 STL 可以节省非常多解题时间,这一点不仅是由于可以直接调用,还是因为它封装良好,可以让代码的可读性变高,解题思路更清晰,调试过程往往更顺利。

解决Linux环境下gdal报错:ERROR 4: `/xxx.hdf‘ not recognized as a supported file format.

题外话:我发现linux系统和Windows系统下面,库的版本是有差异的。比如我的本机Windows上装的是gdal3.2.3和numpy1.19.1,linux服务器上装的却是gdal3.0.2和numpy1.21.5。这个是很常见的回复,网上许多回答都说低版本的 gdal 不支持 hdf5,让你重装高版本的gdal。我之前用pip安装了whl,暴力装上了,但用的时候就会有问题。安装了不冲突的gdal之后,就成功打开文件啦~一开始我是抱着试试的心态,用conda,不用pip,重新安装了一下我的gdal。

C语言常见面试题:什么是枚举,枚举的作用是什么?

首先,枚举是一种特殊的类,它的主要作用是封装一组常量,例如,一周的七天、月份、季节等。枚举在JDK1.5后被引入,相较于之前的常量定义方式,枚举具有更好的安全性和更好的性能。每个枚举常量都是该枚举类的一个实例,因此可以使用构造函数来初始化每个枚举常量的值。综上所述,枚举的常用用法包括常量定义、switch语句、添加新方法、覆盖枚举的方法、实现接口以及使用接口组织枚举等。枚举是一种特殊的数据类型,它是一组具命名的整型常量的集合。枚举是一种特殊的数据类型,它是一组具命名的整型常量的集合。

为什么Java中的String类被设计为final类?

String类作为Java中不可或缺的类之一,被设计成final类带来了不可变性、安全性、可靠性和性能优势。不可变的特性使得String对象在多线程环境下安全共享,提高了应用程序的并发性和性能。此外,String类的设计还符合Java类库的一致性和规范,确保了整个语言的稳定性和可靠性。因此,String类被设计成final类是出于多方面的考虑,以提供最佳的使用体验和编程效率。

计算机组成原理 CPU的功能和基本结构和指令执行过程

根据指令执行过程中的数据和地址的流动方向安排连接线路,避免使用共享的总线,性能较高,但硬件量大。(PC)->Bus->MAR   PCout 和 MARin 有效,现行指令地址->MAR。(MDR)->Bus->IR   MDRout 和 IRin有效,现行指令->IR。每个指令时间可能不同,但是在单指令周期下,所有指令选用相同的执行时间,指令间串行。MEM(MAR)->数据线->MDR   操作数从存储器->数据线->MDR。MDR->Bus->Y   MDRout 和 Yin 有效,操作数->Y。

Promise和箭头函数和普通函数的区别

箭头函数与普通函数的区别在于: 1、箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this, 2、箭头函数没有自己的arguments对象,但是可以访问外围函数的arguments对象 3、不能通过new关键字调用,同样也没有new.target值和原型。6、箭头函数没有自己的arguments,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表。

ElasticSearch 集群搭建与状态监控cerebro

在单机上利用docker容器运行多个es实例来模拟es集群。部署es集群可以直接使用docker-compose来完成,但要求Linux虚拟机至少有4GI的内存空间。"number_of_replicas": 1 // 副本数。"number_of_shards": 3,// 分片款量。kibana可以监控es集群,不过新版本需要依赖es的x-pack 功能,配置比较复杂。第一种方式:利用kibana的DevTools创建索引库 ,在DevTools中输入指令。第二种方式:利用cerebro创建索引库。

网络知识-以太网技术的发展及网络设备

大家都被互联网上各种各样的内容、技术闪亮了眼睛,没有太多人去了解比较底层的一些网络技术。面试的时候,我也问过很多技术人员,对以太网是否了解,了解多少?但是很多人都知之甚少!但是,在我们实际工作碰到问题、分析问题、定位问题、解决问题的时候,又必须要了解这方面的知识。以太网最初到现在的主要设备包括集线器、中继器、网桥、交换机。以太网目前应用在很多行业,在视频监控、安防、视频会议等领域都有很广泛的应用。

详解静态网页数据获取以及浏览器数据和网络数据交互流程-Python

在网站设计领域,基于纯HTML格式构建的网页通常定义为静态网页,这种类型的网页是早期网站建设的主要形式。对于网络爬虫来说,抓取静态网页中的数据相对较为简单,因为所需的所有信息都直接嵌入在网页的HTML代码里。然而,对于那些利用AJAX技术动态加载数据的网页,其数据并不总是直接出现在HTML代码中,这对爬虫的抓取工作造成了一定的难度。在静态网页的数据抓取过程中,Requests库显示出其卓越的实用性。这个库不仅功能全面,而且操作简洁直观。

为什么ChatGPT选择了SSE,而不是WebSocket?

WebSocket是一种网络通信协议,它最早被提出来是为了解决HTTP连接的一大限制:HTTP协议中,一个客户端发送给服务端的请求必须由服务端返回一个响应,这使得服务端无法主动向客户端推送数据。客户端通过发送一个特殊的HTTP请求向服务器请求建立WebSocket连接。这个请求类似于:GET /chat HTTP/1.1 Upgrade: websocket Connection: Upgrade服务器响应这个请求,确认建立WebSocket连接。

【HarmonyOS】ArkTS语言介绍与组件方式运用

自定义组件自定义函数:自定义函数可以将烦长的代码单独抽离出一个函数当中,然后在原位置调用我们设置的函数即可,自定义函数可以定义在全局或组件内,如下:@Styles装饰器@Extend装饰器// 继承模式,只能写在全局。

SpringMVC之获取请求参数和域对象共享数据

一、SpringMVC获取请求参数1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数6、通过POJO获取请求参数7、解决获取请求参数的乱码问题二、域对象共享数据1、使用ServletAPI向request域对象共享数据2、使用ModelAndView向request域对象共享数据3、使用Model向request域对象共享数据4、使用map向request域对象共享数据5、使用ModelMap向request域对象共享数据。
返回
顶部