五层协议中,RPC在第几层?

五层协议
应用层
传输层
网络层
链路层
物理层

我不知道,我要去大气层!

远程过程调用(RPC),比较朴素的说法就是,从某台机器调用另一台机器的一段代码,并获取返回结果。

这之前的一个基层问题就是进程间通信方式(IPC),从是否设计网络通信分为:

  • 基于信号量和共享内存实现的管道和消息队列和其本身(不涉及IP端口)
  • Socket(IP端口)

和共享内存不同,Socket实现不并不是只依靠内存屏障,它还额外需要物理/虚拟网卡设备。

关于网卡,只需要知道网卡可以帮助我们从网络中读写信息,这也是RPC的基础。

jRPC实现

远程过程调用,不如先来研究调用。

回声服务实现

先来一段普通的代码。

public class EchoService {

	public static EchoResponse echo(EchoRequest req) throws Exception {
return new EchoResponse("echo:" + req.content);
} public static void main(String[] args) throws Exception {
System.out.println(EchoService.echo(new EchoRequest("ping")).content); // echo:ping
}
} class EchoRequest {
String content; public EchoRequest(String content) {
this.content = content;
}
} class EchoResponse {
String content; public EchoResponse() {
} public EchoResponse(String content) {
this.content = content;
}
}

回声服务对传入参数直接返回,就像你在山谷中的回声一样。

现在如果使用远程传输,我们需要给网卡注册自己的IP和端口,以便和服务端建立连接。连接建立后,我们还需要确定数据如何传输。

服务端实现

为了朴素性,我们假设只有10台机器和我们进行连接。

