preface

Today’s topic is serialization. This isn’t the first time I’ve written about serialization. Before I start writing today, I went to my blog to read an early serialization article (see picture below).

Why do you want to talk serialization again? In addition, Dubbo has undergone tremendous changes in recent years, among which Tripple protocol, promoted by Dubbo 3.0, has the momentum to replace Dubbo protocol under the banner of the next generation RPC communication protocol. The Tripple protocol uses the Protobuf serialization scheme.

In addition, the Dubbo community has developed a serialized pressure test project: github.com/apache/dubb… This article will also discuss the various serialization frameworks supported by Dubbo from a performance perspective around this project.

When we talk about serialization, what do we care about?

In recent years, a variety of new efficient serialization methods have emerged, the most typical include:

  • Java language specific: JDK serialization, Kryo, FST
  • Cross-language: Protostuff, ProtoBuf, Thrift, Avro, MsgPack, etc

Why have so many serialization frameworks sprung up in the open source community and Dubbo extended so many serialization implementations? It’s mainly to meet different needs.

The selection of serialization framework mainly includes the following aspects:

  1. Across languages. Whether it can only be used for inter-Java serialization/deserialization, whether it is cross-language, cross-platform.
  2. Performance. It is divided into space cost and time cost. Serialized data is generally used for storage or network transmission, and its size is a very important parameter. Parsing time also affects the choice of serialization protocol, as today’s systems strive for extreme performance.
  3. Compatibility. System upgrade is inevitable, and the deserialization protocol should also consider whether the change of an entity’s attribute will lead to deserialization exceptions.

Similar to CAP theory, there are few serialization frameworks on the market that can do all three things at the same time. For example, Hessian2, which is used by Dubbo as the default serialization implementation, has excellent compatibility and good performance, but it is not as good as Kryo and FST in terms of performance. At the cross-language level, it’s not nearly as good as ProtoBuf, JSON.

On the other hand, if there were a serialization scheme that was cross-language, had high performance, and had good compatibility, wouldn’t that have been the standard in the distributed world? The other frames would have been exhausted.

Most of the time, we pick and choose our focus points and find frameworks that fit our needs, which is why serialization frameworks flourish.

The performance test

A lot of serialization frameworks claim to be “high performance”, but I prefer to stick to benchmark Everything’s motto, which gives me a better understanding of each technology and helps me avoid being misinformed by less authoritative blog posts.

How do you do performance testing? Like this?

long start = System.currentTimeMillis();
measure();
System.out.println(System.currentTimeMillis()-start);
Copy the code

It’s not very impressive, but there’s nothing wrong with it. If you think so, I recommend that you take a look at the JMH benchmark framework. My previous article “JAVA Gap-JMH and 8 Testing Pitfalls” recommends you read the following.

In fact, the Dubbo community contributors have already built a fairly complete Dubbo serialization infrastructure: github.com/apache/dubb…

With a basic knowledge of JMH and Dubbo, you can test the performance of various serialization frameworks in Dubbo scenarios.

I have prepared a report of my tests here for your reference. If you are going to test on your own, it is not recommended to benchmark on personal Windows/MAC, the results may be inaccurate. I used two ECS of Aliyun cloud for testing. The test environment: Aliyun Linux, 4C8G, and the startup script:

java -server -Xmx2g -Xms2g -XX:MaxDirectMemorySize=1g -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/home/admin/
Copy the code

Why choose this configuration? I happen to have two of these resources, no special Settings, and judging from the startup script, the pressure test program does not take too many resources, I did not use up.

Test engineering Introduction:

public interface UserService {
    public boolean existUser(String email);

    public boolean createUser(User user);

    public User getUser(long id);

    public Page<User> listUser(int pageNo);
}
Copy the code

A UserService interface for CRUD operations in business applications. The server provides this service with different serialization schemes, and the client uses JMH to perform multiple rounds of pressure measurement.

