RPC 服务与 gRPC 的入门案例

server/2024/12/21 16:59:59/

RPC 协议

RPC(Remote Procedure Call Protocol)即远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务的协议,允许一个计算机程序可以像调用本地服务一样调用远程服务

RPC的主要作用是不同的服务间方法调用就像本地调用一样便捷,它隐藏了网络通信的细节,使得开发者可以像调用本地函数一样调用远程函数,而无需关注底层网络通信的复杂性。

在这里插入图片描述

由于客户端和服务端部署在不同的机器上,服务间的调用就会涉及到网络通信,就需要写一堆网络通信相关的代码。
例如在调用 RESTful 服务时,调用端需要使用 HttpClient 设置很多参数,再去解析状态和返回值,非常复杂且易出错。
而 RPC 的方式可以让我们像调用本地服务一样调用远程服务,而且不必关心网络通信的细节。

RPC原理

Socket套接字

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端被称为 Socket。

Socket 用于描述 IP 地址和端口,是一个通信连接的句柄,可以用来实现不同的计算机之间的通信,是网络编程接口的具体实现。

Socket 套接字是客户端/服务端网络机构程序的基本组成部分,为程序提供了一种相对简单的机制,可以与另一个本地或远程机器上的程序建立连接,并可以来回发送消息。

我们可以使用它的收发消息功能来设计我们的分布式应用程序,也可以使用这些收发功能把 RPC 调用包装成透明的远程服务调用。

调用过程

实现透明的远程调用的重点是创建客户存根(类似于代理模式中的代理),在生成代理代码后,代理的代码就能与远程服务端通信了,通信的过程是由RPC 框架实现,而调用者就像调用本地代码一样。

对于客户端而言,存根函数就像普通的本地函数,但实际上包含了通过网络发送和接收消息的代码。

在这里插入图片描述

  • 第1 步,客户端调用本地的客户端存根方法,客户端存根方法会将参数打包并封装成一个或多个网络消息体并发送到服务端。
  • 第 2 步,客户端存根通过系统调用,使用操作系统内核提供的 Socket 套接字接口来向远程服务发送我们编码的网络消息。
  • 第 3 步,网络消息由内核通过协议(TCP/UDP)传输到远程服务端。
  • 第 4 步,服务端存根接收客户端发送的消息,并对参数消息解码,将参数从标准的网络格式转换为特定的语言格式。
  • 第 5 步,服务端存根调用服务端的方法,并且将从客户端接收的参数传递给该方法,它来运行具体的功能并返回,这部分代码的执行对客户端来说就是远程过程调用。
  • 第 6 步,服务端的方法在执行完成后,会把结果返回到服务端存根代码中。
  • 第 7 步,服务端存根在将该返回值编码且序列化后,通过一个或多个网络消息发送给客户端。
  • 第 8 步,消息通过网络发送到客户端存根中。
  • 第 9 步,客户端存根从本地 Socket 接口中读取结果消息。
  • 第 10 步,客户端存根再将结果返回给客户端函数,并且将消息从网络二进制形式转换为本地语言格式,这样就完成了远程服务调用,客户端代码继续执行后续的操作。

IDL接口

为了生成客户端和服务器的存根函数,我们需要定义一个远程调用接口的定义文件,该文件是使用一种叫做 IDL 的接口定义语言编写的。

接口定义类似于函数原型声明:函数名称、输入参数和返回参数。

在 RPC 编译器运行后,客户端和服务端的程序可以编译并链接到各自的存根函数。客户端存根必须实现初始化 RPC 通信的机制,找到服务器并进行连接,并能与远程服务器通信,并对远程过程调用失败的情况进行处理。

RPC框架-gRPC

gRPC 是 Google 主导开发的一个高性能开源的 RPC 框架,基于 HTTP/2 协议标准设计而成,并用 ProtoBuf 作为序列化工具和接口定义语言(IDL),支持多语言开发。

在这里插入图片描述