public Runnable apply(Integer port) {
return () -> {
try {
try (ServerSocket serverSocket = new ServerSocket(port)) {
for (;;) {
Socket clientSocket = serverSocket.accept();
new Thread(() -> {
// 数据如何传输
}).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
};
}

根据Socket的文档,我们可以很快迭代出一台服务器应该如何与他的客户端连接。对于每个客户端,我们提供了独立的线程支持两台机器间的长连接。

试想一下,此时的长连接如果是百万甚至千万,为每个连接分配一个线程不可取,有什么好办法可以支持到呢?这个问题这里不解了,有兴趣自行研究下。

Serializable

一说起序列化,最怕异口同声json

使用json就难免会使用到 第三方库,如果没有必要,并不希望引入。除了json外,java其实本身就有Serializable实现,他和synchronized一样,java官方提供并维护。

public class EchoService {

	public static EchoResponse echo(EchoRequest req) throws Exception {
throw new UnsupportedOperationException();
}
} class EchoRequest implements Serializable {
String content; public EchoRequest(String content) {
this.content = content;
}
} class EchoResponse implements Serializable {
String content; public EchoResponse() {
} public EchoResponse(String content) {
this.content = content;
}
}

除了参数外,一个rpc需要知道,ip、端口、服务名、方法名。

ip和端口在调用时应该已经知道,为此还需要支持一个header来完成服务名和方法名的指定。

class Header implements Serializable {
String stub;
String method; public Header(String stub, String method) {
this.stub = stub;
this.method = method;
}
}

通过编码解码器对Serializable的数据编码和解码。

public class Codec {
Socket clientSocket;
ObjectInputStream objectInputStream;
ObjectOutputStream objectOutputStream; public Codec(Socket clientSocket)
throws Exception {
this.clientSocket = clientSocket;
this.objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream());
this.objectInputStream = new ObjectInputStream(clientSocket.getInputStream());
} public Header header() throws Exception {
return (Header) this.objectInputStream.readObject();
} public Object read() throws Exception {
return this.objectInputStream.readObject();
} public void write(Header header, Object obj) throws Exception {
this.objectOutputStream.writeObject(header);
this.objectOutputStream.writeObject(obj);
}
}

回到服务端,将空缺的地方通过反射补全。

Codec codec = new Codec(clientSocket);
for (;;) {
Header header = codec.header();
Class<?> stub = Class.forName(header.stub);
Map<String, Method> methods = Arrays.asList(stub.getDeclaredMethods()).stream()
.collect(Collectors.toMap(t -> t.getName(), t -> t));
Method method = methods.get(header.method);
codec.write(header, method.invoke(null, header, codec.read()));
}

通过codec解码stub和method来找到对应的方法,调用对应方法,获取结果后再通过编码返回客户端。

高性能客户端

想一下,如果一个客户端发送了10个请求,其中第2个由于种种原因被阻塞掉,后面的请求会被卡在阻塞的请求之后而无法获得响应。

简单的处理方法,就是抽象掉调用过程,并给其唯一标识。需要一个map来存全部的调用请求。

class Call {
Long seq;
Object req;
Object rsp;
Thread thread; public Call(Long seq, Object req) {
this.seq = seq;
this.req = req;
}
}

对call抽象后,对client也就迎刃而解了。

我知道了,map,用map解。

Long seq;
Codec codec;
ReentrantLock clock;
Map<Long, Call> calls;
ReentrantLock metux;

在map之上提供对seq的操作。

Call register(Call call) {
try {
clock.lock();
call.seq = seq;
calls.put(seq, call);
seq++;
return call;
} finally {
clock.unlock();
}
} Call remove(Call call) {
try {
clock.lock();
call.seq = seq;
calls.remove(seq);
return call;
} finally {
clock.unlock();
}
}

对服务端的响应监听,唤醒阻塞的线程。

void receive() throws Exception {
for (;;) {
Header header = codec.header();
Call call = calls.remove(header.seq);
Object rsp = codec.read();
call.rsp = rsp;
LockSupport.unpark(call.thread);
}
}

最后就是发起客户端调用的代码。

FutureTask<Object> start(Header header, Object req) throws Exception {
Call call = new Call(seq, req);
try {
metux.lock();
final Call fcall = register(call);
header.seq = call.seq;
codec.write(header, req);
FutureTask<Object> task = new FutureTask<>(() -> {
fcall.thread = Thread.currentThread();
LockSupport.park();
return fcall.rsp;
});
task.run();
return task;
} finally {
metux.unlock();
}
}

你好,世界

public static void main(String[] args) throws UnknownHostException, IOException, Exception {
new Thread(new Server().apply(8080)).start(); // 服务端启动
// 模拟调用
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
Client client = new Client(new Codec(new Socket("127.0.0.1", 8080)));
for (int i = 0; i < 100; i++) {
newFixedThreadPool.submit(() -> {
try {
FutureTask<Object> call = client.start(
new Header("EchoService", "echo"),
new EchoRequest("~hello"));
EchoResponse rsp = (EchoResponse) call.get();
System.out.println(rsp.content);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

Output

RPC echo~hello 0
RPC echo~hello 1
RPC echo~hello 2
RPC echo~hello 3
RPC echo~hello 4
RPC echo~hello 6
RPC echo~hello 5
RPC echo~hello 7
RPC echo~hello 9
RPC echo~hello 8

至此,只是实现了rpc的通信过程,完成度比较高。

  • 针对大流量的服务端还有优化空间,比如NIO的使用来管理长连接会更加有效。
  • 没有实现注册中心。

java实现朴素rpc的更多相关文章

  1. Java自带RPC实现,RMI框架入门

    Java自带RPC实现,RMI框架入门 首先RMI(Remote Method Invocation)是Java特有的一种RPC实现,它能够使部署在不同主机上的Java对象进行通信与方法调用,它是一种 ...

  2. Java简单的RPC实现(一)

    RPC使用java最基本的,传输层使用Socket,序列化使用Serializable,java 动态代理模式,但是未实现消息注册等相关信息 大道至简 server端 package com.rpc. ...

  3. 面试题思考:Java RMI与RPC,JMS的比较

    RPC:(Remote Procedure Call)  被设计为在应用程序间通信的平台中立的方式,它不理会操作系统之间以及语言之间的差异. 支持多语言 RMI:(Remote Method Invo ...

  4. Java分布式:RPC(远程过程调用)

    Java分布式:RPC(远程过程调用) 引入RPC 比如我们有一个查询的接口IDBQuery,以及其实现类DBQueryImp,如果我们执行IDBQuery查询方法,只需要new一个DBQueryIm ...

  5. java 远程调用 RPC

    1. 概念 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用).H ...

  6. Java RMI与RPC的区别

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6542811.html  一:RPC 远程过程调用 RPC(Remote Procedure Call Prot ...

  7. 【Java】分布式RPC通信框架Apache Thrift 使用总结

    简介 Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于th ...

  8. Java 简单的rpc 一

    一,简单rpc 是基于Java socket 编程 ServerSocket serverSocket = new ServerSocket(9999); System.out.println(&qu ...

  9. Java 简单的RPC 实现

    借用了网上某大神的例子.... 目录结构是这样的... RpcFramework 主要是两个方法.一个是暴露服务,一个为引用服务.暴露服务的主要作用是声明一个接口的实现类.可以通过socket 远程调 ...

  10. Java实现简单RPC框架(转)

    一.RPC简介 RPC,全称Remote Procedure Call, 即远程过程调用,它是一个计算机通信协议.它允许像本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用).H ...

随机推荐

  1. 使用ansible-app2k8s管理和部署服务到 kubernetes

    ansible-app2k8s #1 介绍 使用 ansible 管理和部署服务到 kubernetes 适用于项目容器化,多套 k8s 环境的管理,可结合CICD工具做DevOps 来自于项目实践, ...

  2. Spring Boot 整合组件套路

    自动配置类 Spring Boot 在整合任何一个组件的时候都会先添加一个依赖 starter,比如整合 MybatisPlus 有一个 mybatis-plus-boot-starter,如下: & ...

  3.  Python + unittest + ddt + HTMLTestRunner + log + excel + mysql + 企业微信通知, 接口自动化框架V2.0,支持多业务处理,仅需维护 excel 用例,无需要编写代码

    Python + unittest + ddt + HTMLTestRunner + log + excel + mysql + 企业微信通知 + Jenkins 实现的接口自动化框架. 项目介绍 接 ...

  4. 【Linq】Value cannot be null. (Parameter 'row')

    报错代码: 原因分析: Linq中左连接后,取值需要处理null值;如下图修改后恢复正常.

  5. 学习C++这一篇就够了(提升篇)

    C++中除了面向对象的编程思想外,还有另一种就是泛型编程 主要用到的技术就是模板 模板机制的分类: 函数模板 类模板 函数模板 作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体定制,用虚拟 ...

  6. 【MAUI Blazor踩坑日记】1.关于图标的处理

    前言 本系列文章,默认你已经踏上了MAUI Blazor的贼船,并且对MAUI Blazor有了一些了解,知道MAUI是什么,知道Blazor是什么. 不会教你怎么写MAUI Blazor的项目,只是 ...

  7. 【pandas小技巧】--修改列的名称

    重命名 pandas 数据中列的名称是一种常见的数据预处理任务.这通常是因为原始数据中的列名称可能不够清晰或准确.例如,列名可能包含空格.大写字母.特殊字符或拼写错误. 使用 pandas 的 ren ...

  8. quarkus依赖注入之八:装饰器(Decorator)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<quarkus依赖注入> ...

  9. 分布式存储系统举例剖析(elasticsearch,kafka,redis-cluster)

    1. 概述 对于分布式系统,人们首先对现实中的分布式系统进行高层抽象,然后做出各种假设,发展了诸如CAP, FLP 等理论,提出了很多一致性模型,Paxos 是其中最璀璨的明珠.我们对分布式系统的时序 ...

  10. 接到一个新需求应该怎么做?(V1.0)

    接到一个新需求应该怎么做?(V1.0) 1 背景 在做业务研发的时候,经常会接到一些 产品需求/技术需求, 无论需求大小,都需要一套可以重复使用的方法论,来保证整个项目的正常交付,这篇思考就是总结梳理 ...