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. 【GIS】点图层符号的方向和大小

    方向:根据属性字段设置点图层中每个要素的符号方向和大小, 1 所有要素使用同一种符号---简单渲染 在图层属性---符号系统---单一符号中进行设置,首先设置符号,在后面的[高级]选项按钮中分别设置[ ...

  2. linux的一般命令------附加

    1.cat /etc/redhat-release             查看linux版本 2. ps -ef|grep ssh                        查看进程在不在 3. ...

  3. 深入理解Java类加载器(二):线程上下文类加载器

    摘要: 博文<深入理解Java类加载器(一):Java类加载原理解析>提到的类加载器的双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器的实现方式.在Java ...

  4. Struts2之处理请求参数

    时间:2017-1-11 11:05 --Struts2中获取请求参数(重点)1.Struts2是一个MVC框架,那么分别表示什么?    View:JSP    Model:Action    Co ...

  5. Java 大数加法HdAcm1002

    1 import java.util.Scanner; 2 3 4 public class Main { 5 public static void main(String[] args) { 6 S ...

  6. 微信小程序学习笔记三 路由的基本使用

    小程序中路由的使用 1.1 页面路由 在小程序中, 所有页面的路由全部由框架进行管理 1.2 页面栈 框架以栈的形式维护了当前的所有页面, 当发生路由切换的时候, 页面栈的表现如下: 1.3 获取当前 ...

  7. 用Java8把List转为Map

    1 import com.yang.test.User; 2 3 import javax.jws.soap.SOAPBinding; 4 import java.util.*; 5 import j ...

  8. 单片机学习(九)定时器扫描按钮和数码管与PWM的使用

    目录 一.使用定时器扫描按钮和数码管 1. 使用定时器进行扫描的缘由 2. 定时器扫描独立按钮 3. 定时器扫描数码管 二.PWM的使用 1. PWM简介 2. LED呼吸灯 实现一 实现二 3. 按 ...

  9. @RequestParam注解的详细介绍

    @RequestParam (org.springframework.web.bind.annotation.RequestParam)用于将指定的请求参数赋值给方法中的形参. 有三个属性: (1)v ...

  10. Python网编之简单的聊天小程序

    服务端: import socket sock = socket.socket() sock.bind(("127.0.0.1",8899)) sock.listen(5) whi ...