定义序列化数据结构

在一个.proto文件中定义数据的格式,这个格式由一系列messages组成,每个message包含多个字段,字段就是数据的名称和值的配对。

// The request message containing the user's name.
message HelloRequest {string name = 1;
}// The response message containing the greetings
message HelloReply {string message = 1;
}
}

接着通过.proto文件来定义gRPC服务,其中RPC方法的参数和返回类型都是上述定义的message

// The greeter service definition.
service Greeter {// Sends a greetingrpc SayHello (HelloRequest) returns (HelloReply) {}
}

最后通过protoc编译器以及gRPC插件来从.proto文件中生成代码。

protoc是Protocol Buffers的编译器,它能够根据.proto文件中定义的数据结构生成各种语言的数据访问类。在gRPC中,protoc与gRPC插件一起使用,可以生成gRPC客户端和服务器的代码,以及用于填充、序列化和检索消息类型的常规协议缓冲区代码。

实战

项目结构

在这里插入图片描述

  • api:存放公共代码;
  • server:服务端;
  • client:客户端。

api

1)首先引入需要的依赖

<properties><grpc.version>1.9.0</grpc.version><protobuf-java.version>3.5.1</protobuf-java.version>
</properties><dependencies><dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>${protobuf-java.version}</version></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-all</artifactId><version>${grpc.version}</version><exclusions><exclusion><artifactId>guava</artifactId><groupId>com.google.guava</groupId></exclusion></exclusions></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>24.1.1-jre</version></dependency><dependency> <!-- necessary for Java 9+ --><groupId>org.apache.tomcat</groupId><artifactId>annotations-api</artifactId><version>6.0.53</version><scope>provided</scope></dependency>
</dependencies>

2)引入插件

<build><extensions><extension><groupId>kr.motd.maven</groupId><artifactId>os-maven-plugin</artifactId><version>1.5.0.Final</version></extension></extensions><plugins><plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.5.1</version><configuration><protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact><pluginId>grpc-java</pluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact></configuration><executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions></plugin></plugins>
</build>

这个插件可以将我们编写的 .proto 文件自动转为对应的 Java 类。

3)创建 proto 目录,并定义.proto文件
在这里插入图片描述

syntax = "proto3";option java_package = "org.young.grpc.demo";package product;service ProductInfo {rpc addProduct (Product) returns (ProductId);rpc getProduct(ProductId) returns(Product);
}message Product {string id = 1;string name=2;string description=3;float price=4;
}message ProductId {string value = 1;
}

4)使用插件生成 java 代码
在这里插入图片描述

compilecompile-custom 都要执行, compile 用来编译消息对象,compile-custom 则依赖消息对象,生成接口服务。

在这里插入图片描述

server

server 项目依赖 api,在 server 中,提供 ProductInfo 的具体实现。

