RPC框架(一)

一、背景

早期经常会在单机上跑我们所有的程序和软件,把所有软件和应用都部署在一台机器上。单机的处理能力主要依靠 CPU、内存、磁盘。通过更换硬件做垂直扩展的方式来提升性能,成本会越来越高,单机处理能力存在瓶颈,CPU、内存都会有自己的性能瓶颈,单机系统存在可用性和稳定性的问题。

随着网站的上线,访问量逐步上升,服务器的负载慢慢提高,假如代码层面的优化已经没办法继续提高,在不能提高单台机器的性能,增加机器是一个比较好的方式,可以把web服务器和数据库服务器进行拆分,这样提高了单机的负载能力,也提高了容灾能力。

随着访问量的继续增加,单台应用服务器已经无法满足需求,我们可以增加应用服务器,通过应用服务器集群将用户请求分流到各个服务器中,从而继续提升负载能力。此时多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务,用户请求可以由Nginx、Apache来转发到具体的应用服务器,通过配置tomcat的session共享来实现是同一个session,虽然增加了服务器的个数,但是对于数据库的负载也在慢慢增大。如果我们也是像增加服务器一样仅仅把数据库一分为二,然后对于后续数据库的请求,分别负载到两台数据库服务器上,那么一定会造成数据库不统一的问题。所以我们还要考虑读写分离的方式,一个数据库进行读操作,一个数据库进行写操作然后刷新到读数据库,而后继续添加搜索引擎和缓冲机制来缓解数据库压力。

添加数据库操作做的改变还只是进行在同一个库中,我们还可以考虑对数据的垂直拆分和水平拆分分别放到不同库中,垂直拆分:把数据库中不同业务数据拆分到不同的数据库,水平拆分:把同一个表中的数据拆分到两个甚至跟多的数据库中(某些业务数据量已经达到了单个数据库的瓶颈)。

随着业务的发展,业务量越来越大,应用的压力越来越大。工程规模也越来越庞大。这个时候就可以考虑将应用拆分,按照领域模型拆分成多个子系统。那么服务拆分以后,各个服务之间如何进行远程通信呢? 所以就要用到RPC 技术。

 

二、RPC

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP/IP或UDP,为通信程序之间携带信息数据。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。在OSI网络通信模型中,RPC跨域了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。RPC实现框架有RMI(java最基础的RPC)、Hessian(与spring可以结合)、Facebook的Thrift(跨语言)等。微服务用的有微博的开源RPC框架高性能的Motan,google的开源的跨语言调用型的gRPC,高成熟度的服务治理型的Dubbo(使用Hessian作为序列化,Netty通信)等。

1、RPC组件

一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。服务端的存根也称为skeleton(骨架)。

客户端(Client),服务的调用方。

客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。

服务端(Server),真正的服务提供者。

服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法

2、运行过程

客户端(client)以本地调用方式(即以接口的方式)调用服务;客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);客户端通过sockets将消息发送到服务端;

服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);服务端存根( server stub)根据解码结果调用本地的服务;本地服务执行并将结果返回给服务端存根( server stub);服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);

服务端(server)通过sockets将消息发送到客户端;客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);客户端(client)得到最终结果。

3、实现RPC需要的技术

1.传输协议

通过socket进行TCP连接传输数据,socket即套接字,是一个对TCP/IP协议进行封装的编程调用接口。HttpURLConnection支持 HTTP特定功能的URLConnection,可以发送HTTP请求,进行HTTP连接传输数据,底层由socket实现。

2.网络通信

可以使用BIO和NIO来进行网络数据读写,在一些RPC框架中使用Netty实现IO通信框架。

3.方法的调用

方法的远程调用使用的是JDK的动态代理,客户端获取的是代理对象,通过代理对象进行连接实现真正方法调用,因为JDK动态代理是面对接口的,所以需要把接口暴露出来。

4.序列化和反序列化

需要把方法名和参数进行传输,所以需要用到序列化技术,使用的最简单的objectStream,序列化技术的好不好,主要看序列化以后的数据大小,序列化操作本身的速度及系统资源开销(CPU、内存)。流行的序列化包括Protobuf、Kryo、Hessian、Jackson、Thrift。

5.服务注册中心

我们如何让调用者知道,存在哪些服务可以调用呢?即如何让别人使用我们的服务呢?那就需要告诉使用者服务的IP以及端口,所以需要一个Registry(注册中心)的角色,用于服务端注册远程服务以及客户端发现服务,服务端对外提供后台服务,将自己的服务信息注册到注册中心,客户端从注册中心获取远程服务的注册信息,然后进行远程过程调用。目前主要的注册中心可以借由zookeeper,eureka,consul,etcd,redis等开源框架实现。

 

三、RMI

JDK实现的一个RPC框架,能直接传输序列化后的Java对象和分布式垃圾收集。它的实现依赖于Java虚拟机(JVM),因此它仅支持从一个JVM到另一个JVM的调用。RMI的优势:这种机制给分布计算的系统设计、编程都带来了极大的方便,可以不必再过问在RMI之下的网络细节了。RMI可将完整的对象作为参数和返回值进行传递。RMI使用专门为保护系统免遭恶意小程序侵害而设计的安全管理程序。