@Benchmark
    @BenchmarkMode({Mode.Throughput })
    @OutputTimeUnit(TimeUnit.SECONDS)
    @Override
    public boolean existUser(a) throws Exception {
      // ...
    }

    @Benchmark
    @BenchmarkMode({Mode.Throughput})
    @OutputTimeUnit(TimeUnit.SECONDS)
    @Override
    public boolean createUser(a) throws Exception {
      // ...
    }

    @Benchmark
    @BenchmarkMode({Mode.Throughput})
    @OutputTimeUnit(TimeUnit.SECONDS)
    @Override
    public User getUser(a) throws Exception {
      // ...
    }

    @Benchmark
    @BenchmarkMode({Mode.Throughput})
    @OutputTimeUnit(TimeUnit.SECONDS)
    @Override
    public Page<User> listUser(a) throws Exception {
      // ...
    }
Copy the code

The overall benchmark framework structure as above, detailed implementation, you can refer to the source code. Here I choose only Throughput, which is Throughput.

Omit a series of pressure measurement process, and directly give the results:

Kryo

Benchmark Mode Cnt Score Error Units Client.createUser THRPT 3 20913.339 ± 3948.207 OPS /s client.existUser THRPT 3 31669.871 ± 1582.723 OPS/Client. THRPT 3 29706.647 ± 3278.029 OPS/Client. THRPT 3 17234.979 ± 1818.964 ops/sCopy the code

Fst

Benchmark Mode Cnt Score Error Units Client.createUser THRPT 3 15438.865 ± 4396.911 OPS /s client.existUser THRPT 3 25197.331 ± 12116.109 OPS/client. THRPT 3 21723.626 ± 7441.582 OPS/client. THRPT 3 15768.321 ± 11684.183 ops/sCopy the code

Hessian2

Benchmark Mode Cnt Score Error Units Client.createUser THRPT 3 22948.875 ± 2005.721 OPS /s client.existUser THRPT 3 34735.122 ± 1477.339 OPS/client.getuser THRPT 3 20679.921 ± 999.129 OPS/client.listuser THRPT 3 3590.129 ± 673.889 ops/sCopy the code

FastJson

Benchmark Mode Cnt Score Error Units Client.createUser THRPT 3 26269.487 ± 1667.895 OPS /s client.existUser THRPT 3 THRPT 3 25204.239 ± 4326.485 OPS /s client.listuser THRPT 3 9823.574 ± 2087.110  ops/sCopy the code

Tripple

Benchmark Mode Cnt Score Error Units Client.createUser THRPT 3 19721.871 ± 5121.444 OPS /s client.existUser THRPT 3 35350.031 ± 20801.169 OPS/Client. THRPT 3 20841.078 ± 8583.225 OPS/Client. THRPT 3 4655.687 ± 207.503 OPS/Client  ops/sCopy the code

How do I see this test result? The results of createUser, existUser, and getUser tests are mixed, and it is not entirely clear which framework is the best. My guess is that the amount of serialized data is relatively simple and not large, just a simple User object. The listUser implementation returns a larger List

. Kryo and Fst serialization do perform well in the first tier. To my surprise, FastJson is even better than Hessian, in the second tier; Tripple (with ProtoBuf behind it) and Hessian2 rank third.

Of course, such conclusions are limited by Benchmark’s model, and the CRUD simulated in the test case may not be entirely close to the business scenario, since the business is complex.

Well, did this result also meet your expectations?

Dubbo serializes two or three things

Finally, a few things you may or may not know about serialization.

hession-lite

The Hessian2 Dubbo uses is not actually a native Hessian2 scheme. Note the dependencies in the source code:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>hessian-lite</artifactId>
</dependency>
Copy the code

Hessian-lite was originally created as an open source project by Ali, and later made its way into Apache with Dubbo’s contribution to the project. Github address: github.com/apache/dubb… Hessian2, Dubbo, a separate repository dedicated to the RPC scenario, to achieve higher performance and meet some of the needs of customization.

