Unveiling Dubbo’s Encoding & Decoding: A Deep Dive into RPC Serialization
This article walks through Dubbo’s RPC encoding and decoding mechanisms, explaining how Netty pipelines add codec handlers, detailing the message header format, exploring Hessian2 serialization, and answering common pitfalls such as class‑path changes and field mismatches.
Background
The author discovered a package‑path change in a shared component during maintenance and, out of curiosity, investigated Dubbo’s encoding and decoding process.
Dubbo RPC Overview
Dubbo is a Java RPC framework that abstracts remote calls so they look like local method invocations. While this simplifies usage, it introduces coupling between providers and consumers through shared interfaces and facades.
Netty Pipeline Integration
Both client and server use Netty. During NettyClient#doOpen and NettyServer#doOpen, codec handlers are added to the pipeline:
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
.addLast("handler", nettyClientHandler);
}
}); bootstrap.group(bossGroup, workerGroup)
.channel(NettyEventLoopFactory.serverSocketChannelClass())
.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
.addLast("handler", nettyServerHandler);
}
});Encoding Flow (Consumer Side)
When a consumer sends a request, the flow is:
Channel → NettyCodecAdapter#getEncoder() → NettyCodecAdapter$InternalEncoder#encode → DubboCountCodec#encode → DubboCodec#encode → ExchangeCodec#encode → ExchangeCodec#encodeRequest The actual serialization object is obtained from Serialization serialization = getSerialization(channel); which defaults to Hessian2.
Decoding Flow (Consumer Side)
Responses are decoded through two stages:
First stage builds a DecodeableRpcResult without fully deserializing the payload.
Second stage, executed in an async thread, invokes DecodeableRpcResult#decode to reconstruct the result object.
if (channel.getUrl().getParameter(DECODE_IN_IO_THREAD_KEY, DEFAULT_DECODE_IN_IO_THREAD)) {
result = new DecodeableRpcResult(channel, res, is, (Invocation) getRequestData(id), proto);
result.decode();
} else {
result = new DecodeableRpcResult(channel, res, new UnsafeByteArrayInputStream(readMessageData(is)), (Invocation) getRequestData(id), proto);
}Message Header Structure
Dubbo uses a fixed‑length header (2 bytes magic, 1 byte flag, 1 byte status, 8 bytes request ID, 4 bytes body length) followed by the serialized body.
Hessian2 Serialization Details
Dubbo’s default serializer is Hessian2. The request data is written by DubboCodec#encodeRequestData:
@Override
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
out.writeUTF(version);
String serviceName = inv.getAttachment(INTERFACE_KEY);
if (serviceName == null) serviceName = inv.getAttachment(PATH_KEY);
out.writeUTF(serviceName);
out.writeUTF(inv.getAttachment(VERSION_KEY));
out.writeUTF(inv.getMethodName());
out.writeUTF(inv.getParameterTypesDesc());
Object[] args = inv.getArguments();
if (args != null) {
for (int i = 0; i < args.length; i++) {
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
}
out.writeAttachments(inv.getObjectAttachments());
}The corresponding deserializer is Hessian2ObjectInput, obtained via CodecSupport.getSerialization(url).deserialize(...).
Common Issues & Answers
Provider changes class path but consumer does not error – Deserialization catches ClassNotFoundException and falls back to the local type.
Why return value is not written in the request – Java method signatures do not include return types as part of the identifier.
Field order mismatch between parent and child classes – In Dubbo 2.7.x this is fixed by reversing the field list during serialization.
Conclusion
The encoding/decoding process in Dubbo is intricate, involving multiple layers of abstraction to hide low‑level details. Understanding the Netty pipeline, message header, and Hessian2 serialization helps developers troubleshoot compatibility issues and extend the framework safely.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