1、RMI实例

1.接口

因为JDK动态代理是面对接口的,Remote接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用。

public interface IService extends Remote{
	public String say(String message) throws RemoteException;
}

2.接口的实现

UnicastRemoteObject类提供很多方法,这些方法可以通过JRMP协议导出一个远程对象的引用,并通过动态代理构建一个可以和远程对象交互的Stub对象。

public class ServiceImpl extends UnicastRemoteObject implements IService {

	private static final long serialVersionUID = 1L;

	protected ServiceImpl() throws RemoteException {
		super();
	}
	@Override
	public String say(String message) throws RemoteException {
		
		return "hello" + message;
	}

}

3.实现服务端

Registry:为了方便client获取到stub对象

rebind方法,通过一个名称映射到该远程对象的引用,客户端通过该名称获取该远程对象的引用

public class Server {
	public static void main(String[] args) {
		Registry registry = null;
		try {
			registry = LocateRegistry.createRegistry(8088);
		} catch (RemoteException e) {

		}
		try {
			Remote server = new ServiceImpl();
			registry.rebind("vince", server);
			System.out.println("bind server");

		} catch (RemoteException e) {

		}
	}
}

4.实现客户端

lookup方法,这个方法通过一个指定的名称来获取,该名称必须与远程对象服务器绑定的名称一致

IP地址在这里就相当于远程对象的引用,而DNS则相当于一个注册表(Registry)。而域名在RMI中就相当于远程对象的标识符,客户端通过提供远程对象的标识符访问注册表,来得到远程对象的引用

public class Client {

	public static void main(String[] args) {
		Registry registry = null;
		try {
			registry = LocateRegistry.getRegistry("127.0.0.1", 8088);
			String[] list = registry.list();
			for (String s : list)
				System.out.println(s);
		} catch (RemoteException e) {

		}
		try {
			IService server = (IService) registry.lookup("vince");
			String result = server.say("张三");
			System.out.println("result from remote : " + result);
		} catch (AccessException e) {

		} catch (RemoteException e) {

		} catch (NotBoundException e) {

		}

	}
}

 

2、RMI运行过程

通信过程都是通过stub(存根)和skeleton(骨架)之间来实现的。

Stub为远程对象在客户端的一个代理,客户端通过lookup方法来获取代理对象就是获取Stub,当客户端调用远程对象的方法时,实际是委托Stub这个代理去调用远程的对象,初始化一个与远程JVM的连接;写入并传输参数给远程JVM;执行远程对象的方法调用,并等待调用结果的返回(return);读取调用的返回值(也可能是一个异常);返回调用的结果给调用者。这些操作(序列号参数, 建立Socket连接等等),都由这个Stub来透明化。

而在服务端每一个对象(需要被远程调用的对象)都有一个相应的skeleton,skeleton的代理对象用来接收stub发来的信息,分发客户端的调用到具体的实现类。读取客户端传递过来的参数;调用实现类的方法;写入并传输返回结果给调用者,同样的,这个结果也是函数调用结果或异常。服务端只是通过lookup方法去获取stub,并不是客户端创建的,两个代理对象都是在服务端创建的。

3、RMI原理

 

Server通过createRegistry方法会生成Stub和Skeleton对象,所有与网络相关的代码都放在了stub和skeleton中,当client想调用server上方法的时候,就可以调用stub上的相同的方法,但是stub里面只有和网络相关的处理逻辑,并没有对应的业务处理逻辑,他仅仅包含如何连接到远程的skeleton、调用方法的详细信息、参数、返回值等等。rebind方法把stub放到Registry的map集合中,client通过lookup获取stub对象。

在服务端new一个远程对象时会调用父类UnicastRemoteObject的构造方法,父类中调用exportObject()方法,把UnicastServerRef类(实现了远程对象的发布服务)作为参数传进去,继续调用UnicastServerRef(注册中心的远程引用对象)的exportObject方法,创建代理stub对象,后调用setSkeleton方法创建skeleton(后面调用createSkeleton),之后new一个Target对象(之前创建的对象都集中在这里,包括stub和Skeleton),继续exportObject方法把Target 对象作为参数,方法内创建ServerSocket并开启线程接收连接,线程执行的任务就是获取原先的Target对象,实现真正的实例方法调用。或者createRegistry,会实例化一个RegistryImpl,里面调用setup方法传UnicastServerRef,后面的过程和前面一样,把stub设置为Skeleton。

当客户端获得stub对象的时候执行调用远程对象方法,当通过调用lookup方法获取stub对象时,内部会调用newCall()方法来建立与Skeleton对象的连接,在newCall()方法里面的对象类StreamRemoteCall的executeCall()方法通过tcp连接发送消息到服务端,之后通过流传输数据返回stub对象。客户端调用本地方法实际上调用的是stub动态代理对象,里面invoke方法就是通过序列化流把参数传过去接收结果返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值