RPC

解决的问题

RPC 主要是为了解决的两个问题:

  • 解决分布式系统中,服务之间的调用问题。

  • 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。

这一节我们来学习下如何基于 websocket 实现最简单的 rpc 调用,后续会实现基于 netty4 的版本。

开源地址: https://github.com/houbb/rpc

完整流程

其中左边的Client,对应的就是前面的Service A,而右边的Server,对应的则是Service B。

下面一步一步详细解释一下。

  1. Service A的应用层代码中,调用了Calculator的一个实现类的add方法,希望执行一个加法运算;

  2. 这个Calculator实现类,内部并不是直接实现计算器的加减乘除逻辑,而是通过远程调用Service B的RPC接口,来获取运算结果,因此称之为Stub;

  3. Stub怎么和Service B建立远程通讯呢?这时候就要用到远程通讯工具了,也就是图中的Run-time Library,这个工具将帮你实现远程通讯的功能,比如Java的Socket,就是这样一个库,当然,你也可以用基于Http协议的HttpClient,或者其他通讯工具类,都可以,RPC并没有规定说你要用何种协议进行通讯;

  4. Stub通过调用通讯工具提供的方法,和Service B建立起了通讯,然后将请求数据发给Service B。需要注意的是,由于底层的网络通讯是基于二进制格式的,因此这里Stub传给通讯工具类的数据也必须是二进制,比如calculator.add(1,2),你必须把参数值1和2放到一个Request对象里头(这个Request对象当然不只这些信息,还包括要调用哪个服务的哪个RPC接口等其他信息),然后序列化为二进制,再传给通讯工具类,这一点也将在下面的代码实现中体现;

  5. 二进制的数据传到Service B这一边了,Service B当然也有自己的通讯工具,通过这个通讯工具接收二进制的请求;

  6. 既然数据是二进制的,那么自然要进行反序列化了,将二进制的数据反序列化为请求对象,然后将这个请求对象交给Service B的Stub处理;

  7. 和之前的Service A的Stub一样,这里的Stub也同样是个“假玩意”,它所负责的,只是去解析请求对象,知道调用方要调的是哪个RPC接口,传进来的参数又是什么,然后再把这些参数传给对应的RPC接口,也就是Calculator的实际实现类去执行。很明显,如果是Java,那这里肯定用到了反射。

  8. RPC接口执行完毕,返回执行结果,现在轮到Service B要把数据发给Service A了,怎么发?一样的道理,一样的流程,只是现在Service B变成了Client,Service A变成了Server而已:Service B反序列化执行结果->传输给Service A->Service A反序列化执行结果 -> 将结果返回给Application,完毕。

简单实现

假设服务 A,想调用服务 B 的一个方法。

因为不在同一个内存中,无法直接使用。如何可以实现类似 Dubbo 的功能呢?

这里不需要使用 HTTP 级别的通信,使用 TCP 协议即可。

common

公用模块,定义通用对象。

  • Rpc 常量
public interface RpcConstant {

    /**
* 地址
*/
String ADDRESS = "127.0.0.1"; /**
* 端口号
*/
int PORT = 12345; }
  • 请求入参
public class RpcCalculateRequest implements Serializable {

    private static final long serialVersionUID = 6420751004355300996L;

    /**
* 参数一
*/
private int one; /**
* 参数二
*/
private int two; //getter & setter & toString()
}
  • 服务接口
public interface Calculator {

    /**
* 计算加法
* @param one 参数一
* @param two 参数二
* @return 返回结果
*/
int add(int one, int two); }

server

  • 服务接口的实现
public class CalculatorImpl implements Calculator {

    @Override
public int add(int one, int two) {
return one + two;
} }
  • 启动服务