@Slf4j
public class ProductInfoImpl extends ProductInfoGrpc.ProductInfoImplBase {/*** @param request* @param responseObserver*/@Overridepublic void addProduct(ProductOuterClass.Product request, StreamObserver<ProductOuterClass.ProductId> responseObserver) {log.info("request:{}", request.toString());responseObserver.onNext(ProductOuterClass.ProductId.newBuilder().setValue(request.getId()).build()); // 方法响应responseObserver.onCompleted(); // 标记 RPC 调用完成}/*** @param request* @param responseObserver*/@Overridepublic void getProduct(ProductOuterClass.ProductId request, StreamObserver<ProductOuterClass.Product> responseObserver) {responseObserver.onNext(ProductOuterClass.Product.newBuilder().setId(request.getValue()).setName("test_grpc").build());responseObserver.onCompleted();}
}

然后再启动该项目

public class ServerBootStrap {Server server;public static void main(String[] args) throws Exception {ServerBootStrap serverBootStrap = new ServerBootStrap();serverBootStrap.start();serverBootStrap.blockUntilShutdown();}void start() throws Exception{server = NettyServerBuilder.forPort(50091).addService(new ProductInfoImpl()).build().start();Runtime.getRuntime().addShutdownHook(new Thread() {@Overridepublic void run() {ServerBootStrap.this.stop();}});}private void stop() {if (server != null) {server.shutdown();}}private void blockUntilShutdown() throws InterruptedException {if (server != null) {server.awaitTermination();}}
}

client

client 项目也需要依赖 api ,然后直接进行方法调用,如下:

@Slf4j
public class GrpcClient {public static void main(String[] args) {ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 50091).usePlaintext(true).build();ProductInfoGrpc.ProductInfoBlockingStub stub = ProductInfoGrpc.newBlockingStub(managedChannel);ProductOuterClass.Product req = ProductOuterClass.Product.newBuilder().setId("1").setPrice(100.0f).setName("test-grpc").setDescription("test-grpc").build();ProductOuterClass.ProductId productId = stub.addProduct(req);log.info("productId.getValue:{}", productId.getValue());ProductOuterClass.Product product = stub.getProduct(ProductOuterClass.ProductId.newBuilder().setValue("99999").build());log.info("product:{}", product.toString());}
}

http://www.ppmy.cn/server/151990.html

相关文章

力扣-图论-14【算法学习day.64】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

Vue2/3 生命周期详细对比与使用指南

Vue 2 生命周期 Vue 2 中,生命周期是指组件实例在创建、挂载、更新、销毁时所经历的一系列过程。以下是 Vue 2 的生命周期图和主要钩子函数的描述: Vue 2 生命周期钩子 beforeCreate:实例初始化之后调用,数据观测和事件配置尚未完成。常用于初始化逻辑。created:实例创建…

Mongodb 集群搭建

Mongodb 集群搭建 一、简介 mongodb 集群有三种方式&#xff1a;Master slave 主从模式、Replica Set 副本集模式、Sharding 分片集模式 Master slave 主从模式&#xff1a;主节点写入&#xff0c;数据同步到 Slave 节点&#xff0c;Slave 节点提供数据查询&#xff0c;最大…

音频声音太小怎么调大?调大音频声音的几种方法

音频声音太小怎么调大&#xff1f;音频声音过小可能由多种原因引起。从设备本身的硬件设置&#xff0c;到应用程序或播放软件的音量控制&#xff0c;再到文件本身的音频质量&#xff0c;都可能是导致声音过小的因素。尤其是在观看视频或听音乐时&#xff0c;若音量过低&#xf…

2024年云计算的发展趋势如何?

2024年云计算的发展趋势 在这个瞬息万变的科技时代&#xff0c;你是否也曾想过&#xff0c;云计算的发展究竟对我们每一个人意味着什么&#xff1f;它不仅是存储和计算能力的提升&#xff0c;更是整个行业的未来构建与转型之道。接下来&#xff0c;我们将一起探索2024年云计算…

VR虚拟展馆如何平衡用户隐私保护与数据收集?

在虚拟现实&#xff08;VR&#xff09;虚拟展馆的设计和运营中&#xff0c;用户隐私保护与数据收集之间的平衡是一个至关重要的议题。 接下来&#xff0c;由专业从事VR虚拟展馆制作的圆桌3D云展厅平台为大家介绍一些策略&#xff0c;可以帮助VR虚拟展馆在收集有用数据的同时&a…

Arm Cortex-M处理器对比表

Arm Cortex-M处理器对比表 当前MCU处理器上主要流行RISC-V和ARM处理器&#xff0c;其他的内核相对比较少&#xff1b;在这两种内核中&#xff0c;又以Arm Cortex-M生态环境相对健全&#xff0c;大部分的厂家都在使用ARM的处理器。本文主要介绍Arm Cortex-M各个不同系列的参数对…

Mybatis分页插件的使用问题记录

项目中配置的分页插件依赖为 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.7</version></dependency>之前的项目代码编写分页的方式为&#xff0c;通过传入的条件…