详细讲讲netty的pipiline!
前言
提到 Netty 首当其冲被提起的肯定是支持它承受高并发的线程模型,说到线程模型就不得不提到 NioEventLoopGroup 这个线程池,接下来进入正题。
线程模型
首先来看一段 Netty 的使用示例
package com.coding.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public final class SimpleServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new SimpleServerHandler())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
}
});
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
}
}
}
下面将分析第一、二行代码,看下 NioEventLoopGroup 类的构造函数干了些什么。其余的部分将在其他博文中分析。
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
从代码中可以看到这里使用了两个线程池 bossGroup 和 workerGroup,那么为什么需要定义两个线程池呢?这就要说到 Netty 的线程模型了。

Netty 的线程模型被称为 Reactor 模型,具体如图所示,图上的 mainReactor 指的就是 bossGroup,这个线程池处理客户端的连接请求,并将 accept 的连接注册到 subReactor 的其中一个线程上;图上的 subReactor 当然指的就是 workerGroup,负责处理已建立的客户端通道上的数据读写;图上还有一块 ThreadPool 是具体的处理业务逻辑的线程池,一般情况下可以复用 subReactor,比我的项目中就是这种用法,但官方建议处理一些较为耗时的业务时还是要使用单独的 ThreadPool。
NioEventLoopGroup 构造函数
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, null);
}
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
this(nThreads, threadFactory, SelectorProvider.provider());
}
public NioEventLoopGroup(
int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
super(nThreads, threadFactory, selectorProvider);
}
NioEventLoopGroup 类中的构造函数最终都是调用的父类 MultithreadEventLoopGroup 如下的构造函数:
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
从上面的构造函数可以得到 如果使用EventLoopGroup workerGroup = new NioEventLoopGroup()来创建对象,即不指定线程个数,则 netty 给我们使用默认的线程个数,如果指定则用我们指定的线程个数。
默认线程个数相关的代码如下:
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
而 SystemPropertyUtil.getInt 函数的功能为:得到系统属性中指定 key(这里:key =”io.netty.eventLoopThreads”)所对应的 value,如果获取不到获取失败则返回默认值,这里的默认值为:cpu 的核数的2倍。
结论:如果没有设置程序启动参数(或者说没有指定 key=”io.netty.eventLoopThreads”的属性值),那么默认情况下线程的个数为 cpu 的核数乘以 2。
继续看,由于 MultithreadEventLoopGroup 的构造函数是调用的是其父类 MultithreadEventExecutorGroup 的构造函数,因此,看下此类的构造函数
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (threadFactory == null) {
threadFactory = newDefaultThreadFactory();
}
children = new SingleThreadEventExecutor[nThreads];
//根据线程个数是否为2的幂次方,采用不同策略初始化chooser
if (isPowerOfTwo(children.length)) {
chooser = new PowerOfTwoEventExecutorChooser();
} else {
chooser = new GenericEventExecutorChooser();
}
//产生nTreads个NioEventLoop对象保存在children数组中
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(threadFactory, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
//如果newChild方法执行失败,则对前面执行new成功的几个NioEventLoop进行shutdown处理
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
}
该构造函数干了如下三件事:
- 产生了一个线程工场:threadFactory = newDefaultThreadFactory();
MultithreadEventExecutorGroup类
protected ThreadFactory newDefaultThreadFactory() {
return new DefaultThreadFactory(getClass());//getClass()为:NioEventLoopGroup.class
}
DefaultThreadFactory类
public DefaultThreadFactory(Class<?> poolType) {
this(poolType, false, Thread.NORM_PRIORITY);
}
- 根据线程个数是否为 2 的幂次方,采用不同策略初始化 chooser
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
- 产生 nTreads 个 NioEventLoop 对象保存在 children 数组中 ,线程都是通过调用 newChild 方法来产生的。
@Override
protected EventExecutor newChild(
ThreadFactory threadFactory, Object... args) throws Exception {
return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]);
}
这里传给 NioEventLoop 构造函数的参数为:NioEventLoopGroup、DefaultThreadFactory、SelectorProvider。
NioEventLoop 构造函数分析
既然上面提到来 new 一个 NioEventLoop 对象,下面我们就看下这个类以及其父类。
NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) {
super(parent, threadFactory, false);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
provider = selectorProvider;
selector = openSelector();
}
继续看父类 SingleThreadEventLoop 的构造函数
protected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
super(parent, threadFactory, addTaskWakesUp);
}
又是直接调用来父类 SingleThreadEventExecutor 的构造函数,继续看
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.parent = parent;
this.addTaskWakesUp = addTaskWakesUp;//false
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
boolean success = false;
updateLastExecutionTime();
try {
//调用NioEventLoop类的run方法
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = STATE_UPDATER.get(SingleThreadEventExecutor.this);
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
// Check if confirmShutdown() was called at the end of the loop.
if (success && gracefulShutdownStartTime == 0) {
logger.error(
"Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
"before run() implementation terminates.");
}
try {
// Run all remaining tasks and shutdown hooks.
for (;;) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
cleanup();
} finally {
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.release();
if (!taskQueue.isEmpty()) {
logger.warn(
"An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
}
terminationFuture.setSuccess(null);
}
}
}
}
});
taskQueue = newTaskQueue();
}
protected Queue<Runnable> newTaskQueue() {
return new LinkedBlockingQueue<Runnable>();
}
主要干如下两件事:
利用 ThreadFactory 创建来一个 Thread,传入了一个 Runnable 对象,该 Runnable 重写的 run 代码比较长,不过重点仅仅是调用 NioEventLoop 类的 run 方法。
使用 LinkedBlockingQueue 类初始化 taskQueue 。
其中newThread 方法的代码如下:
DefaultThreadFactory类
@Override
public Thread newThread(Runnable r) {
Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet());
try {
//判断是否是守护线程,并进行设置
if (t.isDaemon()) {
if (!daemon) {
t.setDaemon(false);
}
} else {
if (daemon) {
t.setDaemon(true);
}
}
//设置其优先级
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
// Doesn't matter even if failed to set.
}
return t;
}
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(r, name);
}
FastThreadLocalThread类
public FastThreadLocalThread(Runnable target, String name) {
super(target, name);// FastThreadLocalThread extends Thread
}
到这里,可以看到底层还是借助于类似于Thread thread = new Thread(r)这种方式来创建线程。
关于NioEventLoop对象可以得到的点有,初始化了如下4个属性。
NioEventLoopGroup (在父类SingleThreadEventExecutor中)
selector
provider
thread (在父类SingleThreadEventExecutor中)
结束
识别下方二维码!回复:
入群,扫码加入我们交流群!

