网易云课堂-微专业Java
Netty学习笔记(一)
Netty线程模型
Java NIO虽然为开发者提供了功能强大的IO处理API,但是实际运用中去使用这样的API是比较繁琐的,而且如果抛开多线程技术,程序则无法达到高性能的指标。
Reactor模型将NIO和多线程结合,实现程序高性能。
- 实现Reactor模型
private ServerSocketChannel serverSocketChannel;
// 1、创建多个线程 - accept处理reactor线程 (accept线程)
private ReactorThread[] mainReactorThreads = new ReactorThread[1];
// 2、创建多个线程 - io处理reactor线程 (I/O线程)
private ReactorThread[] subReactorThreads = new ReactorThread[8];
在开源社区中,有很多对JDK NIO进行了封装的十分优秀的网络编程框架,其中应用较高的就有Netty。为了让NIO处理更好的利用多线程特性,Netty实现了Reactor线程模型。
- Netty结构图
可以看出Netty主要包括三个部分:左上方红色区域表示支持Socket等多种传输方式;右上方表示提供了多种协议编解码实现;下方表示核心设计,主要包括事件处理模型、API使用、ByteBuffer增强等。
- Netty实现Reactor线程模型与上代码直接使用线程不同的是其是采用事件轮询机制。
// 创建EventLoopGroup accept线程组 NioEventLoop
EventLoopGroup mainGroup = new NioEventLoopGroup(1);
// 创建EventLoopGroup I/O线程组
EventLoopGroup subGroup2 = new NioEventLoopGroup(1);
// 服务端启动引导工具类
ServerBootstrap b = new ServerBootstrap();
// 配置服务端处理的reactor线程组以及服务端的其他配置
b.group(mainGroup, subGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new EchoServerHandler());
}
});
ServerBootstrap是Netty服务服务启动类。
group中的parent Group负责处理accept请求,child Group则负责处理具体的业务实现。
channel用于创建具体的通道实例,传入NIOServerChannel。
handler是用来处理服务端通道请求的。
childHandler是用来处理具体socket连接后的请求。
pipeline是责任链(后续学习)。
- Reactor线程实现:多个请求同时达到服务端,EventLoop开始事件轮询,然后通过Dispatcher分发给各个时间处理器去处理具体的业务实现。
上图中采用了两组不同的EventLoop处理不同通道的不同事件。Main EventLoopGroup主要轮询accept事件,然后将不同请求分发至SubEventLoopGroup中进行具体的I/O处理。
浅看new NioEventLoopGroup源码
- 创建NioEventLoopGroup的时候,默认线程数是cpu数量*2,也可以显式指定线程数。
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
/**
* @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
*/
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
//默认是CPU数*2
}
- 当调用excutor方法提交任务时,则会判断是否启动,未启动时则调用内置的excutor创建新线程来触发run方法执行
-
创建多个NioEventLoop,形成NioEventLoopGroup
-
newChild是NioEventLoop的实现
NioEventLoop具体完成哪些事情呢? -
将通道注册到EventLoop的selector上,进行事件轮轮询
-
一步一步查看NioEventLoop父类,发现其最终继承Exector类。在SingleThreadEventExecute类中实现execute方法。
-
执行run方法
-
查看NioEventLoop中的run方法实现,其执行网络处理(selector)
-
点击查看processSelectedKeys(),一步一步查看,查看到根据某个事件类型进行操作的代码
– 注意ServerSocketChannel用的是NioMessageUnsafe,SocketChannel用的是NioByteUnsafe
–EventLoop的启动
EventLoop自身实现了Excutor接口,当调用excutor方法提交任务时,则会判断是否启动,未启动时则调用内置的excutor创建新线程来触发run方法执行。
服务端启动
// 通过bind启动服务
ChannelFuture f = b.bind(PORT).sync();
// 阻塞主线程,知道网络服务被关闭
f.channel().closeFuture().sync();
bind是创建通道,绑定端口。
浅看bind源码
- 初始化(selector在之前创建的eventLoop中)
- 查看register方法
- 找到相应的register方法。(当子类找不到合适的方法时,就去父类中找)
- 在AbstractChannel.java中unsafe()返回一个unsafe对象
- 在构造函数中会将一个Unsafe对象赋值给unsafe变量
- AbstractNioMessageChannel.java中newUnsafe()中返回一个NioMessageUnsafe
- Unsafe().register()调用的是AbstractChannel中的register方法
- register0()中调用doRegister()方法
- doRegister方法将通道注册到selector中
- 绑定端口
- bind0方法
- bind具体实现
至此,netty服务端服务就启动完成。
上一篇: dubbo中是如何使用netty的
下一篇: Redis 持久化介绍