前言
前面我们分析的dubbo的服务引用,和调用,但是没有更深入底层数据传输和编码,没有深入到网络传输当中去,所以这一篇我们主要分析服务调用的数据传输层。源码分析
分析DubboInvoker#invoke
步骤分析- 轮询选择一个ExchangeClient
- 通过ExchangeClient 发送请求 返回一个异步结果,如果是单向的返回一个默认的已经成功了的结果
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(PATH_KEY, getUrl().getPath());
inv.setAttachment(VERSION_KEY, version);
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = calculateTimeout(invocation, methodName);
invocation.put(TIMEOUT_KEY, timeout);
//是否是单向=不带返回值
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
return AsyncRpcResult.newDefaultAsyncResult(invocation);
} else {
ExecutorService executor = getCallbackExecutor(getUrl(), inv);
CompletableFuture<AppResponse> appResponseFuture =
currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
// save for 2.6.x compatibility, for example, TraceFilter in Zipkin uses com.alibaba.xxx.FutureAdapter
FutureContext.getContext().setCompatibleFuture(appResponseFuture);
AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
result.setExecutor(executor);
return result;
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
所以 可以看到 **ExchangeClient 是一个非常重要的媒介,它真实将 rpc请求发送出去,它负责着具体的请求编解码和对象序列化。**来我们来分析一下这个 ExchangeClient 的类图,可以看到他继承了远程客户端(Client)和交换Channel(ExchangeChannel)
ExchangeChannel是做什么的?它是**将rpc请求转化为Request对象,并且提供异步返回的包装实现,**他主要干了这两件事。它的底层最终还是调用channel来将请求发送出去
ExchangeClient实现分析
来我们继续分析,既然我们看到了他的类图,那我们现在的DubboInvoker到底使用的ExchangeClient是哪个呢?他是怎么实现的ExchangeChannel呢?我们可以看到他有三个实现。我们可以在这里debug来看他是哪个实现。在这里debug
最终定位到 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getClients 协议这里
Exchangers.connect(url, requestHandler) 我们可以着重看到这个
private ExchangeClient initClient(URL url) {
...
ExchangeClient client;
try {
// connection should be lazy
if (url.getParameter(LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
可以看到又通过dubbo spi 加载** Exchanger** 实现 来 获取最终的**ExchangeClient,**可以看到默认的实现 DEFAULT_EXCHANGER="header",org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger
public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
// url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
return getExchanger(url).connect(url, handler);
}
public static Exchanger getExchanger(URL url) {
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}
public static Exchanger getExchanger(String type) {
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}
最终我们可以看到 ExchangeClient的实现是这个 HeaderExchangeClient,现在我们以他为入口来分析,他是如何发送Request,和如何接收的呢?
HeaderExchangeClient#request分析
可以看到非常简单,最终是通过 **HeaderExchangeChannel发送出去的.**可以看到HeaderExchangeChannel是通过Client将数据发送出去的,它是将rpc请求包装成Request,然后发送出去。
@Override
public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null,
"Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
try {
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
初步总结
这个时候我们暂停一下 我们来初步的总结一下画个图理一下大概得主要类如上所示,但是我觉得还是不够细,还是可以结合我们分析的源码再画一个,上面的还是比较粗浅。
打完手工,可以看到完整的链路如上。但是上面的分析还是不够底层,还没到编解码和具体的网络传输。接下来我们继续分析 Channel.send 具体的网络传输
继续分析 Channel.send
这个Channel怎么构建的?我们debug可以看到这里 org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#connect,构建 **HeaderExchangeClient**的时候 **Transporters.connect **可以看到来构建传输数据的Client(它本身就实现了channel,所以是通过他来发送数据的)客户端Transporter的默认实现是 org.apache.dubbo.remoting.transport.netty.NettyTransporter,通过他来构造org.apache.dubbo.remoting.transport.netty4.NettyClient
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().connect(url, handler);
}
public static Transporter getTransporter() {
return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
学过netty的都知道 有两个点非常重要
1.编解码
2.编解码后的对象,怎么处理
让我们来分析分析 NettyClient的上面两点
可以看到我们能够找到编解码处理类** NettyCodecAdapter和 编解码后的对象处理类NettyClientHandler**
来我们一个一个分析
编解码分析
我们可以看到这个编解码适配器 中的 getCodec(),他最终是通过SPI来加载 指定协议的 编解码实现NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
可以看到这个获取协议,通过协议类型找到指定的编解码器,这里我们可以看下dubbo协议的编解码器 org.apache.dubbo.rpc.protocol.dubbo.DubboCodec
protected static Codec2 getChannelCodec(URL url) {
String codecName = url.getProtocol(); // codec extension name must stay the same with protocol name
if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
} else {
return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
.getExtension(codecName));
}
}
看看org.apache.dubbo.rpc.protocol.dubbo.DubboCodec的类图,可以看到它继承了ExchangeCodec,可以看到这个命名可以看到 ExchangeCodec 肯定是对Request,Response进行编解码,但是他不真正的去做编解码,而是通过DubboCodec去做真实的dubbo协议的编码与解码
果然可以看到这个验证了我们的想法,但是ExchangeCodec它还是做了dubbo协议的基础编码,在下面我们将看到
我们继续跟踪 org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#encodeRequest 编码请求的方法,发现这个就是完整的dubbo协议
protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
//获取指定的序列化协议
Serialization serialization = getSerialization(channel, req);
// header.
byte[] header = new byte[HEADER_LENGTH];
// set magic number. 设置模数
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
if (req.isTwoWay()) {
header[2] |= FLAG_TWOWAY;
}
if (req.isEvent()) {
header[2] |= FLAG_EVENT;
}
// set request id.
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
//1.将rpc请求 通过序列化协议将请求的对象进行序列化成二进制
//2.添加dubbo协议的具体详情信息 例如哪个接口 哪个方法 版本号
if (req.isHeartbeat()) {
// heartbeat request data is always null
bos.write(CodecSupport.getNullBytesOf(serialization));
} else {
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
if (req.isEvent()) {
encodeEventData(channel, out, req.getData());
} else {
encodeRequestData(channel, out, req.getData(), req.getVersion());
}
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
checkPayload(channel, len);
Bytes.int2bytes(len, header, 12);
// write
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
所以真实的dubbo请求协议内容如下
有没有发现请求头当中第3个字节浪费了,没有使用,
当为Response时,这个字节是用来存储 响应状态的
来我们继续看 Response是如何编码的
org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#encodeResponse
这里我就不贴出来直接画出这样的编码协议
终于我们探究到了他的底层编码信息,这个时候来梳理一下核心代码
编解码核心代码梳理
简单层级核心代码更加细致的dubbo协议代码梳理
client 发送请求和响应分析
NettyClientHandler分析可以看到这个客户端是 包装了ChannelHandler最终处理的还是这个包装了的ChannelHandler
final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
通过 NettyClient也是 ChannelHandler的实现类,并且他自己内部有个ChannelHandler,最终调用的是这个传过来了的 ChannelHandler
public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
// you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
// the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler
super(url, wrapChannelHandler(url, handler));
}
但是到底发送请求是哪个呢?处理Server响应是哪个呢?
我们继续debug 可以看到我们发送请求 使用的NettyClient的父类的AbstractClient#send方法 构建的是NettyChannel,通过他包装的Netty的Channel将数据发送出去。
那NettyClient的构造函数 ChannelHandler 这个有什么用呢?我们继续debug
我们可以知道这个外面包装是 DecodeHandler,可以看到最终处理的还是这个HeaderExchangeHandler handler
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
我们来详细分析一下 HeaderExchangeHandler的源码
这个就是接收服务端的响应,根据请求id,将结果塞入到 DefaultFuture当中
@Override
public void received(Channel channel, Object message) throws RemotingException {
final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
if (message instanceof Request) {
// handle request.
Request request = (Request) message;
if (request.isEvent()) {
handlerEvent(channel, request);
} else {
if (request.isTwoWay()) {
handleRequest(exchangeChannel, request);
} else {
handler.received(exchangeChannel, request.getData());
}
}
} else if (message instanceof Response) {
//将结果塞入到 DefaultFuture当中,根据请求id
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
if (isClientSide(channel)) {
Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
logger.error(e.getMessage(), e);
} else {
String echo = handler.telnet(channel, (String) message);
if (echo != null && echo.length() > 0) {
channel.send(echo);
}
}
} else {
handler.received(exchangeChannel, message);
}
}
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {
DefaultFuture.received(channel, response);
}
}
public static void received(Channel channel, Response response) {
received(channel, response, false);
}
public static void received(Channel channel, Response response, boolean timeout) {
try {
//拿到这个请求的 DefaultFuture
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
Timeout t = future.timeoutCheckTask;
if (!timeout) {
// decrease Time
t.cancel();
}
//将response塞入DefaultFuture当中
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response status is " + response.getStatus()
+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()) + ", please check provider side for detailed result.");
}
} finally {
CHANNELS.remove(response.getId());
}
}
还有一个问题 我debug的时候发现 这个handler是包装了多层的 是一个完整的链路
**MultiMessageHandler=>HeartbeatHandler=>AllChannelHandler=>DecodeHandler=HeaderExchangeHandler ,**这个是在哪里包装的呢?
我们来看到这个NettyClient 原来是在这里又包装了一下,我来猜一下他这个是干嘛了,肯定是为了异步处理,当接收到多个服务端的响应时,应该采用多线程的方式来高效的处理响应
public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
// you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
// the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler
super(url, wrapChannelHandler(url, handler));
}
Dispatcher 果然所有的响应都是通过线程池调度来处理,可以看到默认的AllDispatcher(当然还有其他策略 可以自行摸索)是这个,所以所有的响应都先放到队列里,多线程并发的来处理这个响应。最终调到这个 HeaderExchangeHandler
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
protected static ChannelHandlers getInstance() {
return INSTANCE;
}
static void setTestingChannelHandlers(ChannelHandlers instance) {
INSTANCE = instance;
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)));
}
client 发送请求和响应分析核心代码梳理
发送请求核心代码流程响应服务端返回Response
可以看到管道链路非常解耦 ,dubbo有很多地方都是用了这个 Invoker也使用了,这个也是我们可以学习的地方。