分布式架构网络通信(RPC,RMI) 03

文章目录

  • [1. 基本原理](#1. 基本原理)
  • [2. RPC 远程过程调用](#2. RPC 远程过程调用)
  • [3. RMI 远程方法调用](#3. RMI 远程方法调用)
  • [4. RMI代码实现](#4. RMI代码实现)
  • [4. BIO、NIO、AIO](#4. BIO、NIO、AIO)
    • [4.1 同步和异步](#4.1 同步和异步)
    • [4.2 阻塞和非阻塞](#4.2 阻塞和非阻塞)
    • [4.3 BIO](#4.3 BIO)
    • [4.4 NIO](#4.4 NIO)
    • [4.5 AIO](#4.5 AIO)

1. 基本原理

要实现网络机器间的通讯,首先得来看看计算机系统网络通信的基本原理,在底层层面去看,网络通信需要做的就 是将流从一台计算机传输到另外一台计算机,基于传输协议和网络IO来实现,其中传输协议比较出名的有tcp、 udp等等,tcp、udp都是在基于Socket概念上为某类应用场景而扩展出的传输协议,网络IO,主要有bio、nio、 aio三种方式,所有的分布式应用通讯都基于这个原理而实现,只是为了应用的易用,各种语言通常都会提供一些更为贴近应用易用的应用层协议。

2. RPC 远程过程调用

RPC全称为remote procedure call,即远程过程调用。

借助RPC可以做到像本地调用一样调用远程服务,是一种进程间的通信方式

比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上的 应用提供的方法,由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。

需要注意的是RPC并不是一个具体的技术,而是指整个网络远程调用过程。

RPC架构 一个完整的RPC架构里面包含了四个核心的组件,分别是ClientClient StubServer 以及Server Stub,这个Stub可以理解为存根。

  1. 客户端(Client),服务的调用方。
  2. 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远 程发送给服务方。
  3. 服务端(Server),真正的服务提供者。
  4. 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。

    (1)客户端(client)以本地调用方式(即以接口的方式)调用服务;
    (2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对 象序列化为二进制);
    (3) 客户端通过sockets将消息发送到服务端;
    (4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
    (5) 服务端存根( server stub)根据解码结果调用本地的服务;
    (6) 本地服务执行并将结果返回给服务端存根( server stub);
    (7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
    (8) 服务端(server)通过sockets将消息发送到客户端;
    (9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);
    (10) 客户端(client)得到最终结果。 RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。

注意:无论是何种类型的数据,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二 进制流,而数据的接收方则需要把二进制流再恢复为对象。

在java中RPC框架比较多,常见的有Hessian、gRPC、Thrift、HSF (High Speed Service Framework)、Dubbo等,其实对 于RPC框架而言,核心模块 就是通讯和序列化

3. RMI 远程方法调用

Java RMI 指的是远程方法调用 (Remote Method Invocation),是java原生支持的远程调用 ,采用JRMP(Java Remote Messageing protocol)作为通信协议,可以认为是纯java版本的分布式远程调用解决方案, RMI主要用于不同虚拟机之间的通信,这些虚拟机可以在不同的主机上、也可以在同一个主机上,这里的通信可以理解为一个虚拟机上的对象调用另一个虚拟机上对象的方法。

客户端:

1)存根/桩(Stub):远程对象在客户端上

  1. 远程引用层(Remote Reference Layer):解析并执行远程引用协议;

3)传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果。

服务端:

1)骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法, 并接收方法执行后的返回值;

2)远程引用层(Remote Reference Layer):处理远程引用后向骨架发送远程方法调用;

3)传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。

注册表(Registry):以URL形式注册远程对象,并向客户端回复对远程对象的引用。


4. RMI代码实现

服务端

1)定义Remote 子接口,在其内部定义要发布的远程方法,并且这些方法都要Throws RemoteException

2)定义实现远程接口,并且继承:UnicastRemoteObject

3)启动服务器:依次完成注册表的启动和远程对象绑定。

客户端:

1)通过符合JRMP规范的URL字符串在注册表中获取并强转成Remote子接口对象;

2)调用这个Remote子接口对象中的某个方法就是为一次远程方法调用行为。

1.创建远程接口

java 复制代码
import java.rmi.Remote; 
import java.rmi.RemoteException; /** * 远程服务对象接口必须继承Remote接口;同时方法必须抛出RemoteExceptino异常 */ 

public interface Hello extends Remote { 
	public String sayHello(User user) throws RemoteException;
}

2. 创建引用对象User实体类

java 复制代码
import java.io.Serializable; 
/** * 引用对象应该是可序列化对象,这样才能在远程调用的时候:1. 序列化对象 2. 拷贝 3. 在网络中传输 * 4. 服务端反序列化 5. 获取参数进行方法调用; 这种方式其实是将远程对象引用传递的方式转化为值传递的方式 */ 
@Data
public class User implements Serializable {
	private String name; 
	private int age; 

3. 实现远程服务对象

java 复制代码
import java.rmi.RemoteException; 
import java.rmi.server.UnicastRemoteObject; 
/** * 远程服务对象实现类写在服务端;必须继承UnicastRemoteObject或其子类 **/ 

public class HelloImpl extends UnicastRemoteObject implements Hello {
/** * 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,必须声明抛出RemoteException异常 * * @throws RemoteException */

private static final long serialVersionUID = 3638546195897885959L;

protected HelloImpl() throws RemoteException { 
	super(); // TODO Auto-generated constructor stub
	}
	
@Override public String sayHello(User user) throws RemoteException { System.out.println("this is server, hello:" + user.getName()); return "success";
}
}

4. 服务端程序

java 复制代码
import java.net.MalformedURLException; 
import java.rmi.Naming; 
import java.rmi.RemoteException; 
import java.rmi.registry.LocateRegistry; /** * 服务端程序 **/ 

public class Server {
public static void main(String[] args) {
	try { 
	Hello hello = new HelloImpl(); 
	LocateRegistry.createRegistry(8080); 
	try {
		Naming.bind("//127.0.0.1:8080/zm", hello); 
	} catch (MalformedURLException e) { 
	e.printStackTrace();
}
	System.out.println("service bind already!!");
	} catch (RemoteException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
  1. 客户端程序
java 复制代码
import java.net.MalformedURLException; 
import java.rmi.Naming; import 
java.rmi.NotBoundException; 
import java.rmi.RemoteException; /** * 客户端程序 * @author zm * */

public class Client { public static void main(String[] args) { 
try {
//在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法 
	Hello hello = (Hello) Naming.lookup("//127.0.0.1:8080/zm");//获取远程对象 
	User user = new User(); user.setName("james");
	System.out.println(hello.sayHello(user));
} catch (MalformedURLException e) { // TODO Auto-generated catch block 	
    e.printStackTrace();
} catch (RemoteException e) { // TODO Auto-generated catch block 
	e.printStackTrace();
} catch (NotBoundException e) { // TODO Auto-generated catch block 	
  	e.printStackTrace();
}
}

启动服务端程序
客户端调用

4. BIO、NIO、AIO

4.1 同步和异步

同步(synchronize)、异步(asychronize)是指应用程序和内核的交互而言的.

同步: 指用户进程触发IO操作等待或者轮训的方式查看IO操作是否就绪。

同步举例: 银行取钱,我自己去取钱,取钱的过程中等待.

异步:当一个异步进程调用发出之后,调用者不会立刻得到结果。而是在调用发出之后,被调用者通过状态、通知来通知 调用者,或者通过回调函数来处理这个调用。

异步举例: 我请朋友帮我取钱,他取到钱后返回给我. (委托给操作系统OS, OS需要支持IO异步API)

4.2 阻塞和非阻塞

阻塞和非阻塞是针对于进程访问数据的时候,根据IO操作的就绪状态来采取不同的方式.

简单点说就是一种读写操作方法的实现方式. 阻塞方式下读取和写入将一直等待, 而非阻塞方式下,读取和写入方法会理解返回一个状态值.

阻塞:

ATM机排队取款,你只能等待排队取款(使用阻塞IO的时候,Java调用会一直阻塞到读写完成才返回。)

非阻塞:

柜台取款,取个号,然后坐在椅子上做其他事,等广播通知,没到你的号你就不能去,但你可以不断的问大堂经理 排到了没有。(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行

读写,不断循环直到读写完成)

4.3 BIO

同步阻塞IO。B代表blocking

服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连 接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

适用场景:Java1.4之前唯一的选择,简单易用但资源开销太高

4.4 NIO

同步非阻塞IO (non-blocking IO / new io)是指JDK 1.4 及以上版本。 服务器实现模式为一个请求一个通道,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接 有IO请求时才启动一个线程进行处理。

通道(Channels)

NIO 新引入的最重要的抽象是通道的概念。Channel 数据连接的通道。 数据可以从Channel读到Buffer中,也可以 从Buffer 写到Channel中

缓冲区(Buffers)

通道channel可以向缓冲区Buffer中写数据,也可以像buffer中存数据。

选择器(Selector)

使用选择器,借助单一线程,就可对数量庞大的活动 I/O 通道实时监控和维护

当一个连接创建后,不会需要对应一个线程 ,这个连接会被注册到多路复用器,所以一个连接只需要一个线程即 可,所有的连接需要一个线程就可以操作,该线程的多路复用器会轮训,发现连接有请求时,才开启一个线程处

理。

4.5 AIO

异步非阻塞IO。A代表asynchronize 当有流可以读时,操作系统会将可以读的流传入read方法的缓冲区,并通知应用程序,对于写操作,OS将write方法的流 写入完毕是操作系统会主动通知应用程序。因此read和write都是异步 的,完成后会调用回调函数。

使用场景:连接数目多且连接比较长(重操作)的架构,比如相册服务器。重点调用了OS参与并发操作,编程比较复杂。Java7开始支持

相关推荐
P.H. Infinity1 小时前
【RabbitMQ】03-交换机
分布式·rabbitmq
龙哥·三年风水3 小时前
群控系统服务端开发模式-应用开发-个人资料
分布式·php·群控系统
Dann Hiroaki4 小时前
GPU架构概述
架构
茶馆大橘4 小时前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel
点点滴滴的记录4 小时前
RPC核心实现原理
网络·网络协议·rpc
徒步僧4 小时前
ThingsBoard规则链节点:RPC Call Reply节点详解
qt·microsoft·rpc
coding侠客5 小时前
揭秘!微服务架构下,Apollo 配置中心凭啥扮演关键角色?
微服务·云原生·架构
lipviolet6 小时前
架构系列---高并发
架构
funnyZpC6 小时前
quartz集群增强版🎉
java·分布式·开源·集群·定时任务
Phodal6 小时前
架构赋能 AI:知识工程推动下的软件架构数字化
人工智能·架构