public static void main(String[] args) throws IOException {
Calculator calculator = new CalculatorImpl();
try (ServerSocket listener = new ServerSocket(RpcConstant.PORT)) {
System.out.println("Server 端启动:" + RpcConstant.ADDRESS + ":" + RpcConstant.PORT);
while (true) {
try (Socket socket = listener.accept()) {
// 将请求反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
System.out.println("Request is: " + object);
// 调用服务
int result = 0;
if (object instanceof RpcCalculateRequest) {
RpcCalculateRequest calculateRpcRequest = (RpcCalculateRequest) object;
result = calculator.add(calculateRpcRequest.getOne(), calculateRpcRequest.getTwo());
}
// 返回结果
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

启动日志:

Server 端启动:127.0.0.1:12345

client

  • 客户端调用
public static void main(String[] args) {
Calculator calculator = new CalculatorProxy();
int result = calculator.add(1, 2);
System.out.println(result);
}
  • 计算的代理类
public class CalculatorProxy implements Calculator {

    @Override
public int add(int one, int two) {
try {
Socket socket = new Socket(RpcConstant.ADDRESS, RpcConstant.PORT); // 将请求序列化
RpcCalculateRequest calculateRpcRequest = new RpcCalculateRequest(one, two);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); // 将请求发给服务提供方
objectOutputStream.writeObject(calculateRpcRequest); // 将响应体反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object response = objectInputStream.readObject(); if (response instanceof Integer) {
return (Integer) response;
} else {
throw new RuntimeException();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
  • 调用日志

client 端

3

server 端

Server 端启动:127.0.0.1:12345
Request is: RpcCalculateRequest{one=1, two=2}

开源地址

为了便于大家学习,以上源码已经开源:

https://github.com/houbb/rpc

我是老马,期待与你的下次重逢。

java 从零开始手写 RPC (01) 基于 websocket 实现的更多相关文章

  1. java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

    说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...

  2. java 从零开始手写 RPC (04) -序列化

    序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...

  3. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  4. java 从零开始手写 RPC (07)-timeout 超时处理

    <过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...

  5. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

  6. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  7. 手写RPC框架(六)整合Netty

    手写RPC框架(六)整合Netty Netty简介: Netty是一个基于NIO的,提供异步,事件驱动的网络应用工具,具有高性能高可靠性等特点. 使用传统的Socket来进行网络通信,服务端每一个连接 ...

  8. 从零开始手写 dubbo rpc 框架

    rpc rpc 是基于 netty 实现的 java rpc 框架,类似于 dubbo. 主要用于个人学习,由渐入深,理解 rpc 的底层实现原理. 前言 工作至今,接触 rpc 框架已经有很长时间. ...

  9. OpenCV手写数字字符识别(基于k近邻算法)

    摘要 本程序主要参照论文,<基于OpenCV的脱机手写字符识别技术>实现了,对于手写阿拉伯数字的识别工作.识别工作分为三大步骤:预处理,特征提取,分类识别.预处理过程主要找到图像的ROI部 ...

随机推荐

  1. Flink与Strom两个框架的对比分析

    一.Flink与Storm两个框架的对比 二.Flink 的特性 1.高吞吐.低延迟.高性能 2.支持带事件的窗口(window) 操作:time.count.session.data-driven ...

  2. tensorflow models flags 初步使用

    参考官方仓库:https://github.com/tensorflow/models/tree/master/official/utils/flags 测试Demo代码如下: from absl i ...

  3. 剑指offer 计划1(栈与队列)---java

    1.1.题目1 剑指 Offer 09. 用两个栈实现队列 1.2.解法 解法如题目所说.定义两个栈.这里假设第一个栈为a,第二个栈为b. 实现两个函数增加尾和删除头. 增加即直接push入第一个栈. ...

  4. ubuntu18.04 更换镜像源

    废话不多说,直接上图了 1. 首先选software & update 2. 点这个,然后选择others,选择China 建议选择清华源,不建议选择mirrors.aliyun.com,因为 ...

  5. canal数据同步

    前面提到数据库缓存不一致的几种解决方案,但是在不同的场景下各有利弊,而今天我们使用的canal进行缓存与数据同步的方案是最好的,但是也有一个缺点,就是相对前面几种解决方案会引入阿里巴巴的canal组件 ...

  6. Kubernetes-kubectl介绍

    前言 本篇是Kubernetes第三篇,大家一定要把环境搭建起来,看是解决不了问题的,必须实战.本篇重要介绍kubectl的使用. Kubernetes系列文章: Kubernetes介绍 Kuber ...

  7. C#中的“等待窗体”对话框

    这篇文章向您展示了如何在c#.net Windows窗体应用程序中创建一个等待窗体对话框.创建一个新表单,然后输入您的表单名称为frmWaitForm.接下来,将Label,Progress Bar控 ...

  8. python库--requests

    requests 方法 返回 参数 方法详情 .get()  r  url  get请求 params  url?后面的内容会以'key=value'的方式接到url后面 proxies 设置代理ip ...

  9. Playfield 类方法的注释

    前言 本篇随笔的底包采用的是百度炉石兄弟吧20200109折腾版中自带的 routines 文件. 本次仅为绝大多数方法添加 xml 注释和简单解析,没有具体解析与重构. Playfield 类方法众 ...

  10. Sonarqube C# 配置资料整理

    c#配置方式: http://www.cnblogs.com/CoderAyu/p/9416376.html http://www.cnblogs.com/jingridong/p/6513884.h ...