Serialization in the IO thread

In older versions of the Dubbo client, serialization is done in the business thread by default, not the IO thread, and you can control which thread the serialization is bound to via decode.in. IO

    <dubbo:reference id="userService" check="false"
                     interface="org.apache.dubbo.benchmark.service.UserService"
                     url="dubbo://${server.host}:${server.port}">
        <dubbo:parameter key="decode.in.io" value="true" />
    </dubbo:reference>
Copy the code

At Benchmark, I found that serialization performed better in IO threads, probably due to the fact that serialization itself is a CPU-intensive operation and that multithreading can’t speed up and leads to more contention.

SerializationOptimizer

Some serialization implementations, such as Kryo and Fst, can be accelerated by showing registered serialized classes. If you want to take advantage of this feature to improve serialization performance, Can realize org.apache.dubbo.com mon. Serialize. Support. SerializationOptimizer interface. An example:

public class SerializationOptimizerImpl implements SerializationOptimizer {
    @Override
    publicCollection<Class<? >> getSerializableClasses() {returnArrays.asList(User.class, Page.class, UserService.class); }}Copy the code

Most people would probably find this a hassle, and few users would probably use it. Note that this optimization needs to be enabled on both the client and the server.

Don’t forget to specify this optimizer in the Protocol configuration:

<dubbo:protocol name="dubbo" host="${server.host}" server="netty4" port="${server.port}" serialization="kryo" optimizer="org.apache.dubbo.benchmark.serialize.SerializationOptimizerImpl"/>
Copy the code

The serialization method is specified by the server

In general, the protocol (Dubbo by default) and serialization method (hessian2 by default) used by the Dubbo framework are specified by the server and do not need to be specified by the consumer. Because the server is the provider of the service, it has the right to define the service. When the consumer subscribes to the service and receives the notification of the service address, the service address will contain the realization of serialization. In this way, Dubbo realizes the cooperative communication between the consumer and provider.

In most business applications, the application may be both the provider of service A and the consumer of service B. Therefore, it is recommended that the architecture decision maker negotiate A unified agreement. If there are no special requirements, keep the default values.

However, if the application is only a consumer and wants to specify a serialization protocol or optimizer (for some special scenarios), note that configuring Protolcol does not work because the protocol configuration process is not triggered without a service provider. The consumer configuration can be specified as follows:

<dubbo:reference id="userService" check="false"
                 interface="org.apache.dubbo.benchmark.service.UserService"
                 url="dubbo://${server.host}:${server.port}? optimizer=org.apache.dubbo.benchmark.serialize.SerializationOptimizerImpl&amp;serialization=kryo">
    <dubbo:parameter key="decode.in.io" value="true" />
</dubbo:reference>
Copy the code

& Stands for & to avoid escape problems in XML

conclusion

With the implementation of each serialization framework in Dubbo, this article discusses our concerns when choosing a serialization framework, discusses the performance of each serialization implementation in Dubbo, and gives a detailed test report. At the same time, it also gives some serialization tips. If you change the default serialization behavior in Dubbo, You may want to pay attention to these details.

Finally, the Tripple protocol supported by Dubbo3 is used to talk about the trend of technology development. We know that JSON can replace XML as a technology that many front and back end developers are familiar with, not because of its performance, but because it solves everyone’s problem just right. If a technology is popular, it must be because it helps users to solve their pain points. As for the problems solved, they are different in each historical development stage. In the past, Dubbo2.x helped users solve a series of problems by relying on its rich expansion ability, powerful performance, active community and other advantages, and also gained a lot of users. At present, the application level service discovery, unified governance rules and Tripple protocol proposed by Dubbo3.x are also trying to solve the difficult problems in the cloud native era, such as multi-language, adaptation of cloud native infrastructure, etc., to catch up with The Times and help users.

This article is published by OpenWrite!