1、讲一讲什么是RPC?
说到RPC就必须要聊一聊单体项目和分布式/微服务项目
单体项目时:一次服务调用发生在同一台机器上的 同一个进程内部 ,也就是说调用发生在本机内部,因此也被叫作本地方法调用。
分布式/微服务项目时:服务提供者和服务消费者运行在两台不同物理机上的不同进程内 ,它们之间的调用相比于本地方法调用,可称之为远程方法调用,简称 RPC。
RPC(Remote Procedure Call ——远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术。
Dubbo :阿里2011年开源,一个经典的RPC框架(基于TCP的)。
还有springcloud中的Feign也是一个RPC的框架,不过它是基于http的。
2、讲一讲RPC的底层原理!
RPC包含哪些部分?
- 客户端和服务端建立网络连接模块( server模块、client模块 )
- 服务端处理请求模块
- 协议模块
- 序列化和反序列模块。
设计RPC会考虑哪些问题?
- 代理问题
- 序列化问题
- 服务注册问题
代理问题
代理本质上是要解决什么问题?要解决的是被调用的服务本质上是远程的服务,但是调用者不知道也不关心,调用者只要结果,具体的事情由代理的那个对象来负责这件事。既然是远程代理,当然是要用代理模式了。
代理(Proxy)是一种设计模式,即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。那我们这里额外的功能操作是干什么,通过网络访问远程服务。
jdk的代理有两种实现方式:静态代理和动态代理。
public class Client2 {
//远程调用类
public static IUserService getStub() throws Exception{
//创建代理类
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket("127.0.0.1", 8888);
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
dos.writeInt(13);
socket.getOutputStream().write(out.toByteArray());
socket.getOutputStream().flush();
DataInputStream dis = new DataInputStream(socket.getInputStream());
int ReceId = dis.readInt();
String name = dis.readUTF();
User user = new User(ReceId, name);
dos.close();
socket.close();
return user;
}
};
//执行动态代理(传入类加载器、接口、代理对象; 返回对象)
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),
new Class[]{IUserService.class},handler);
return (IUserService)o;
}
}
序列化问题
序列化问题在计算机里具体是什么?我们的方法调用,有方法名,方法参数,这些可能是字符串,可能是我们自己定义的java的类,但是在网络上传输或者保存在硬盘的时候,网络或者硬盘并不认得什么字符串或者javabean,它只认得二进制的01串,怎么办?要进行序列化,网络传输后要进行实际调用,就要把二进制的01串变回我们实际的java的类,这个叫反序列化。java里已经为我们提供了相关的机制Serializable。
影响序列化性能的关键因素:
- 序列化后的码流大小(网络带宽的占用)
- 序列化的性能(CPU资源占用)
- 是否支持跨语言(异构系统的对接和开发语言切换)。
Java默认提供的序列化:
无法跨语言、序列化后的码流太大、序列化的性能差
XML:
优点:人机可读性好,可指定元素或特性的名称。
缺点:序列化数据只包含数据本身以及类的结构,不包括类型标识和程序集信息;只能序列化公共属性和字段;不能序列化方法;文件庞大,文件格式复杂,传输占带宽。
适用场景:当做配置文件存储数据,实时数据转换。
JSON
是一种轻量级的数据交换格式,
优点:兼容性高、数据格式比较简单,易于读写、序列化后数据较小,可扩展性好,兼容性好、与XML相比,其协议比较简单,解析速度比较快。
缺点:数据的描述性比XML差、不适合性能要求为ms级别的情况、额外空间开销比较大。
适用场景(可替代XML):
跨防火墙访问、可调式性要求高、基于Web browser的Ajax请求、传输数据量相对小,实时性要求相对低(例如秒级别)的服务。
Fastjson
采用一种“假定有序快速匹配”的算法。
优点:接口简单易用、目前java语言中最快的json库。
缺点:过于注重快,而偏离了“标准”及功能性、代码质量不高,文档不全、安全漏洞较多。
适用场景:协议交互、Web输出、Android客户端
Thrift
不仅是序列化协议,还是一个RPC框架。
优点:序列化后的体积小, 速度快、支持多种语言和丰富的数据类型、对于数据字段的增删具有较强的兼容性、支持二进制压缩编码。
缺点:使用者较少、跨防火墙访问时,不安全、不具有可读性,调试代码时相对困难、不能与其他传输层协议共同使用(例如HTTP)、无法支持向持久层直接读写数据,即不适合做数据持久化序列化协议。
适用场景:分布式系统的RPC解决方案
Protobuf
将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性。
优点:序列化后码流小,性能高、结构化数据存储格式(XML JSON等)、通过标识字段的顺序,可以实现协议的前向兼容、结构化的文档更容易管理和维护。
缺点:需要依赖于工具生成代码、支持的语言相对较少,官方只支持Java 、C++ 、python。适用场景:对性能要求高的RPC调用、具有良好的跨防火墙的访问属性、适合应用层对象的持久化
服务注册问题
在Dubbo中,往往会选用Zookeeper做服务注册中心
从Zookeeper的数据结构特点看,并不是基于服务注册而设计的,ZooKeeper提供的命名空间与文件系统的名称空间非常相似,在数据结构上高度抽象为K-V格式,十分通用。
ZooKeeper组件支持节点短暂存在,只要创建znode的会话处于活动状态,这些znode就会存在,会话结束时,将删除znode。Dubbo框架正是基于这个特点,服务启动往Zookeeper注册的就是临时节点,需要定时发心跳到Zookeeper来续约节点,并允许服务下线时,将Zookeeper上相应的节点删除,同时Zookeeper使用ZAB协议虽然保证了数据的强一致性。
最后说到这里不得不提一下Redis,也可以作为注册中心使用,只是用的不多。(因为他的服务的检测功能比较弱)
Eureka组件
SpringCloud框架生态中最原生的深度结合组件,Eureka是Netflix开发的服务发现框架,基于REST的服务,主要用于服务注册,管理,负载均衡和服务故障转移。但是官方声明在Eureka2.0版本停止维护,不建议使用。
Nacos组件
Nacos致力于发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您实现动态服务发现、服务配置管理、服务及流量管理。Nacos更敏捷和容易地构建、交付和管理微服务平台。
Nacos 是构建以“服务”为中心的现代应用架构(例如微服务范式、云原生范式)的服务基础设施。Nacos支持作为RPC注册中心,例如:支持Dubbo框架;也具备微服务注册中心的能力,例如:SpringCloud框架
3、什么Netty?讲一下你对Netty的理解
本质:网络应用程序框架
实现:异步、事件驱动
特性:高性能、可维护、快速开发
用途:开发服务器和客户端
Netty的性能很高,按照Facebook公司开发小组的测试表明,Netty最高能达到接近百万的吞吐。
Netty程序
maven中引入4.1.25.Final的版本
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.28.Final</version>
</dependency>
代码如下:
重要的类、方法解析
EventLoop
EventLoop暂时可以看成一个线程、EventLoopGroup自然就可以看成线程组。
网络编程里,“服务器”和“客户端”实际上表示了不同的网络行为;换句话说,是监听传入的连接还是建立到一个或者多个进程的连接。因此,有两种类型的引导:一种用于客户端(简单地称为Bootstrap),而另一种(ServerBootstrap)用于服务器。无论你的应用程序使用哪种协议或者处理哪种类型的数据,唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器。
ServerBootstrap将绑定到一个端口,因为服务器必须要监听连接,而Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。
引导一个客户端只需要一个EventLoopGroup,但是一个ServerBootstrap 则需要两个,因为服务器需要两组不同的Channel。第一组将只包含一个ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的Channel。
Channel 是Java NIO 的一个基本构造。
它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作
目前,可以把Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。
事件和ChannelHandler、ChannelPipeline
Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。
可能由入站数据或者相关的状态更改而触发的事件包括:
连接已被激活或者连接失活;数据读取;用户事件;错误事件。
出站事件是未来将会触发的某个动作的操作结果,这些动作包括:
打开或者关闭到远程节点的连接;将数据写到或者冲刷到套接字。
每个事件都可以被分发给ChannelHandler 类中的某个用户实现的方法。
Netty 提供了大量预定义的可以开箱即用的ChannelHandler 实现,包括用于各种协议(如HTTP 和SSL/TLS)的ChannelHandler。
ChannelFuture
Netty 中所有的I/O 操作都是异步的。
JDK 预置了interface java.util.concurrent.Future,Future 提供了一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以Netty提供了它自己的实现——ChannelFuture,用于在执行异步操作的时候使用。
每个Netty 的出站I/O操作都将返回一个 ChannelFuture 。