`
san_yun
  • 浏览: 2639010 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Netty服务器线程模型概览

 
阅读更多

一切从ServerBootstrap开始

ServerBootstrap 负责初始话netty服务器,并且开始监听端口的socket请求。

bootstrap bootstrap = new ServerBootstrap(
	new NioServerSocketChannelFactory(
		Executors.newCachedThreadPool(),//boss线程池
		Executors.newCachedThreadPool()//worker线程池
	)
);
bootstrap.setPipelineFactory(new HttpChannelPipelineFactory());
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
bootstrap.bind(new InetSocketAddress(httpPort));//端口开始监听

 

ServerBootstrap 用一个ServerSocketChannelFactory 来实例化。ServerSocketChannelFactory 有两种选择,一种是NioServerSocketChannelFactory,一种是OioServerSocketChannelFactory。 前者使用NIO,后则使用普通的阻塞式IO。它们都需要两个线程池实例作为参数来初始化,一个是boss线程池,一个是worker线程池。

ServerBootstrap.bind(int)负责绑定端口,当这个方法执行后,ServerBootstrap就可以接受指定端口上的socket连接了。一个ServerBootstrap可以绑定多个端口。

 

boss线程和worker线程

可 以这么说,ServerBootstrap监听的一个端口对应一个boss线程,它们一一对应。比如你需要netty监听80和443端口,那么就会有两 个boss线程分别负责处理来自两个端口的socket请求。在boss线程接受了socket连接求后,会产生一个channel(一个打开的 socket对应一个打开的channel),并把这个channel交给ServerBootstrap初始化时指定的 ServerSocketChannelFactory来处理,boss线程则继续处理socket的请求。

ServerSocketChannelFactory则会从worker线程池中找出一个worker线程来继续处理这个请求。
如 果是OioServerSocketChannelFactory的话,那个这个channel上所有的socket消息消息,从开始到 channel(socket)关闭,都只由这个特定的worker来处理,也就是说一个打开的socket对应一个指定的worker线程,这个 worker线程在socket没有关闭的情况下,也只能为这个socket处理消息,无法服务器他socket。

如果是NioServerSocketChannelFactory的话则不然,每个worker可以服务不同的socket或者说channel,worker线程和channel不再有一一对应的关系。
显然,NioServerSocketChannelFactory只需要少量活动的worker线程及能很好的处理众多的channel,而OioServerSocketChannelFactory则需要与打开channel等量的worker线程来服务。

线 程是一种资源,所以当netty服务器需要处理长连接的时候,最好选择NioServerSocketChannelFactory,这样可以避免创建大 量的worker线程。在用作http服务器的时候,也最好选择NioServerSocketChannelFactory,因为现代浏览器都会使用 http keepalive功能(可以让浏览器的不同http请求共享一个信道),这也是一种长连接。

worker线程的生命周期(life circle)

当 某个channel有消息到达或者有消息需要写入socket的时候,worker线程就会从线程池中取出一个。在worker线程中,消息会经过设定好 的ChannelPipeline处理。ChannelPipeline就是一堆有顺序的filter,它分为两部分:UpstreamHandler和 DownStreamHandler。本文着重介绍netty的线程模型,所以关于pipeline的内容从简说明。

客户端送入的消息会首先由许多UpstreamHandler依次处理,处理得到的数据送入应用的业务逻辑handler,通常用SimpleChannelUpstreamHandler来实现这一部分。

 

public class SimpleChannelUpstreamHandler{
	public void messageReceived(ChannelHandlerContext c,MessageEvent e) throws Exception{
		//业务逻辑开始掺入
	}
}

 

 

对 于Nio当messageReceived()方法执行后,如果没有产生异常,worker线程就执行完毕了,它会被线程池回收。业务逻辑hanlder 会通过一些方法,把返回的数据交给指定好顺序的DownStreamHandler处理,处理后的数据如果需要,会被写入channel,进而通过绑定的 socket发送给客户端。这个过程是由另外一个线程池中的worker线程来完成的。

对于Oio来说,从始到终,都是由一个指定的worker来处理。

减少worker线程的处理占用时间

worker 线程是由netty内部管理,统一调配的一种资源,所以最好应该尽快的把让worker线程执行完毕,返回给线程池回收利用。worker线程的大部分时 间消耗在在ChannelPipeline的各种handler中,而在这些handler中,一般是负责应用程序业务逻辑掺入的那个handler最占 时间,它通常是排在最后的UpstreamHandler。所以通过把这部分处理内容交给另外一个线程来处理,可以有效的减少worker线程的周期循环 时间。一般有两种方法:

messageReceived()方法中开启一个新的线程来处理业务逻辑

public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception{
	 ...
	 new Thread(...).start();
}

 

在messageReceived()中开启一个新线程来继续处理业务逻辑,而worker线程在执行完messageReceived()就会结束了。更加优雅的方法是另外构造一个线程池来提交业务逻辑处理任务。

利用netty框架自带的ExecutionHandler

基本使用方法:

 public class DatabaseGatewayPipelineFactory implements ChannelPipelineFactory {
 
     private final ExecutionHandler executionHandler;
 
     public DatabaseGatewayPipelineFactory(ExecutionHandler executionHandler) {
         this.executionHandler = executionHandler;
     }
 
     public ChannelPipeline getPipeline() {
         return Channels.pipeline(
                 new DatabaseGatewayProtocolEncoder(),
                 new DatabaseGatewayProtocolDecoder(),
                 executionHandler, // 多个pipeline之间必须共享同一个ExecutionHandler
                 new DatabaseQueryingHandler());//业务逻辑handler,IO密集
     }
 }

 

把 共享的ExecutionHandler实例放在业务逻辑handler之前即可,注意ExecutionHandler一定要在不同的pipeline 之间共享。它的作用是自动从ExecutionHandler自己管理的一个线程池中拿出一个线程来处理排在它后面的业务逻辑handler。而 worker线程在经过ExecutionHandler后就结束了,它会被ChannelFactory的worker线程池所回收。

它的构造方法是ExecutionHandler(Executor executor) ,很显然executor就是ExecutionHandler内部管理的线程池了。netty额外给我们提供了两种线程池:
MemoryAwareThreadPoolExecutor和OrderedMemoryAwareThreadPoolExecutor,它们都在org.jboss.netty.handler.execution 包下。
MemoryAwareThreadPoolExecutor 确保jvm不会因为过多的线程而导致内存溢出错误,OrderedMemoryAwareThreadPoolExecutor是前一个线程池的子类,除 了保证没有内存溢出之外,还可以保证channel event的处理次序。具体可以查看API文档,上面有详细说明。

 

 

 

 

Netty服务端启动步骤:
代码:

// 构造一个服务端Bootstrap实例,并通过构造方法指定一个ChannelFactory实现
// 其中后两个参数分别是BOSS和WORK的线程池
Bootstrap serverBootstrap = new ServerBootstrap(
        new NioServerSocketChannelFactory(
                Executors.newCachedThreadPool(), 
                Executors.newCachedThreadPool()));

// 注册用户自己实现的ChannelPipelineFactory
serverBootstrap.setPipelineFactory(this.pipelineFactory);

// 调用bind等待客户端来连接
((ServerBootstrap) serverBootstrap).bind(socketAddress);



Netty提供NIO与BIO两种模式,我们主要关心NIO的模式:
NIO处理方式:
1.Netty用一个BOSS线程去处理客户端的接入,创建Channel
2.从WORK线程池(WORK线程数量默认为cpu cores的2倍)拿出一个WORK线程交给BOSS创建好的Channel实例(Channel实例持有java网络对象)
3.WORK线程进行数据读入(读到ChannelBuffer)
4.接着触发相应的事件传递给ChannelPipeline进行业务处理(ChannelPipeline中包含一系列用户自定义的ChannelHandler组成的链)

有 一点要注意的是,执行整个ChannelHandler链这个过程是串行的,如果业务逻辑(比如DB操作)比较耗时,会导致WORK线程长时间被占用得不 到释放,最终影响整个服务端的并发处理能力,所以一般我们通过ExecutionHandler线程池来异步处理ChannelHandler调用链,使 得WORK线程经过ExecutionHandler时得到释放。
要解决这个问题增加下面代码即可:

ExecutionHandler executionHandler =
        new ExecutionHandler(
                new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576));
public ChannelPipeline getPipeline() {
        return Channels.pipeline(
                                new DatabaseGatewayProtocolEncoder(),
                                new DatabaseGatewayProtocolDecoder(),
                                executionHandler, // Must be shared
                                new DatabaseQueryingHandler());
}



Netty为ExecutionHandler提供了两种可选的线程池模型:
1) MemoryAwareThreadPoolExecutor
通过对线程池内存的使用控制,可控制Executor中待处理任务的上限(超过上限时,后续进来的任务将被阻塞),并可控制单个Channel待处理任务的上限,防止内存溢出错误;
2) OrderedMemoryAwareThreadPoolExecutor
是 1)的子类。除了MemoryAwareThreadPoolExecutor 的功能之外,它还可以保证同一Channel中处理的事件流的顺序性,这主要是控制事件在异步处理模式下可能出现的错误的事件顺序,但它并不保证同一 Channel中的事件都在一个线程中执行,也没必要保证这个。
我们看下OrderedMemoryAwareThreadPoolExecutor中的注释:

Thread X: --- Channel A (Event A1) --.   .-- Channel B (Event B2) --- Channel B (Event B3) --->
                                                                    \ /
                                                                    X
                                                                    / \
Thread Y: --- Channel B (Event B1) --'   '-- Channel A (Event A2) --- Channel A (Event A3) --->

处理同一个Channel的事件,是串行的方式执行的,但是同一个Channel的多个事件,可能会分布到线程中池中的多个线程去处理,不同的Channel事件可以并发处理,互相并不影响

再来看看MemoryAwareThreadPoolExecutor中的注释
Thread X: --- Channel A (Event 2) --- Channel A (Event 1) --------------------------->
Thread Y: --- Channel A (Event 3) --- Channel B (Event 2) --- Channel B (Event 3) --->
Thread Z: --- Channel B (Event 1) --- Channel B (Event 4) --- Channel A (Event 4) --->

同 一个Channel的事件,并不保证处理顺序,可能一个线程先处理了Channel A (Event 3),然后另一个线程才处理Channel A (Event 2),如果业务不要求保证事件的处理顺序,我认为还是尽量使用MemoryAwareThreadPoolExecutor比较好

Netty采用标准的SEDA(Staged Event-Driven Architecture) 架构
SEDA的核心思想是把一个请求处理过程分成几个Stage,不同资源消耗的Stag使用不同数量的线程来处理,Stag间使用事件驱动的异步通信模式。更进一步,在每个Stage中可以动态配置自己的线程数,在超载时降级运行或拒绝服务。

Netty所设计的事件类型,代表了网络交互的各个阶段,每个阶段发生时,会触发相应的事件并交给ChannelPipeline进行处理。事件处理都是通过Channels类中的静态方法调用开始的。

Channels中事件流转静态方法:
1. fireChannelOpen
2. fireChannelBound
3. fireChannelConnected
4. fireMessageReceived
5. fireWriteCompleteLater
6. fireWriteComplete
7. fireChannelInterestChangedLater
8. fireChannelDisconnectedLater
9. fireChannelDisconnected
10. fireChannelUnboundLater
11. fireChannelUnbound
12. fireChannelClosedLater
13. fireChannelClosed
14. fireExceptionCaughtLater
15. fireExceptionCaught
16. fireChildChannelStateChanged


Netty将网络事件分为两种类型:
1.Upstresam:上行,主要是由网络底层反馈给Netty的,比如messageReceived、channelConnected
2.Downstream:下行,框架自己发起的,比如bind、write、connect等

Netty的ChannelHandler 分为3种类型:
1.只处理Upstream事件:实现ChannelUpstreamHandler接口
2.只处理Downstream事件:实现ChannelDownstreamHandler接口
3.同时处理Upstream和Downstream事件:同时实现ChannelUpstreamHandler和ChannelDownstreamHandler接口
ChannelPipeline 维持所有ChannelHandler的有序链表,当有Upstresam或Downstream网络事件发生时,调用匹配事件类型的 ChannelHandler来处理。ChannelHandler自身可以控制是否要流转到调用链中的下一个 ChannelHandler(ctx.sendUpstream(e)或者ctx.sendDownstream(e)),这一样有一个好处,比如业务 数据Decoder出现非法数据时不必继续流转到下一个ChannelHandler

下面是我胡乱的画的一个图:

 

分享到:
评论
2 楼 wzhiju 2016-12-19  
谢谢,最后问一下,画图用的是什么软件?
1 楼 yqluo2008 2016-01-28  
非常详细,谢谢分享。

相关推荐

    Netty服务器线程模型概览-线程模型

    Netty服务器线程模型概览_线程模型

    Netty实现原理浅析.pdf

    3. **多Reactor多线程模型**:这是Netty默认采用的模型。它进一步将Reactor分为两个角色——主Reactor(Boss)和次Reactor(Worker)。主Reactor负责监听服务器套接字,接受新的连接请求,并将其分配给次Reactor。次...

    Netty_in_Action_v10_MEAP

    - **第15章:事件循环与线程模型**:深入研究Netty的线程模型,特别是事件循环机制。 - **第16章:案例研究,第一部分**:通过Droplr、Firebase和UrbanAirship等公司的实际案例,展示Netty的实际应用效果。 - **...

    netty权威指南

    虽然正文中没有提供书的具体章节安排,但可以推测,作为一本权威指南,它将从Netty的基础架构和工作原理讲起,可能包括网络IO模型的基础知识、Netty的事件模型、核心组件的介绍、实际案例的剖析以及性能优化和调试...

    Netty权威指南 第2版 带书签目录 完整版.pdf

    第二部分则深入到了Netty的内部原理,包括Netty的启动流程、核心组件的内部运作机制、线程模型与并发模型,以及如何进行高效的内存管理。 对于Netty的学习者来说,了解其架构设计是非常重要的。Netty采用了模块化的...

    netty源码解析视频

    - Netty采用了多线程模型来处理网络I/O操作。 - 主要包括`BossGroup`和`WorkerGroup`两种线程池。 - `BossGroup`负责接受客户端连接请求,而`WorkerGroup`则负责处理网络读写操作。 #### 四、Netty高级特性 1. ...

    Netty In Action(中文版)【高清】

    5. **线程模型与事件循环**:解析Netty的事件驱动模型,探讨EventLoopGroup和EventLoop的运作机制,以及如何通过它们实现线程池优化。 6. **Netty的连接管理**:讨论连接的创建、管理和关闭,包括连接的建立、心跳...

    Netty_in_Action(第五版目录修正)

    - **线程模型概述**:解释Netty中的不同线程模型及其优缺点。 - **最佳实践**:给出选择合适线程模型的最佳实践建议。 **第16章:在EventLoop中取消注册/重新注册** - **EventLoop机制**:深入探讨EventLoop的工作...

    Java Netty-入门教程.pdf

    Netty 概览 ##### 1.1 Netty 是什么? Netty 是一款基于 Java 的高性能网络应用框架,支持多种协议,包括但不限于 HTTP、FTP、SMTP 等,并且特别擅长处理 TCP 和 UDP 协议。它采用异步非阻塞的事件驱动模型来处理...

    尚硅谷NIO百度云连接

    4. **性能优化**:探讨如何利用NIO进行性能优化,比如合理配置缓冲区大小、优化多线程模型等。 5. **实战项目**:提供完整的实战项目,让学员能够在实践中巩固所学知识,增强解决实际问题的能力。 综上所述,...

    高并发编程实战1,2,3阶段

    - **Netty框架**:基于事件驱动模型的高性能网络通信框架。 ##### 4. 性能调优与监控 - **JVM参数调整**:如-Xms、-Xmx、-XX:+UseConcMarkSweepGC等。 - **线程分析工具**:VisualVM、jstack命令行工具。 - **性能...

    java网络编程高清pdf

    - **定义**:在BIO模型中,客户端与服务器建立连接后,客户端发起的数据读取或写入操作会导致线程阻塞,直至操作完成。这种模型简单易懂,但在高并发场景下,每个连接都需要分配一个独立的线程来处理,容易导致资源...

    seata源码研究.docx

    - **Netty**:Netty使用NIO实现了一套高效、可扩展的网络编程模型。通过提供一系列API,开发者可以专注于业务逻辑而非底层网络编程细节。 ##### 2. TM(Transactional Manager)实现原理解析 TM在分布式事务中扮演...

    跳槽涨薪精选面试题.pdf

    ### 跳槽涨薪精选面试题概览 在当今快速发展的IT行业中,技术更新换代的速度非常快,为了能够顺利地跳槽并获得理想的薪资涨幅,掌握最新的技术和面试技巧至关重要。本文将根据提供的文件标题、描述以及部分内容,对...

    consumer.start.pdf

    11. **NettyRemotingClient**:基于 Netty 的远程通信客户端,用于客户端与消息服务器之间的通信。 12. **PullMessageService**:负责从消息服务器拉取消息的服务。 13. **RebalanceService**:用于消息队列重平衡的...

    大数据课程体系

    - **Netty异步io通信框架**:学习Netty框架的特点及其在高并发服务器开发中的应用。 - **Zookeeper实现netty分布式架构的高可用**:利用Zookeeper保障Netty服务的高可用性。 #### 九、消息队列Kafka - **kafka是...

Global site tag (gtag.js) - Google Analytics