一、RPC原理学习
1.1什么是RPC
RPC(Remote Procedure Call Protocol) ——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
总的来说,RPC就是可以让我们在客户端,通过网络传输协议,远程调用服务端相关类的相关方法,而无需在客户端程序中编写任何实现类的逻辑。
下面我们就通过一个RPC的设计思路,来更近一步的了解RPC。
注:这里需要具备Java的Socket相关基础,如果不是太了解,请先阅读我之前的相关博文进行学习:
《【Netty入门和实践】1.传统的socket分析》
《【Netty入门和实践】2.NIO的样例代码分析》
二、一个RPC框架的设计思路
我们需要实现的效果:
上图就是我们在一个服务A中使用Spring的“@Autowired”注解,注入一个“OrderService”接口的实现类,并调用相关方法。而这个注入的实现类,以及相关实现方法,不在服务A中,而是通过RPC远程调用去从服务B的实现类“OrderServiceImpl”。
要实现这种机制,需要构建相关的客户端和服务端的RPC逻辑。
备注:该图引用于传智播客《零基础大数据教学视频》
1.服务端:
在服务B中被远程调用的实现类“OrderServiceImpl”,是通过一个类似“@RpcService("xxxService")”的注解,启动一个Socket服务,来对外暴露该“OrderService”接口的实现类。
在服务B中,当项目启动加载时,去扫描“@RpcService("xxxService")”的注解,拿到注解中的“xxxService”接口名称,然后借助Spring的bean注入,将相关的“xxxService”接口的实现类构造出来。此时,在Spring容器中,就已经加载出相关的接口对应的实现类,就如同我们在之前的xml配置的Bean一样:
此时将容器中加载好的各种Service存放在Map(HashMap)中,id为对应Service的接口名称,value放置Spring加载好的对应的实现类。
Map构造好之后,在服务B中启动一个SocketServer服务,即启动一个(TCP/UDP)长连接服务。该服务来监听某个端口的请求。客户端通过RPC框架发送请求,使用Socket客户端请求SocketServer服务的对应端口,向服务端传输需要注入的Service接口名称、方法名称、参数名称。SocketServer服务端收到客户端的请求后,通过Map获取用户需要的Service接口的实现类,然后通过反射加载相关实现类,并调用相关方法,传递相关参数。
上面这一系列就是一个RPC框架帮我们做到的,此时服务端的开发人员,只需要在相关的实现类上填写注解,即可实现Service服务实现类的暴露。
2.客户端
在上面的设计图中,服务A中使用Spring的“@Autowired”注解来进行“OrderService”接口实现类的注入,这里注入的并不是一个真正的实现类,而是一个动态代理。看似我们在客户端调用的“OrderService”接口的“createOrder()”方法,但实际上是调用的动态代理对象的相关方法。
(注:不了解动态代理的同学,请参见我之前的博文:
《【设计模式】动态代理Proxy_01》
在我们为“OrderService”创建的动态代理实现类中,在调用代理方法时,使用classLoader类加载器进行相关接口的加载,并以参数的形式,传递给构建代理实现类的方法“createProxy”,此时动态代理类就会返回一个代理实现对象“proxy”,其实现了“OrderService”接口。
完成上述操作,此时就可以通过代理实现对象“proxy”调用“createOrder()”方法,在该方法中,我们使用反射的invoke将加载接口中的相关参数,结合classLoader类加载器加载的“OrderService”接口,将Service接口名称、方法名称、参数名称封装成一个request请求,通过Socket网络传输请求,将这些信息传输给服务B暴露的SocketServer服务。
相关参数通过Socket传输至服务B的SocketServer服务后,SocketServer服务将传输过来的request请求进行decode解码,获取传输过来的Service接口名称、方法名称、参数名称,然后去Map中获取请求的Service接口的实现类,通过反射加载该实现类,调用相关方法并传输相关的参数,并将相应的实现类的调用结果封装在response请求反馈对象中,进行encode编码,然后通过Socket反馈给客户端。
此时客户端收到相关response反馈后,进行decode解码,将相关实现类调用的方法结果,反馈给相关的调用类。
上面这一系列也是一个RPC框架帮我们做到的,此时客户端的开发人员,只需要在相关的注入类上填写注解,即可实现Service服务实现类的加载,以及相关方法的调用,无需关注底层实现。
3.注册中心
上面的客户端与服务端进行交互时,肯定要知道相关的服务端的调用地址等信息,那么这些信息需要一个注册中心来进行协调。
常用的服务发现与注册中心为Zookeeper,在该注册中信息,完成的功能就是:
(1)服务端将自己暴露了服务地址、哪些实现类,哪些方法注册到注册中心,供客户端去发现。
(2)客户端将自己需要调用的接口名称和对应方法发送给注册中心,注册中心帮其寻找注册在中心上的服务是否有符合要求的,如果有,按照一定规则提供相关服务的调用地址。
(3)注册中心还可以使用负载均衡来调整服务端与客户端的调用。
4.高性能的Socket
在上面的传输过程中,使用了Socket进行网络传输。而这里是可能出现瓶颈的地方,因为往往有很多台客户端去服务端的服务,此时传统的第一代Socket,是阻塞型IO,对大量多线程请求的支持十分不友好,很有可能造成请求拥堵。所以目前在RPC的传输中,使用新一代的NIO框架(如Netty),即非阻塞型多线程网络请求,有很好的承载能力。
通过上面的一个自定义RPC框架的设计思路,相信大家应该对RPC有一定的了解了。
后面会对高性能的NIO、以及NIO框架进行一系列的学习,为后面手动实现RPC框架打基础。
参考:
传智播客《2017零基础大数据》教学视频