Netty实现高性能RPC服务器优化篇之消息序列化
在本人写的前一篇文章中,谈及有关如何利用Netty开发实现,高性能RPC服务器的一些设计思路、设计原理,以及具体的实现方案(具体参见:谈谈如何使用Netty开发实现高性能的RPC服务器)。在文章的最后提及到,其实基于该方案设计的RPC服务器的处理性能,还有优化的余地。于是利用周末的时间,在原来NettyRPC框架的基础上,加以优化重构,本次主要优化改造点如下:
1、NettyRPC中对RPC消息进行编码、解码采用的是Netty自带的ObjectEncoder、ObjectDecoder(对象编码、解码器),该编码、解码器基于的是Java的原生序列化机制,从已有的文章以及测试数据来看,Java的原生序列化性能效率不高,而且产生的序列化二进制码流太大,故本次在优化中,引入RPC消息序列化协议的概念。所谓消息序列化协议,就是针对RPC消息的序列化、反序列化过程进行特殊的定制,引入第三方编解码框架。本次引入的第三方编解码框架有Kryo、Hessian。这里,不得不再次提及一下,对象序列化、反序列化的概念,在RPC的远程服务调用过程中,需要把消息对象通过网络传输,这个就要用到序列化将对象转变成字节流,到达另外一端之后,再反序列化回来变成消息对象。
2、引入GoogleGuava并发编程框架对NettyRPC的NIO线程池、业务线程池进行重新梳理封装。
3、利用第三方编解码框架(Kryo、Hessian)的时候,考虑到高并发的场景下,频繁的创建、销毁序列化对象,会非常消耗JVM的内存资源,影响整个RPC服务器的处理性能,因此引入对象池化(ObjectPooling)技术。众所周知,创建新对象并初始化,可能会消耗很多的时间。当需要产生大量对象的时候,可能会对性能造成一定的影响。为了解决这个问题,除了提升硬件条件之外,对象池化技术就是这方面的银弹,而ApacheCommonsPool框架就是对象池化技术的一个很好的实现(开源项目路径:http://commons.apache.org/proper/commons-pool/download_pool.cgi)。本文中的Hessian池化工作,主要是基于ApacheCommonsPool框架,进行封装处理。
本文将着重,从上面的三个方面,对重构优化之后的NettyRPC服务器的实现思路、实现方式进行重点讲解。首先请大家简单看下,本次优化之后的NettyRPC服务器支持的序列化协议,如下图所示:
可以很清楚的看到,优化之后的NettyRPC可以支持Kryo、Hessian、Java本地序列化三种消息序列化方式。其中Java本地序列化方式,相信大家应该很熟悉了,再次不在重复讲述。现在我们重点讲述一下,另外两种序列化方式:
1、Kryo序列化。它是针对Java,而定制实现的高效对象序列化框架,相比Java本地原生序列化方式,Kryo在处理性能上、码流大小上等等方面有很大的优化改进。目前已知的很多著名开源项目,都引入采用了该序列化方式。比如alibaba开源的dubboRPC等等。本文中采用的Kryo的默认版本是基于:kryo-3.0.3。它的下载链接是:https://github.com/EsotericSoftware/kryo/releases/tag/kryo-parent-3.0.3。为什么采用这个版本?主要原因我上面也说明了,出于应对高并发场景下,频繁地创建、销毁序列化对象,会非常消耗JVM的内存资源、以及时间。Kryo的这个发行版本中,集成引入了序列化对象池功能模块(KryoFactory、KryoPool),这样我们就不必再利用ApacheCommonsPool对其进行二次封装。
2、Hessian序列化。Hessian本身是一种序列化协议,它比Java原生的序列化、反序列化速度更快、序列化出来的数据也更小。它是采用二进制格式进行数据传输,而且,目前支持多种语言格式。本文中采用的是:hessian-4.0.37版本,它的下载链接是:http://hessian.caucho.com/#Java。
接下来,先来看下优化之后的NettyRPC的消息协议编解码包(newlandframework.netty.rpc.serialize.support、newlandframework.netty.rpc.serialize.support.kryo、newlandframework.netty.rpc.serialize.support.hessian)的结构,如下图所示:
其中RPC请求消息结构代码如下:
复制代码
/
@filename:MessageRequest.java
NewlandCo.Ltd.Allrightsreserved.
@Description:rpc服务请求结构
@authortangjie
@version1.0
/
packagenewlandframework.netty.rpc.model;
importjava.io.Serializable;
importorg.apache.commons.lang.builder.ReflectionToStringBuilder;
publicclassMessageRequestimplementsSerializable{
privateStringmessageId;
privateStringclassName;
privateStringmethodName;
privateClass>[]typeParameters;
privateObject[]parametersVal;
publicStringgetMessageId(){
returnmessageId;
}
publicvoidsetMessageId(StringmessageId){
this.messageId=messageId;
}
publicStringgetClassName(){
returnclassName;
}
publicvoidsetClassName(StringclassName){
this.className=className;
}
publicStringgetMethodName(){
returnmethodName;
}
publicvoidsetMethodName(StringmethodName){
this.methodName=methodName;
}
publicClass>[]getTypeParameters(){
returntypeParameters;
}
publicvoidsetTypeParameters(Class>[]typeParameters){
this.typeParameters=typeParameters;
}
publicObject[]getParameters(){
returnparametersVal;
}
publicvoidsetParameters(Object[]parametersVal){
this.parametersVal=parametersVal;
}
publicStringtoString(){
returnReflectionToStringBuilder.toStringExclude(this,newString[]{"typeParameters","parametersVal"});
}
}
复制代码
RPC应答消息结构,如下所示:
复制代码
/
@filename:MessageResponse.java
NewlandCo.Ltd.Allrightsreserved.
@Description:rpc服务应答结构
@authortangjie
@version1.0
/
packagenewlandframework.netty.rpc.model;
importjava.io.Serializable;
importorg.apache.commons.lang.builder.ReflectionToStringBuilder;
publicclassMessageResponseimplementsSerializable{
privateStringmessageId;
privateStringerror;
privateObjectresultDesc;
publicStringgetMessageId(){
returnmessageId;
}
publicvoidsetMessageId(StringmessageId){
this.messageId=messageId;
}
publicStringgetError(){
returnerror;
}
publicvoidsetError(Stringerror){
this.error=error;
}
publicObjectgetResult(){
returnresultDesc;
}
publicvoidsetResult(ObjectresultDesc){
this.resultDesc=resultDesc;
}
publicStringtoString(){
returnReflectionToStringBuilder.toString(this);
}
}
复制代码
现在,我们就来对上述的RPC请求消息、应答消息进行编解码框架的设计。由于NettyRPC中的协议类型,目前已经支持Kryo序列化、Hessian序列化、Java原生本地序列化方式。考虑到可扩展性,故要抽象出RPC消息序列化,协议类型对象(RpcSerializeProtocol),它的代码实现如下所示:
复制代码
/
@filename:RpcSerializeProtocol.java
NewlandCo.Ltd.Allrightsreserved.
@Description:RPC消息序序列化协议类型
@authortangjie
@version1.0
/
packagenewlandframework.netty.rpc.serialize.support;
importorg.apache.commons.lang.builder.ReflectionToStringBuilder;
importorg.apache.commons.lang.builder.ToStringStyle;
publicenumRpcSerializeProtocol{
//目前由于没有引入跨语言RPC通信机制,暂时采用支持同构语言Java序列化/反序列化机制的第三方插件
//NettyRPC目前已知的序列化插件有:Java原生序列化、Kryo、Hessian
JDKSERIALIZE("jdknative"),KRYOSERIALIZE("kryo"),HESSIANSERIALIZE("hessian");
privateStringserializeProtocol;
privateRpcSerializeProtocol(StringserializeProtocol){
this.serializeProtocol=serializeProtocol;
}
publicStringtoString(){
ReflectionToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE);
returnReflectionToStringBuilder.toString(this);
}
publicStringgetProtocol(){
returnserializeProtocol;
}
}
复制代码
针对不同编解码序列化的框架(这里主要是指Kryo、Hessian),再抽象、萃取出一个RPC消息序列化/反序列化接口(RpcSerialize)、RPC消息编解码接口(MessageCodecUtil)。
复制代码
/
@filename:RpcSerialize.java
NewlandCo.Ltd.Allrightsreserved.
@Description:RPC消息序列化/反序列化接口定义
@authortangjie
@version1.0
/
packagenewlandframework.netty.rpc.serialize.support;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
publicinterfaceRpcSerialize{
voidserialize(OutputStreamoutput,Objectobject)throwsIOException;
Objectdeserialize(InputStreaminput)throwsIOException;
}
复制代码
复制代码
/
@filename:MessageCodecUtil.java
NewlandCo.Ltd.Allrightsreserved.
@Description:RPC消息编解码接口
@authortangjie
@version1.0
/
packagenewlandframework.netty.rpc.serialize.support;
importio.netty.buffer.ByteBuf;
importjava.io.IOException;
publicinterfaceMessageCodecUtil{
//RPC消息报文头长度4个字节
finalpublicstaticintMESSAGE_LENGTH=4;
publicvoidencode(finalByteBufout,finalObjectmessage)throwsIOException;
publicObjectdecode(byte[]body)throwsIOException;
}
复制代码
最后我们的NettyRPC框架要能自由地支配、定制Netty的RPC服务端、客户端,采用何种序列化来进行RPC消息对象的网络传输。因此,要再抽象一个RPC消息序列化协议选择器接口(RpcSerializeFrame),对应的实现如下:
复制代码
/
@filename:RpcSerializeFrame.java
NewlandCo.Ltd.Allrightsreserved.
@Description:RPC消息序序列化协议选择器接口
@authortangjie
@version1.0
/
packagenewlandframework.netty.rpc.serialize.support;
importio.netty.channel.ChannelPipeline;
publicinterfaceRpcSerializeFrame{
publicvoidselect(RpcSerializeProtocolprotocol,ChannelPipelinepipeline);
}
复制代码
现在有了上面定义的一系列的接口,现在就可以定制实现,基于Kryo、Hessian方式的RPC消息序列化、反序列化模块了。先来看下整体的类图结构:
首先是RPC消息的编码器MessageEncoder,它继承自Netty的MessageToByteEncoder编码器。主要是把RPC消息对象编码成二进制流的格式,对应实现如下:
复制代码
/
@filename:MessageEncoder.java
NewlandCo.Ltd.Allrightsreserved.
@Description:RPC消息编码接口
@authortangjie
@version1.0
/
packagenewlandframework.netty.rpc.serialize.support;
importio.netty.buffer.ByteBuf;
importio.netty.channel.ChannelHandlerContext;
importio.netty.handler.codec.MessageToByteEncoder;
publicclassMessageEncoderextendsMessageToByteEncoder |
|