微服务的核心之一远程通信,http请求大家很熟悉了,我们主要来看看RPC。
首先说下RPC和HTTP的区别:很多博客说的真的一言难尽
首先:http是个通讯协议,而RPC是一个远程调用方案,它通常包括了通信协议和序列化协议。当然他们都是在传输层及其以上作用。
这也是为什么我们经常说RPC也可以给予http来实现,因为我们采用了http作为我们的RPC实现的应用层通信协议。而我们平时写http请求的时候,一般会采用基于文本编码的josn的序列化协议,而RPC采用的是基于二进制编码的protobuf协议。http协议header其实有很多附带字段,这些占用了网络报文,在高并发场景下,这些字段是不需要的,使用自定义的应用层协议可以减少一些数据,别看这个不多,但是一旦数据量很多,效果是非常明显的。如果采用http来做通信协议,其实和我们普通的rest请求差别不大。当然http和http2.0又是两个差别很大的协议了。
RPC 是一种技术思想而非一种规范或协议,通常的我们需要一些框架和技术来帮助我们快速实现rpc通信,常见 RPC 技术和框架有:
- 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud、Facebook 的 Thrift、Twitter 的 Finagle 等。
- 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
- 通信框架:MINA 和 Netty。
- ps: Google gRPC 框架是基于 HTTP2 协议实现的,底层使用到了 Netty 框架的支持。
目前来看从2014年开始,grpc框架已经成为高性能微服务通信的首选。
1. RPC 框架
一个典型 RPC 的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中“RPC 协议”就指明了程序如何进行网络传输和序列化。
一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等。
下面分别介绍核心 RPC 框架的重要组成:
- 客户端(Client):服务调用方。
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
- 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
- 服务端(Server):服务的真正提供者。
- Network Service:底层传输,可以是 TCP 或 HTTP。
一次 RPC 调用流程如下:
- 服务消费者(Client 客户端)通过本地调用的方式调用服务。
- 客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。
- 客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端。
- 服务端存根(Server Stub)收到消息后进行解码(反序列化操作)。
- 服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理
- 服务端(Server)本地服务业务处理。
- 处理结果返回给服务端存根(Server Stub)。
- 服务端存根(Server Stub)序列化结果。
- 服务端存根(Server Stub)将结果通过网络发送至消费方。
- 客户端存根(Client Stub)接收到消息,并进行解码(反序列化)。
- 服务消费方得到最终结果。
RPC的目标就是要2~10这些步骤都封装起来,让用户对这些细节透明。
实际上写代码也非常简单:
只需要先定义接口文件:
我们添加一个proto文件:helloworld.proto
只不过以前java中,我们叫interfce,文件名是.java,当然我们需要框架帮我们生成一些java类文件,帮助我们完成序列化、反序列化、编码等琐碎的工作。
syntax = "proto3"; // 协议版本
// 选项配置
option java_package = "com.chenj.protobuf";
option java_outer_classname = "RPCDateServiceApi";
option java_multiple_files = true;
// 定义包名
package com.chenj.protobuf;
// 服务接口.定义请求参数和相应结果
service RPCDateService {
rpc getDate (RPCDateRequest) returns (RPCDateResponse) {
}
}
// 定义请求体
message RPCDateRequest {
string userName = 1;
}
// 定义响应内容
message RPCDateResponse {
string serverDate = 1;
}
根据.proto文件生成消息体类文件和XXXGrpc类文件
使用maven命令.
在第一步修改的pom.xml的路径下,首先执行
mvn protobuf:compile
生成消息体类文件
接着执行:
mvn protobuf:compile-custom
生成XXXGrpc类文件
使用maven插件, 编译.
第一个命令执行完. 在 target目录里找就行了. 第二个命令也是找就行了. 然后将生成的Java文件拷贝到你的目录里.就可以了
编写接口实现类
package com.chenj;
import com.chenj.grpc.api.RPCDateRequest;
import com.chenj.grpc.api.RPCDateResponse;
import com.chenj.grpc.api.RPCDateServiceGrpc;
import io.grpc.stub.StreamObserver;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// RPCDateServiceGrpc.RPCDateServiceImplBase 这个就是接口.
// RPCDateServiceImpl 我们需要继承他的,实现方法回调
public class RPCDateServiceImpl extends RPCDateServiceGrpc.RPCDateServiceImplBase {
@Override
public void getDate(RPCDateRequest request, StreamObserver<RPCDateResponse> responseObserver) {
//请求结果,我们定义的
RPCDateResponse rpcDateResponse = null;
//
String userName = request.getUserName();
String response = String.format("你好:%s,今天是%s.", userName,LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
try {
// 定义响应,是一个builder构造器.
rpcDateResponse = RPCDateResponse.newBuilder()
.setServerDate(response)
.build();
//int i = 10/0;
} catch (Exception e) {
responseObserver.onError(e);
} finally {
responseObserver.onNext(rpcDateResponse);
}
responseObserver.onCompleted();
}
}
定义服务端
package com.chenj;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
public class GRPCServer {
private static final int port = 9999;
public static void main(String[] args) throws IOException, InterruptedException {
//设置service端口
Server server = ServerBuilder.forPort(port)
.addService(new RPCDateServiceImpl())
.build().start();
System.out.println(String.format("GRpc服务端启动成功, 端口号: %d.", port));
server.awaitTermination();
}
}
定义客户端
package com.chenj;
import com.chenj.grpc.api.RPCDateRequest;
import com.chenj.grpc.api.RPCDateResponse;
import com.chenj.grpc.api.RPCDateServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class GRPCClient {
private static final String host = "localhost";
private static final int serverPort = 9999;
public static void main(String[] args) {
//1,拿到一个通信channel
ManagedChannel channel = ManagedChannelBuilder.forAddress(host, serverPort).
usePlaintext()//无需加密或认证
.build();
try {
//2.拿到stub对象
RPCDateServiceGrpc.RPCDateServiceBlockingStub rpcDateService = RPCDateServiceGrpc.newBlockingStub(channel);
RPCDateRequest rpcDateRequest = RPCDateRequest.newBuilder()
.setUserName("JACK")
.build();
//3,请求
RPCDateResponse rpcDateResponse = rpcDateService.getDate(rpcDateRequest);
//4,输出结果
System.out.println(rpcDateResponse.getServerDate());
} finally {
// 5.关闭channel, 释放资源.
channel.shutdown();
}
}
}
然后先启动Server:
再启动Client:
可以看到执行成功。一个简单的gRPC helloworld工程就搭建好了。当然具体到我们实际使用中,
还要搭配注册中心来完成rpc调用,不用自己写死域名、ip和端口
参考文献:3小时快速入门Java版gRPC系列(一)-了解RPC - 知乎
需要先生存
grpc服务注册与发现