Using Efficient Java Serialization in Dubbo (Kryo and FST)

[Transfer: http://blog.csdn.net/moonpure/article/details/53175519]

 

Serialization Talk

Dubbo RPC is a high-performance, high-throughput remote call method at the core of the dubbo system. I like to call it a multiplexed TCP long connection call. Simply put:

  • Long connection: avoids creating a new TCP connection every time, and improves the response speed of the call
  • Multiplexing: A single TCP connection can alternately transmit multiple request and response messages, reducing the idle waiting time of the connection, thereby reducing the number of network connections with the same number of concurrent connections and improving system throughput.

Dubbo RPC is mainly used for remote calls between two dubbo systems, especially suitable for Internet scenarios with high concurrency and small data.

Serialization also plays a crucial role in the response speed, throughput, and network bandwidth consumption of remote calls, and is one of the most critical factors for us to improve the performance of distributed systems.

In dubbo RPC, multiple serialization methods are supported at the same time, such as:

  1. dubbo serialization: Ali has not yet developed a mature and efficient java serialization implementation, Ali does not recommend using it in the production environment
  2. hessian2 serialization: hessian is a cross-language efficient binary serialization method. But this is actually not the native hessian2 serialization, but the hessian lite modified by Ali, which is the serialization method enabled by default in dubbo RPC
  3. JSON serialization: At present, there are two implementations, one is using Ali's fastjson library, and the other is using the simple JSON library implemented by Dubbo, but the implementation is not particularly mature, and the text serialization performance of JSON is Generally not as good as the above two binary serialization.
  4. Java serialization: It is mainly implemented by using the Java serialization that comes with JDK, and the performance is not ideal.

In general, the performance of these four main serialization methods decreases from top to bottom. For dubbo RPC, which is a high-performance remote calling method, there are actually only two efficient serialization methods, 1 and 2, which are relatively suitable, and the first dubbo serialization is immature, so only 2 are actually available. So dubbo RPC uses hessian2 serialization by default.

But hessian is an older serialization implementation, and it is cross-language, so it is not optimized for Java alone . Dubbo RPC is actually a Java to Java remote call. In fact, there is no need to use cross-language serialization (of course, cross-language serialization is certainly not excluded).

In recent years, various new efficient serialization methods have emerged one after another, constantly refreshing the upper limit of serialization performance. The most typical ones include:

  • Specifically for the Java language: Kryo, FST, etc.
  • Cross-language: Protostuff, ProtoBuf, Thrift, Avro, MsgPack and more

Most of these serialization methods perform significantly better than hessian2 (even the immature dubbo serialization).

In view of this, we introduce two efficient Java serialization implementations, Kryo and FST, for dubbo to gradually replace hessian2.

Among them, Kryo is a very mature serialization implementation, which has been widely used in Twitter, Groupon, Yahoo and many famous open source projects (such as Hive, Storm). And FST is a newer serialization implementation that lacks enough mature use cases, but I think it's still very promising.

For production-oriented applications, I would recommend Kryo as the preferred option for now.

Enable Kryo and FST

Using Kryo and FST is as simple as adding a property to the XML configuration of the dubbo RPC:

<dubbo:protocol name="dubbo" serialization="kryo"/>
<dubbo:protocol name="dubbo" serialization="fst"/>

Register the serialized class

To make Kryo and FST fully performant, it is best to register those classes that need to be serialized into the dubbo system. For example, we can implement the following callback interface:

public class SerializationOptimizerImpl implements SerializationOptimizer {

    public Collection<Class> getSerializableClasses() { List<Class> classes = new LinkedList<Class>(); classes.add(BidRequest.class); classes.add(BidResponse.class); classes.add(Device.class); classes.add(Geo.class); classes.add(Impression.class); classes.add(SeatBid.class); return classes; } }

Then in the XML configuration add:

<dubbo:protocol name="dubbo" serialization="kryo" optimizer="com.alibaba.dubbo.demo.SerializationOptimizerImpl"/>

在注册这些类后,序列化的性能可能被大大提升,特别针对小数量的嵌套对象的时候。

当然,在对一个类做序列化的时候,可能还级联引用到很多类,比如Java集合类。针对这种情况,我们已经自动将JDK中的常用类进行了注册,所以你不需要重复注册它们(当然你重复注册了也没有任何影响),包括:

GregorianCalendar
InvocationHandler
BigDecimal
BigInteger
Pattern
BitSet
URI
UUID
HashMap
ArrayList
LinkedList
HashSet
TreeSet
Hashtable
Date
Calendar
ConcurrentHashMap
SimpleDateFormat
Vector
BitSet
StringBuffer
StringBuilder
Object
Object[]
String[]
byte[]
char[]
int[]
float[]
double[]

由于注册被序列化的类仅仅是出于性能优化的目的,所以即使你忘记注册某些类也没有关系。事实上,即使不注册任何类,Kryo和FST的性能依然普遍优于hessian和dubbo序列化。

当然,有人可能会问为什么不用配置文件来注册这些类?这是因为要注册的类往往数量较多,导致配置文件冗长;而且在没有好的IDE支持的情况下,配置文件的编写和重构都比java类麻烦得多;最后,这些注册的类一般是不需要在项目编译打包后还需要做动态修改的。

另外,有人也会觉得手工注册被序列化的类是一种相对繁琐的工作,是不是可以用annotation来标注,然后系统来自动发现并注册。但这里annotation的局限是,它只能用来标注你可以修改的类,而很多序列化中引用的类很可能是你没法做修改的(比如第三方库或者JDK系统类或者其他项目的类)。另外,添加annotation毕竟稍微的“污染”了一下代码,使应用代码对框架增加了一点点的依赖性。

除了annotation,我们还可以考虑用其它方式来自动注册被序列化的类,例如扫描类路径,自动发现实现Serializable接口(甚至包括Externalizable)的类并将它们注册。当然,我们知道类路径上能找到Serializable类可能是非常多的,所以也可以考虑用package前缀之类来一定程度限定扫描范围。

当然,在自动注册机制中,特别需要考虑如何保证服务提供端和消费端都以同样的顺序(或者ID)来注册类,避免错位,毕竟两端可被发现然后注册的类的数量可能都是不一样的。

无参构造函数和Serializable接口

如果被序列化的类中不包含无参的构造函数,则在Kryo的序列化中,性能将会大打折扣,因为此时我们在底层将用Java的序列化来透明的取代Kryo序列化。所以,尽可能为每一个被序列化的类添加无参构造函数是一种最佳实践(当然一个java类如果不自定义构造函数,默认就有无参构造函数)。

另外,Kryo和FST本来都不需要被序列化都类实现Serializable接口,但我们还是建议每个被序列化类都去实现它,因为这样可以保持和Java序列化以及dubbo序列化的兼容性,另外也使我们未来采用上述某些自动注册机制带来可能。

序列化性能分析与测试

本文我们主要讨论的是序列化,但在做性能分析和测试的时候我们并不单独处理每种序列化方式,而是把它们放到dubbo RPC中加以对比,因为这样更有现实意义。

测试环境

粗略如下:

  • 两台独立服务器
  • 4核Intel(R) Xeon(R) CPU E5-2603 0 @ 1.80GHz
  • 8G内存
  • 虚拟机之间网络通过百兆交换机
  • CentOS 5
  • JDK 7
  • Tomcat 7
  • JVM参数-server -Xms1g -Xmx1g -XX:PermSize=64M -XX:+UseConcMarkSweepGC

当然这个测试环境较有局限,故当前测试结果未必有非常权威的代表性。

测试脚本

和dubbo自身的基准测试保持接近:

10个并发客户端持续不断发出请求:

  • Pass in a nested complex object (but the amount of single data is small), do nothing, and return it as it is
  • Pass in a 50K string, do nothing, and return it as it is (TODO: the result is not yet listed)

Conduct a 5-minute performance test. (Quoting the consideration of dubbo's own testing: "It mainly examines the performance of serialization and network IO, so the server does not have any business logic. Taking 10 concurrency is to consider that the http protocol may have a high CPU usage rate under high concurrency. to the bottleneck.")

Comparison of byte sizes generated by different serialization in Dubbo RPC

The size of the bytecode generated by serialization is a relatively deterministic indicator, which determines the network transmission time and bandwidth usage of remote calls.

The results for complex objects are as follows (smaller values ​​are better):

Serialization implementation request bytes response bytes
Kryo 272 90
FST 288 96
Dubbo Serialization 430 186
Hessian 546 329
FastJson 461 218
Json 657 409
Java Serialization 963 630

technology sharing

Comparison of different serialization response time and throughput in Dubbo RPC

The average response time of the remote call method The average TPS (transactions per second)
REST: Jetty + JSON 7.806 1280
REST: Jetty + JSON + GZIP EVERYTHING EVERYTHING
REST: Jetty + XML EVERYTHING EVERYTHING
REST: Jetty + XML + GZIP EVERYTHING EVERYTHING
REST: Tomcat + JSON 2.082 4796
REST: Netty + JSON 2.182 4576
Dubbo: FST 1.211 8244
Dubbo: kyro 1.182 8444
Dubbo: dubbo serialization 1.43 6982
Dubbo: hessian2 1.49 6701
Dubbo: fastjson 1.572 6352

technology sharing

technology sharing

Test summary

As far as the current results are concerned, we can see that Kryo and FST have a very significant improvement compared to the original serialization method in Dubbo RPC, regardless of the size of the generated bytes, the average response time and the average TPS.

future

In the future, when Kryo or FST are mature enough in dubbo, we are likely to change the default serialization of dubbo RPC from hessian2 to one of them.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326711066&siteId=291194637