线程模式
- 目前的线程模式,
传统阻塞IO服务模型、Reactor(反应器模式)模式
传统阻塞IO服务模型,采用阻塞IO模型获取输入的数据,每个连接需要独立的线程完成数据的输入、业务处理和返回;当并发数很大时,需要创建大量的线程,占用大量系统资源;连接创建后,如果当前线程暂时没有数据可读,阻塞在read操作上,造成线程资源浪费
- Reactor的三种典型实现,
单Reactor单线程、单Reactor多线程、主从Reactor多线程
- 单Reactor单线程,前台接待员和服务员是同一个人,全程服务顾客
- 单Reactor多线程,1个前台接待员,多个服务员,接待员只负责接待
- 主从Reactor多线程,多个前台接待员,多个服务员
- Netty线程模式基于
主从Reactor多线程模型
Reactor
- 反应器模式、分发者模式(Dispatcher)、通知者模式(notifier)
- 基本原理
- 基于IO服用模型,多个连接共用一个阻塞对象
Selector,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接,当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理
- 基于线程池复用线程资源,不必为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接业务

- Reactor,通过一个或多个输入同时传递给服务处理器的模型,基于事件驱动
- 服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程,分发者
- 使用了多路IO复用监听事件,收到事件后,分发给某个线程(进程),网络服务高并发处理的关键

- Reactor,在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件作出反应,类比电话接线员,通过接线员进行转接
- Handlers,处理程序执行IO事件要完成的实际事件,真正处理业务的部分;Reactor通过调度适当的处理程序来响应IO事件,处理程序执行非阻塞操作
- 响应快,不必为单个同步事件所阻塞,但是本身依然是同步的,单个阻塞,但是可以调用其他的线程处理其他业务
- 最大程度避免复杂的多线程及同步问题,避免了多线程/进程的切换开销
- 扩展性好,增加Reactor实例,来利用CPU资源
- 复用性好,与具体事件处理逻辑无关
单Reactor单线程
- reactor和handler在一个线程里
- 群聊系统
- accept,接受链接
- readData,接受发送的信息,进行群发

Reactor对象通过Select监控客户端请求,收到事件后,通过DIspatch进行分发
- 如果建立连接请求事件,则由
Acceptor通过Accept处理连接请求,创建Handler对象处理连接完成后的业务
- 如果不是建立连接事件,则
Reactor分发调用连接对应的Handler来响应
- 模型简单,没有多线程、进程通信、竞争的问题
- 性能不足,只有一个线程,无法完全发挥多核CPU性能,Handler在处理某个连接的业务时,整个进程无法处理其他连接事件
- 线程意外终止,或进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障
- 使用场景,适用于客户端的数量有限,业务处理非常迅速,如
Redis在业务处理的时间复杂度O1的情况
单Reactor多线程
Reactor 通过select监听客户端请求事件,收到事件后,通过dispatch进行分发
- 如果是建立连接,则有
Acceptor通过accept处理连接请求,创建Handler对应该连接,完成连接之后的业务
- 如果不是连接请求,由
Reactor分发调用连接对应的Handler来处理
Handler负责响应事件,不做具体的业务处理,通过read读取数据后,分发给Worker线程池的某个线程处理某个业务
Worker线程池分配独立的线程,完成真正的业务,同时把结果返回给Handler
Handler收到响应后,通过send将结果返回给Client

- 优点,可以充分利用多核CPU处理能力
- 缺点,多线程数据共享和访问比较复杂;Reactor处理所有的事件的监听和响应,在单线程运行,高并发场景中出现性能瓶颈
主从Reactor多线程
Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
- 当
Acceptor处理连接事件后,MainReactor将连接分配给SubReactor
SubReactor将连接加入到连接队列进行监听,并创建Handler进行事件处理
- 当有新事件发生时,
SubReactor调用对应的Handler进行处理
Handler通过read读取数据,分配给Worker线程池分配的线程进行业务处理,并返回处理
Handler收到响应的结果后,通过send将结果返回给Client
Reactor主线程可以对应多个Reactor子线程,即MainReactor可以关联多个SubReactor

- Doug Lea,Scalable IO in Java

- 优点,父线程与子线程交互简单,职责明确,父线程只需要接受新连接,子线程完成后续的业务处理
- 缺点,编程复杂读高
- 实际应用,Nginx、Memcache、Netty
Netty模型
简单版
BossGroup维护Selector,只关心Accept
- 当接收到
Accept事件,获取对应的SocketChannel,封装成NIOSocketChannel,并注册到Worker线程的Selector(事件循环)
- 当
Wroker线程监听到Selector中注册的通道,发生感兴趣的事件,进行处理,分配给Handler,已经加入到通道中

进阶版
- 主从Reactor

详细版
- Netty抽象出两组线程池,
BossGroup专门负责接受客户端的连接,WorkerGroup专门负责网络的读写
BossGroup和WorkerGroup类型为NioEventLoopGroup,相当于事件循环组,这个组中还有多个时间循环,每一个事件循环为NioEventLoop
NioEventLoop,表示一个不断循环的执行处理任务的线程,每个都有一个Selector,用于监听绑定在其上的Socket的网络通讯
NioEventLoop内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由IO线程的NioEventLoop负责
NioEventLoopGroup含有多个线程,即含有多个NioEventLoop
- 每个
NioEventLoop中包含一个Selector,一个taskQueue
- 每个
NioEventLoop的Selector可以注册监听多个NioChannel
- 每个
NioChannel只会绑定在唯一的NioEventLoop上
- 每个
NioChannel都绑定一个自己的ChannelPipeline
- 轮询
accept事件
- 处理
accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个Worker NioEventLoop上的Selector上
- 处理任务队列的其他任务,即
runAllTasks
- 轮询
read/write事件
- 处理IO事件,即
read/write事件,在对应的NioSocketChannel上进行处理
- 处理任务队列的其他任务,即
runAllTasks
- 每个
Work NioEventLoop在处理业务时,会使用pipeline 管道,其中包含了Channel,通过pipeline可以获取到对应的通道,管道中维护了很多的处理器,通过处理器进行业务处理

Netty-TCP服务
案例
Client
public class Client {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());
}
});
System.out.println("client is ready");
ChannelFuture cf = bootstrap.connect("localhost", 8888).sync();
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
ClientHandler
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client: " + ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, server", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("msg from server: " + buf.toString(CharsetUtil.UTF_8));
System.out.println("server address: "+ ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Server
public class Server {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());
}
});
System.out.println("server is ready");
ChannelFuture cf = bootstrap.bind(8888).sync();
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
ServerHandler
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("server ctx: " + ctx);
ByteBuf buf = (ByteBuf) msg;
System.out.println("client msg: " + buf.toString(CharsetUtil.UTF_8));
System.out.println("client address: " + ctx.channel().remoteAddress());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, client", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
分析
BossGroup和WorkerGroup包含的NioEventLoop子线程个数,默认为系统CPU核数*2,由构造函数追入

WorkerGroup根据子线程数循环分配,加入当前子线程数为8,并且有9个客户端访问,前八个客户端依次对应一个子线程,第九个重新分配给第一个
- 手动设置线程数
EventLoopGroup bossGroup = new NioEventLoopGroup(1)
EventLoopGroup workerGroup = new NioEventLoopGroup(8)
EventLoop包含的信息,每个子线程都有自己selector,进行事件循环

ctx的信息,涉及到出站入站的操作,利用管道实现流式操作

pipeline实现上为双向链表