详细讲讲netty的pipiline!的更多相关文章
- 详细图解 Netty Reactor 启动全流程 | 万字长文 | 多图预警
本系列Netty源码解析文章基于 4.1.56.Final版本 大家第一眼看到这幅流程图,是不是脑瓜子嗡嗡的呢? 大家先不要惊慌,问题不大,本文笔者的目的就是要让大家清晰的理解这幅流程图,从而深刻的理 ...
- 【转】Netty那点事(二)Netty中的buffer
[原文]https://github.com/code4craft/netty-learning/blob/master/posts/ch2-buffer.md 上一篇文章我们概要介绍了Netty的原 ...
- Netty那点事: 概述, Netty中的buffer, Channel与Pipeline
Netty那点事(一)概述 Netty和Mina是Java世界非常知名的通讯框架.它们都出自同一个作者,Mina诞生略早,属于Apache基金会,而Netty开始在Jboss名下,后来出来自立门户ne ...
- 性能追击:万字长文30+图揭秘8大主流服务器程序线程模型 | Node.js,Apache,Nginx,Netty,Redis,Tomcat,MySQL,Zuul
本文为<高性能网络编程游记>的第六篇"性能追击:万字长文30+图揭秘8大主流服务器程序线程模型". 最近拍的照片比较少,不知道配什么图好,于是自己画了一个,凑合着用,让 ...
- netty系列之:netty中的懒人编码解码器
目录 简介 netty中的内置编码器 使用codec要注意的问题 netty内置的基本codec base64 bytes compression json marshalling protobuf ...
- Netty 系列之 Netty 高性能之道
1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用 Netty4 + Thrift 压缩二进制编解码技术,他们实现了 10 W TPS(1 K 的复杂 POJO 对象)的跨 ...
- Netty系列之Netty高性能之道
转载自http://www.infoq.com/cn/articles/netty-high-performance 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Ne ...
- WebViewJavascriptBridge详细使用(转载)
WebViewJavascriptBridge是支持到iOS6之前的版本的,用于支持native的iOS与javascript交互.如果需要支持到iOS6之前的app,使用它是很不错的.本篇讲讲Web ...
- Netty高性能之道
1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用.相比于 ...
随机推荐
- SpringMVC(5)数据绑定-2
在SpringMVC(4)数据绑定-1中我们介绍了如何用@RequestParam来绑定数据,下面我们来看一下其它几个数据绑定注解的使用方法. 1.@PathVariable 用来绑定URL模板变量值 ...
- MySQL 删除devices表中id最大的一行
原想法:delete from devices where id in (select max(id) from devices); 报错:ERROR 1093 (HY000): You can't ...
- bugKu变量1
重点:两个$$,这暗示着第二个$变量的值,作为第一个$的名称. 而且代码审计之后,发现只含有字母. 又暗示在变量中,那么猜想是在全局变量中,而php中全局变量是$GLOBALS.
- WPF下如何使用TTF字体
之前再写代码的时候如果遇到了图标,我都喜欢再资源文件下创建JPG或者PNG来作为图片. 但是随着TTF字体图标的普及,图标类型的的图片越来越多的被放入到TTF中. 这篇也主要是写再WPF下如何使用TT ...
- postgresql 使用游标笔记
游标介绍:游标是一种从表中检索数据并进行操作的灵活手段,游标主要用在服务器上,处理由客户端发送给服务端的sql语句,或是批处理.存储过程.触发器中的数据处理请求. 游标的优点在于它允许应用程序对查询语 ...
- QT从入门到入土(一)——Qt5.14.2安装教程和VS2019环境配置
引言 24岁的某天,承载着周围人的关心,一路南下.天晴心静,听着斑马,不免对未来有些彷徨.但是呢,人生总要走陌生的路,看陌生的风景,所幸可以听着不变的歌,关心自己的人就那么多.就像是对庸常生活的一次越 ...
- P4827「国家集训队」 Crash 的文明世界
「国家集训队」 Crash 的文明世界 提供一种不需要脑子的方法. 其实是看洛谷讨论版看出来的( (但是全网也就这一篇这个方法的题解了) 首先这是一个关于树上路径的问题,我们可以无脑上点分治. 考虑当 ...
- python 获取当前py文件所在的位置 及对应的文件名称
# 导入sys整个模块 import sys # 使用sys模块名作为前缀来访问模块中的成员 print(sys.argv[0]) 当前文件名:12.py 程序运行结果: ============== ...
- 短信链接点击跳转到微信小程序
短信轰炸的时代,之前链接都是跳转到网页的,后来发现粘性不强,再次唤醒用户成本较高,但小程序的订阅功能,再次唤醒成本较低,还便于给用户通知结果.所以现在链接都改跳转到小程序了.废话不多说,现在就看看是如 ...
- Hive——join的使用
Hive--join的使用 hive中常用的join有:inner join.left join .right join .full join.left semi join.cross join.mu ...
