目录
1、高性能网络通信框架
2、负载均衡算法
3、注册中心
4、自定义通信协议
5、杂谈
-----------------------------文中内容仅为个人理解,欢迎指点--------------------------------
在我设计的这个RPC框架中,网络通信框架是可以选择的。其中提供了原生Socket(BIO)进行传输和Netty(NIO)进行传输,下面我会简单介绍这两种框架的不同。
从BIO开始,顾名思义,阻塞的IO,BIO是最传统的I/O模型,它是同步阻塞的。在BIO中,当一个线程调用输入输出操作时,它会被阻塞直到操作完成。这意味着一个线程只能处理一个连接,如果有多个连接需要处理,就需要创建多个线程。这种模型在连接数较少时运行良好,但在高并发情况下,线程数量的增加会导致系统资源消耗过大。当然,我的程序中也进行了简单的优化,我会在接受到新的连接之后,单独创建一个线程去处理它,不过这不是一劳永逸的方法,随着连接数的增多,性能也会有下降;
然后就是NIO了,NIO是Java 1.4中引入的一种非阻塞I/O模型。它通过使用选择器(Selector)和通道(Channel)来实现非阻塞操作。在NIO中,一个线程可以管理多个连接,通过选择器监听多个通道的事件,当有事件发生时,线程可以处理这些事件。NIO适用于需要处理大量连接但每个连接处理时间较短的场景,如聊天服务器。
首先是大体实现流程,最简单的NIO实现是通过Channel实现的,建立连接之后,把连接放进一个专门存放Channel的集合中,接着去遍历Channel集合,假如该连接收到了消息,就会处理。这种做法只需要一个线程来处理连接的建立,把信息的处理交给了Channel集合,从而无需阻塞负责建立连接的线程。但是这种做法会有种浪费,Channel中的每一个连接不一定都有消息发送,而逐个遍历无疑是浪费资源的行为。
基于这个问题,引入了selector(多路复用器)。首先与上面说的一样,会建立Channel,然后存放到Channel集合中,但是同时会把这些Channel注册到多路复用器,来监听消息,从而做到只遍历处理有消息的Channel。
这个多路复用器涉及到一个叫epoll相关的函数底层实现,这些都是底层C语言的源码:
1、epoll_create:建立selector多路复用器,底层是一个结构体;
2、epoll_ctl:负责监听Channel集合,假如某个Channel有消息,就把这个Channel放到rdist中,这是一个专门存放有消息的Channel的集合;
3、epoll_wait:负责监听rdlist,也就是负责处理Channel中的消息。
那么还有一个问题,select、poll、epoll有什么区别?
select就是上面讲的NIO大体实现流程,它会遍历所有Channel,而且最大连接数有限制,是1024;而poll与select大体相似,只是更改了存储结构和使连接无上限。
奥还有一个AIO,这里也简单提一下吧。
AIO是Java 7中引入的一种异步非阻塞I/O模型。在AIO中,当一个线程发起输入输出操作时,它不会被阻塞,而是继续执行其他操作。当操作完成后,系统会通知线程进行处理。这种模型可以处理多个连接而不需要创建大量线程,因此在高并发情况下具有更好的性能。AIO是一种异步非阻塞I/O模型,通过异步回调机制来处理I/O操作,适用于需要处理大量并发操作的场景。
负载均衡算法是在RPC框架中用于分配请求到不同服务器的一种策略。它的目标是使得每台服务器的负载尽可能均衡,避免某些服务器过载而导致性能下降。这里简单介绍几种常见的负载均衡算法:
1、随机算法:随机选择一台服务器来处理请求。适用于服务器性能相似的情况;
2、轮询算法:按照顺序依次将请求分配给每台服务器,循环往复。适用于服务器性能相似的情况;
3、权重算法:为每台服务器设置一个权重值,根据权重比例分配请求。适用于服务器性能不均衡的情况;
4、最少连接算法:选择当前连接数最少的服务器来处理请求。适用于服务器性能不均衡的情况;
5、哈希算法:根据请求的某个属性(如IP地址、URL等)计算哈希值,将请求分配给对应的服务器。可以保证相同请求始终分配到同一台服务器上;
6、最短响应时间算法:根据服务器的响应时间选择最快的服务器来处理请求。
我的项目中实现了前两个算法,当然我的随机算法与Dubbo相比还是简单的多,当然也有点细节。比方说随机算法是动态的,只有当请求到达时才进行选择而不是启动时就确定,这样可以避免单个服务提供者过载的情况。而轮询算法则简单的多,通过一个整形变量来记录轮询位置,逐个使用。另外要说的是,我的服务是存放在一个List集合里交给负载均衡方法去选择的,所以各种算法实践起来较为简单,但实际要实现起来要考虑的因素要多得多,比方说要实时地检测某个服务是否健康。
在RPC框架中,注册中心是一个关键组件,用于管理和维护服务的注册和发现。注册中心充当了服务提供者和服务消费者之间的中介,使得它们能够动态地发现和通信。通过注册中心,服务提供者和消费者都无需关心底层实现,通俗地讲,一个负责放,一个负责拿就可以了。
注册中心的实现逻辑:
我的注册中心是通过一个HashMap集合来存放信息的,服务提供者只需将服务名称和地址传入即可,美中不足的是,每次新创建一个服务,都需要手动进行注册。由于是简易的注册中心,注册好的信息并没有上传到服务器,而是象征性地存放在本地txt文件中,当有新服务注册或有服务宕机会覆盖性地重新往txt写入最新的服务,也就是把这个txt当作一个存储信息的"服务器",当消费者想要调用某个服务时,注册中心会获取"服务器"中对应服务的所有地址,并进行负载均衡,最后获取服务的代理对象并返回给消费者,拿到代理对象之后消费者就可以自由地传参数了。
心跳检测机制:
刚刚讲到了不健康的服务将会被暂时移出注册中心,其实是根据心跳检测机制实现的,下面讲讲心跳检测机制的原理:
1、服务提供者在启动时,会向注册中心注册自己的服务信息,并同时启动一个心跳发送线程;
2、心跳发送线程会定期向注册中心发送心跳包,告知注册该服务提供者仍然存活。心跳包中包含了服务提供者的唯一标识符和当前时间戳等信息;
3、注册中心接收到心跳包后,会更新服务提供者的心跳时间戳,表示该服务提供者仍然处于活动状态;
4、除此之外,注册中心会定期检查每个服务的时间戳,并与当前时间作比较查看是否查过阈值,若超过则将该服务从txt文件中暂时移除,直至服务提供者再次发送心跳包,经注册中心检查之后会重新恢复。
这部份相比上面实际几个实际功能实现就略显抽象了。自定义通信协议是指开发人员可以根据自己的需求设计和实现的用于在客户端和服务器之间进行通信的规则和格式。具体能够自定义的内容包括但不限于下面几种:
1、消息格式:自定义通信协议需要定义消息的格式,包括消息头和消息体。消息头可以包含一些元数据信息,如消息类型、消息长度等。消息体则是具体的业务数据;
2、序列化和反序列化:通信协议需要定义数据的序列化和反序列化方式,将数据在网络中进行传输。常见的序列化方式有JSON、XML、Protobuf等。这里简单说下序列化这个概念。序列化是指将对象转换为字节流的过程,以便在网络传输或存储中进行传输或持久化。序列化的逆过程称为反序列化,即将字节流转换为对象;
3、安全性:自定义的安全机制,如加密和认证,用于保护通信过程中的数据安全。
当然在我的项目中我还是使用了HTTP协议,毕竟做这个项目只是为了学习而不是真的开发一个框架来使用,我的项目中也专门提供了一个可以自由填写的自定义通信协议,仅作举例。
public class CustomProtocol {
// 请求消息
public static class Request {
private int requestId;
private String methodName;
private Object[] parameters;
// 省略构造函数、getter和setter方法
}
// 响应消息
public static class Response {
private int requestId;
private Object result;
// 省略构造函数、getter和setter方法
}
// 将请求消息编码为字节数组
public static byte[] encodeRequest(Request request) {
// 使用字节流进行编码,将请求消息的各个字段按照自定义规则转换为字节数组
// ...
return encodedBytes;
}
// 将字节数组解码为请求消息
public static Request decodeRequest(byte[] bytes) {
// 使用字节流进行解码,将字节数组按照自定义规则转换为请求消息的各个字段
// ...
return request;
}
// 将响应消息编码为字节数组
public static byte[] encodeResponse(Response response) {
// 使用字节流进行编码,将响应消息的各个字段按照自定义规则转换为字节数组
// ...
return encodedBytes;
}
// 将字节数组解码为响应消息
public static Response decodeResponse(byte[] bytes) {
// 使用字节流进行解码,将字节数组按照自定义规则转换为响应消息的各个字段
// ...
return response;
}
}
这是一个基于TCP的二进制协议,包含请求和响应的数据结构,功能未做完善,仅作展示。
关于RPC框架还有一些常见的问题,这里做个整理:
1、PRC和HTTP调用方式有哪些差异点?
RPC通常使用自定义的协议和数据格式,可以直接调用远程服务的方法,类似于本地方法调用;而HTTP是一种基于请求-响应的协议,用于在客户端和服务器之间传输数据,通常使用URL作为资源定位符。
2、RPC和HTTP在身份校验上有什么区别?
RPC框架提供了自定义的身份验证机制,可以在请求和响应之间传递身份凭证信息,服务端会验证这戏信息的有效性和权限;HTTP本身并不提供身份验证机制,但是可以通过头部来传递验证信息。
3、RPC和HTTP在性能上有什么差异?
1、传输效率:RPC通常使用二进制数据格式进行传输,而HTTP使用文本格式(如JSON或XML)。二进制格式相对于文本格式来说,传输效率更高,占用的网络带宽更少,可以减少网络传输的开销;
2、序列化和反序列化开销:RPC框架通常使用高效的序列化和反序列化技术,如Protocol Buffers或Thrift,可以在数据的编码和解码过程中减少开销。而HTTP使用通用的文本格式,需要进行更复杂的解析和处理,可能会导致较高的序列化和反序列化开销;
3、连接复用和长连接:RPC框架通常支持连接的复用和长连接,可以减少连接的建立和断开开销,提高性能。而HTTP默认使用短连接,每次请求都需要建立和断开连接,会增加连接管理的开销。
4、在浏览器输入网址到打开网站页面整个过程发生了什么?(单纯有点忘了顺便记一下)
1. DNS解析:浏览器首先会将输入的网址发送给本地的DNS解析器,解析器会查询DNS服务器,获取对应网址的IP地址。如果DNS缓存中有对应的IP地址,则直接返回,否则进行递归查询。
2. 建立TCP连接:一旦浏览器获得了目标网站的IP地址,它会使用HTTP协议通过TCP/IP协议栈与目标服务器建立连接。这个过程涉及到三次握手,即客户端发送SYN包给服务器,服务器回复SYN+ACK包给客户端,最后客户端发送ACK包给服务器,建立起双向通信的连接。
3. 发送HTTP请求:建立TCP连接后,浏览器会发送HTTP请求给目标服务器。HTTP请求中包含请求的方法(GET、POST等)、请求的URL、请求头部信息等。
4. 服务器处理请求:目标服务器收到浏览器发送的HTTP请求后,会根据请求的URL和方法来处理请求。服务器可能会执行一些后端逻辑,如查询数据库、生成动态内容等。
5. 返回HTTP响应:服务器处理完请求后,会生成一个HTTP响应,包含响应的状态码、响应的头部信息和响应的内容。服务器将HTTP响应发送给浏览器。
6. 接收和渲染页面:浏览器接收到服务器发送的HTTP响应后,开始解析响应内容。如果响应内容是HTML页面,浏览器会解析HTML结构,构建DOM树;同时解析CSS样式,构建CSSOM树;然后将DOM树和CSSOM树合并成渲染树(Render Tree);最后根据渲染树进行布局和绘制,将页面内容显示在浏览器窗口中。
7. 关闭TCP连接:页面渲染完成后,浏览器会关闭与服务器之间的TCP连接。如果页面中有其他资源(如图片、脚本等),浏览器会重复步骤3到步骤6,发送请求并渲染这些资源。