基于netty的高性能RPC服务器技术简介:RPC 远程过程调用协议,是一种通过网络,向远程计算机程序上请求服务,而不必了解底层网络技术的协议。简单点说就是客户端在不必知道调用细节的前提下,调用远程计算机上运行的某个对象,使用起来就像调用本地的对象一样。
目前典型的RPC框架有Facebook开源的Thrift、阿里巴巴的Dubbo等等。RPC针对网络协议和网络IO是透明的,对于调用的客户端而言仿佛就是在调用本地的对象一样。 在传输层网络协议上运行的是TCP协议、UDP协议亦或是Http协议不关心。 在网络IO模型上,是基于select、poll还是epoll方式都不需要关心。
目前主流的RPC框架都支持跨语言的调用,适用于异构系统。在影响一个RPC框架性能中,RPC网络IO的选择至关重要,在网络IO的基础上可以支持同步阻塞IO、非阻塞同步IO、多路复用IO模型和异步IO模型。此外传输协议选择上,目前主流的都是基于TCP协议。
以上基本上描述清楚了RPC服务器是个什么东西,现在我就使用Java,基于高性能NIO框架netty 做一个高性能的RPC服务器。
在开始做之前我们需要考虑一些问题:
1)怎么实现、基于什么原理?
2)并发性能怎么样?
为了提高通信的性能,基于Java我们一般都是使用Java的NIO,但是JDK中的NIO使用不太方便,并且使用起来需要对NIO有很深的技术功底,所以我们一般首选Java的NIO框架netty,而且netty还能帮我们解决TCP粘包、网络通信异常、消息链接处理等等网络通信细节问题。netty也是基于TCP,对于处理高并发也是绰绰有余。这里我基于netty4来进行开发。
下面先来简单介绍一下技术原理:
1.技术原理
(1)定义RPC 请求消息、 应答消息的结构, RPC接口里面需要包括:接口唯一ID标识、远程调用类名、方法名、参数结构、参数值等信息。
(2)服务端初始化的时候会通过Spring加载RPC接口定义和实现类对象的映射关系,然后等待客户端发起调用请求。
(3)客户端将请求消息体通过网络以字节流的方式发送给RPC服务器。
(4)服务端收到客户端的请求消息后,根据消息体的内容到容器中找对应的具体实现对象。RPC服务端找到实现对象的参数信息,通过反射机制创建该对象的实例,并返回调用处理结果,最后封装成RPC应答消息通知到客户端。
(5)客户端通过网络接收响应消息,进行解码然后显示调用结果。
上面的介绍说的很简单,但是具体实现起来有很多问题需要考虑,比如:
(1)我们是基于TCP来进行数据传输的,很典型的在TCP的底层如果出现了黏包怎么办?基于netty怎么解决?好在netty给出了解决方案,使用LengthFieldBasedFrameDecoder解码器
(2)netty的服务端线程模型是单线程、多线程(一个线程负责处理客户端连接,然后丢给后台的IO处理线程池处理)、还是主从模式(客户端连接、后端IO处理都是基于线程池的实现)。这里为了提高性能,我们使用了netty的主从线程池模型
(3)netty的IO处理线程池,如果碰到了非常耗时的业务出现了阻塞怎么办?这就很容易把后端的NIO线程给挂死。这里为了处理这种问题,用到了Java的异步回调功能,将业务直接分派到业务线程池处理,处理完后异步回调处理。
(4)RPC请求和响应消息的编码和解码字节流使用哪一种方式?原生的Java序列化,还是更加优秀的开源方式,比如Google的protobuf。 这里因为不涉及到异构系统,为了方便起见首先使用JDK原生序列化方式。
(5)服务端得考虑在多线程和高并发情况下的处理,所以必须是线程安全的,获得线程安全的程序就肯定会涉及到加锁。这里不适用原生提供的悲观锁synchronized, 而使用轻量级的显示锁ReentrantLock对代码块加锁。
下面通过一个示意图来表示服务端接收客户端的请求之后的处理流程:
服务端的netty连接器接收到了客户端的并发请求之后,由netty分发出N个NIO连接线程,这时候netty的连接器任务就结束了。之后把这N个NIO连接线程统一的加入netty的NIO处理线程池进行管理,在NIO处理线程池对请求消息进行解码或则编码等操作。
之后再分派给单独的Handler线程来进行处理,这里的handler线程也就是我们自己实现的handler。在handler进行消息处理的时候,为了提高性能,防止耗时任务的阻塞,这里直接把复杂的消息处理过程丢给我自己实现的专门的RPC业务处理线程池来处理,然后handler对应的NIO线程立即返回不阻塞。当RPC业务处理线程池结束之后通过回调机制异步通知客户端请求结果。
在netty的服务端程序中,最核心的就是handler的实现,也就是核心业务的处理,这里对netty中对RPC请求和响应的编解码过程中涉及到的编解码器和流程做一个分析。(这里使用的是基于JDK原生序列化来序列化消息数据)。