dubbo 2.5.10 版本,netty仍然使用的是netty的3.10.5版本,我们从下面的代码可以看出,SPI默认使用的是“netty”,而不是“netty4”。

package com.alibaba.dubbo.remoting;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI; import javax.sound.midi.Receiver; /**
* Transporter. (SPI, Singleton, ThreadSafe)
* <p>
* <a href="http://en.wikipedia.org/wiki/Transport_Layer">Transport Layer</a>
* <a href="http://en.wikipedia.org/wiki/Client%E2%80%93server_model">Client/Server</a>
*
* @see com.alibaba.dubbo.remoting.Transporters
*/
@SPI("netty")
public interface Transporter {

不管是NettyClient,还是NettyServer创建Channel的工厂类ChannelFactory地创建方式都是一样的,代码如下:

// ChannelFactory's closure has a DirectMemory leak, using static to avoid
// https://issues.jboss.org/browse/NETTY-424
private static final ChannelFactory channelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(new NamedThreadFactory("NettyClientBoss", true)),
Executors.newCachedThreadPool(new NamedThreadFactory("NettyClientWorker", true)),
Constants.DEFAULT_IO_THREADS);

顾名思义,ChannelFactory类是用来创建Channel的,那Channel是用来做什么的呢?一句话概括就是:所有与I/O相关的操作都是由Channel来实现的。从上面的代码可以看出Dubbo创建了2个I/O线程池,分别为Boss线程池和Workder线程池,这2个线程池都是初始化为“无边界”的cached线程池,也就是说刚开始都是“来者不拒”,但实际上Boss线程默认最大只允许1个线程,而Work线程池最大为Constants.DEFAULT_IO_THREADS指定的线程数,即:CPU核数+1与32二者取最小值。Boss线程负责进行处理所有的连接请求,连接请求处理完成后把后续任务的处理转交给Work线程来处理。代码如下:

private static final int DEFAULT_BOSS_COUNT = 1;
public NioClientSocketChannelFactory(
Executor bossExecutor, Executor workerExecutor, int workerCount) {
this(bossExecutor, workerExecutor, DEFAULT_BOSS_COUNT, workerCount);
}
public static final int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32);

从引导类的相关代码可以看出Client与Server是建立的TCP长连接(keepAlive=true),连接超时时间是从请求的Url中读取。启动TCP_NODELAY,就意味着禁用了Nagle算法,允许小包的发送。对于延时敏感型,同时数据传输量比较小的应用,开启TCP_NODELAY选项无疑是一个正确的选择。

bootstrap = new ClientBootstrap(channelFactory);
// config
// @see org.jboss.netty.channel.socket.SocketChannelConfig
bootstrap.setOption("keepAlive", true);
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("connectTimeoutMillis", getTimeout());

每个Channel都会在同一个线程内默认创建一个对应的ChannelPipeline,可以在ChannelPipeline中注册一个或多个ChannelHandler(用来处理相关事件的回调)。从下面的代码可以看出,在ChannelPipleline中注册了编码处理器、解码处理器以及自定义的nettyHanlder。

final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});

编码主要使用了TelnetCodec 与 TransportCodec,TelnetCodec用于字符串的编码,TransportCodec用于对其它对象进行编码。

TelnetCodec(字符串编码):

    public void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException {
if (message instanceof String) {
if (isClientSide(channel)) {
message = message + "\r\n";
}
byte[] msgData = ((String) message).getBytes(getCharset(channel).name());
buffer.writeBytes(msgData);
} else {
super.encode(channel, buffer, message);
}
}

TransportCodec(对象编码):

    public void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException {
OutputStream output = new ChannelBufferOutputStream(buffer);
ObjectOutput objectOutput = getSerialization(channel).serialize(channel.getUrl(), output);
encodeData(channel, objectOutput, message);
objectOutput.flushBuffer();

引导类和ChannelPiple创建好之后就可以进行执行connect操作了

boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS);

上面这行代码用来阻塞当前线程直到connect完成或者大于设定的超时时间。如果在设定的超时时间前完成connect,则通过future来判断连接是否成功。连接成功则把当前使用的Channel的写功能挂起。如果连接不成功则抛出相关的异常。具体代码如下:

  protected void doConnect() throws Throwable {
long start = System.currentTimeMillis();
ChannelFuture future = bootstrap.connect(getConnectAddress());
try {
boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS); if (ret && future.isSuccess()) {
Channel newChannel = future.getChannel();
newChannel.setInterestOps(Channel.OP_READ_WRITE);
try {
// Close old channel
Channel oldChannel = NettyClient.this.channel; // copy reference
if (oldChannel != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close old netty channel " + oldChannel + " on create new netty channel " + newChannel);
}
oldChannel.close();
} finally {
NettyChannel.removeChannelIfDisconnected(oldChannel);
}
}
} finally {
if (NettyClient.this.isClosed()) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close new netty channel " + newChannel + ", because the client closed.");
}
newChannel.close();
} finally {
NettyClient.this.channel = null;
NettyChannel.removeChannelIfDisconnected(newChannel);
}
} else {
NettyClient.this.channel = newChannel;
}
}
} else if (future.getCause() != null) {
throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+ getRemoteAddress() + ", error message is:" + future.getCause().getMessage(), future.getCause());
} else {
throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+ getRemoteAddress() + " client-side timeout "
+ getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client "
+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion());
}
} finally {
if (!isConnected()) {
future.cancel();
}
}
}

连接成功,通过future获取到的当前使用的Channel会通过getChannel()方法来把此Channel放到一个静态的channelMap中去,代码如下:

@Override
protected com.alibaba.dubbo.remoting.Channel getChannel() {
Channel c = channel;
if (c == null || !c.isActive())
return null;
return NettyChannel.getOrAddChannel(c, getUrl(), this);
} private static final ConcurrentMap<org.jboss.netty.channel.Channel, NettyChannel> channelMap = new ConcurrentHashMap<org.jboss.netty.channel.Channel, NettyChannel>();
static NettyChannel getOrAddChannel(Channel ch, URL url, ChannelHandler handler) {
if (ch == null) {
return null;
}
NettyChannel ret = channelMap.get(ch);
if (ret == null) {
NettyChannel nettyChannel = new NettyChannel(ch, url, handler);
if (ch.isActive()) {
ret = channelMap.putIfAbsent(ch, nettyChannel);
}
if (ret == null) {
ret = nettyChannel;
}
}
return ret;
}

如果Client断开了与Server的连接,则删除channelMap中的于当前连接有关的Channel

    @Override
protected void doDisConnect() throws Throwable {
try {
NettyChannel.removeChannelIfDisconnected(channel);
} catch (Throwable t) {
logger.warn(t.getMessage());
}
}

总结:

  1. Dubbo底层的网络通讯默认采用的是Netty框架;
  2. Netty Client采用2级I/O线程池,分别为:Boss线程池(默认最大只允许1个线程)、Worker线程池(默认最大只允许CPU核心数+1或32,二者取其小);Boss负责处理连接请求,后续任务由Workder来处理;
  3. Netty Client 与 Netty Server 之间建立的是TCP长连接;
  4. 由引导类(bootStrap)来注册ChannelPipeline与建立连接(connect),在ChannelPipeline中需要注册项目编码、解码等ChannelHanlder以处理相关事件的回调。

Dubbo 选择 Transporter 的流程图:

buddo源码分析-transport组件之Netty(一)的更多相关文章

  1. Netty源码分析第1章(Netty启动流程)---->第1节: 服务端初始化

    Netty源码分析第一章:  Server启动流程 概述: 本章主要讲解server启动的关键步骤, 读者只需要了解server启动的大概逻辑, 知道关键的步骤在哪个类执行即可, 并不需要了解每一步的 ...

  2. Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建

    Netty源码分析第一章:  Server启动流程 第二节:NioServerSocketChannel的创建 我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通 ...

  3. Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化

    Netty源码分析第一章:Netty启动流程   第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...

  4. Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用

    Netty源码分析第一章:Netty启动流程   第四节:注册多路复用 回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继 ...

  5. Netty源码分析第1章(Netty启动流程)---->第5节: 绑定端口

    Netty源码分析第一章:Netty启动步骤 第五节:绑定端口 上一小节我们学习了channel注册在selector的步骤, 仅仅做了注册但并没有监听事件, 事件是如何监听的呢? 我们继续跟第一小节 ...

  6. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. python基础之对象之间的交互

    面对对象编程之对象之间的交互 这是一个猫狗大战的例子 # 猫类 class Cat: def __init__(self, name, hp, attack): self.name = name # ...

  2. day 33

    目录 数据库是什么 为什么使用数据库 数据库的分类 关系型(把数据保存在硬盘里) 非关系型(把数据保存在内存里) mysql的架构 初识mysql 操作数据库 增 删 改 查 数据库是什么 数据库即存 ...

  3. 【Linux】Linux下安装RabbitMQ服务

    一.安装步骤 1)安装erlang yum install -y erlang2)安装rabbitmq-serveryum install -y rabbitmq-server3)开启rabbitmq ...

  4. Java开发分析工具JProfiler的详细使用方法解析

    JProfiler 11 是一款功能强大的Java代码分析工具,JProfiler的直观UI可帮助您解决性能瓶颈,确定内存泄漏并了解线程问题且JProfiler 11 Mac破解版配置会话非常简单,第 ...

  5. redis windows下载地址

    https://github.com/MicrosoftArchive/redis/tags

  6. vue.js集成codeMirror代码编辑器

    1.前端代码 <link href="https://cdn.bootcss.com/codemirror/5.48.4/codemirror.css" rel=" ...

  7. Locust性能测试6-命令行参数详解

    前言 当我们在linux上使用locust工具压测的时候,会使用no-web模式,然后需要收集运行的日志,方便查找问题. 命令行参数 输入locust --help 查看所有的命令行参数 > l ...

  8. SpringBoot——SpringBoot学习记录【一】

    前言 公司目前主要的业务,用的语言是java,所以学习下相关的技术呀,还好大学基础语言学的JAVA SpringBoot简介 官网 SpringBoot 简介 SpringBoot是用来简化Sprin ...

  9. static在Swift 中表示 “类型范围作用域”

    In Swift, however, type properties are written as part of the type’s definition, within the type’s o ...

  10. int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex)

    mutex:为了保护条件变量而存在的: cond:为了线程通信而存在的. 整个机制都是为了保护条件变量和线程间通信而存在的. pthread_cond_wait()函数一进入wait状态就会自动rel ...