目录
一、NIO存在问题以及Netty的优点
二、线程模型基本介绍
三、工作原理图(传统同步阻塞式IO)
四、Reactor模式
五、单Reactor单线程
六、单Reactor多线程
七、主从REACTOR多线程
八、Reactor模式小结
九、Netty模型
十、异步模型
一、NIO存在问题以及Netty的优点
原生NIO存在的问题
1.NIO的库和API繁杂,使用麻烦
需要掌握ServerSocketChannel,SocketChannel,Selector,ByteBuffer
需要掌握多线程,网络编程
2.传输遇到各种问题
1.断连重连,网络闪断,网络拥塞
2.半包读写
3.失败缓存
4.异常流的处理
3.JDK的NIO的Epoll Bug会导致Selector空轮询,最终导致CPU100%占用,直到JDK1.7版本这个问题依旧存在,没有根本解决
NETTY的优点
1.官网翻译
1.JBOSS 提供的一个 Java 开源框架,Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序
2.简化和流程化了 NIO 的开发过程
3.Netty获得了广泛的应用知名的 Elasticsearch 、Dubbo 框架内部都采用了 Netty。
2.优点
(1)高性能,吞吐量,延迟低,资源消耗低,不必要的内存复制(零拷贝)
(2)设计优雅,使用各种传输类型(扩展性强)的阻塞和非阻塞Socket,灵活的可扩展事件模型
定制线程模型----线程池
(3)社区活跃,版本Bug修复快,迭代周期短
3.Netty版本说明
(1)Netty5出现重大bug,已经被官网废弃了,目前推荐使用的是Netty4.x的稳定版本
(2)netty 下载地址
二、线程模型基本介绍
1.基本介绍
(1)线程模型有:传统阻塞 I/O 服务模型 Reactor 模式
(2)根据 Reactor的三种实现方式
#Reactor数量和处理的资源池中的线程数量不同,有3中实现方式
1.单 Reactor 单线程
2.单 Reactor 多线程
3.主从 Reactor 多线程(多个Reactor)
Netty线程模式(Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从 Reactor多线程模型有多个Reactor)
三、工作原理图(传统同步阻塞式IO)
四、Reactor模式
1.解决传统阻塞I/O模型的2个缺点
(1)多连接对应一个阻塞对象
1.1 基于I/O复用模型,多个连接共用一个阻塞对象,服务器端无需阻塞所有的连接,只需要阻塞公用的阻塞对象即可
1.2 当某个连接有数据可以处理(读事件),则OS会通知引用程序,线程从阻塞状态返回,开始业务处理
(2)复用线程池
2.1 server端不必为每个连接创建线程,连接完成后的业务处理 --分配--> 线程处理
2.2 一个线程就可以处理多个连接对象呢
(3)Reactor模式高并发关键
3.1 server端收到多个请求,分发到相应的线程,这就是网络服务器高并发的处理关键
2.I/0复用结合线程池,就是Reactor模式基本设计思想
1.复用handler和使用了线程池
2.NIO + 线程池
3.selector是单线程,而reactor是用共享的线程池处理
4.BIO没有多路复用,是简单递归,多路复用是操作系统底层实现的
3.Reactor模式中核心组成(Reactor和Handler)
4.Reactor 模式分类和叫法
#分类
单 Reactor 单线程
单 Reactor 多线程
主从 Reactor 多线程
#叫法
1.反应器模式
2.分发者模式(Dispatcher)----server收到多个请求分派到相应的处理线程处理(从线程池)
3.通知者模式(notifier)---有新数据,OS通知application,线程从阻塞状态返回,进行业务处理
五、单Reactor单线程
优缺点 || 场景
1.优点:模型简单,没有多线程和竞争的问题,一个线程中完成全部功能(连接,读写业务逻辑)
2.缺点:
(1)性能低: 单线程无法发挥多核 CPU 的性能。Handler在处理某个连接的业务时,整个进程则无法处理其他连接,导致应用的性能瓶颈。
(2)可靠性: 线程如果进入死循环,会导致整个系统通信模块不可用,造成节点故障
3.使用场景:Redis在业务处理的时间复杂度 O(1) 的情况
(1)客户端的数量有限,业务处理非常快速
(2)连接少,处理快,就不会阻塞单线程
六、单Reactor多线程
原理图
优缺点分析
1.优点:可以充分的发挥多核CPU的性能
2.缺点:虽然处理逻辑可以由线程池来处理(并发),但是Reactor依然是单线程运行的,它需要处理所有事件的监听和响应(accept和handler都是单线程)
在高并发的场景容易出现性能瓶颈,多线程的数据共享和数据访问
七、主从REACTOR多线程
工作原理图
优缺点分析
1.优点
(1)父子线程的职责清晰,MainReactor负责接收新连接,SubReactor负责后续的业务处理(read数据----多线程),在把业务处理交给线程池处理(多线程)
(2)父子线程的数据交互简单,Reactor主线程只需要把新连接传给子线程,子线程无需返回数据
2.缺点
(1)编程复杂度高
3.场景:
(1)Nginx主从Reactor多进程模型
(2)Netty主从多线程模型
(3)Memcached主从多线程模型
八、Reactor模式小结
总结
#3种模式用生活案例来理解
1.单Reactor单线程,前台接待员和服务员是同一个人(select是一个线程处理)
2.单Reactor多线程,1个前台接待员,多个服务员(线程池),接待员只负责接待
3.主从 Reactor 多线程,多个前台接待员(多个线程subReactor来handler),多个服务生(线程池)
#Reactor模式具有如下的优点
1.响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的
可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销
2.扩展性好,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源
3.复用性好,Reactor 模型本身与具体事件处理逻辑无关,具有很高的复用性
九、Netty模型
工作原理示意图1-简单版
Netty 主要基于主从 Reactors 多线程模型(如图)做了一定的改进,其中主从 Reactor 多线程模型有多个 Reactor
工作原理示意图-详细版
Pipeline管道的处理器Handler是关键,最后都是由它来处理读写事件
(1)管道像水流一样,中间有许多的handler处理器对水进行加工,形成矿泉水-------业务逻辑------处理数据
(2)通道更多的是传输数据,读写的操作--------read,write操作----传输数据
1.Netty快速入门实例-TCP服务
1.导入netty的包
导入到lib目录下jar包: https://www.cnblogs.com/128-cdy/p/12608779.html
<!--netty依赖-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
</dependency>
2.编写客户端和服务器端代码
//1.NettyServer.java服务器
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 1.创建BossGroup 和 WorkerGroup,两个都是无限循环
// 含有多少子线程(NioEventLoop),默认是cpu核心数 * 2
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 2.创建服务器端的启动对象bootstrap,配置各种参数
ServerBootstrap bootstrap = new ServerBootstrap();
try{
// 3.使用链式编程设置参数(底层其实是一堆的set方法)
bootstrap.group(bossGroup,workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//使用NioServerSocketChannel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128)//设置线程队列得到的连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {
//给pipeline设置处理器(管道和通道可以相互获取)
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});//给我们的worker Group的EventLoop对应的管道设置处理器
System.out.println("...服务器 is ready...");
// 4.绑定一个端口并同步(启动服务器),生成一个ChannelFuture对象
ChannelFuture cf = bootstrap.bind(6668).sync();
// 5.对关闭通道进行监听
cf.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
//2.NettyClient.java客户端
public class NettyClient {
public static void main(String[] args) throws Exception{
// 1.客户端需要一个事件循环组
NioEventLoopGroup group = new NioEventLoopGroup();
// 2.创建客户端启动对象bootstrap,并进行相应的配置
Bootstrap bootstrap = new Bootstrap();
try {
// 3.设置相关参数
bootstrap.group(group)//设置线程组
.channel(NioSocketChannel.class)//设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());//加入自己的处理器
}
});
System.out.println("客户端 ok ...");
// 4.启动客户端连接服务器(ChannelFuture设计到Netty的异步模型)
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
// 5.监听关闭通道
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
3.启动效果(先启动server,在启动client)
2.源码之NioEventLoop&&ChannelHandlerContext
1.NioEventLoopGroup含有多少子线程(NioEventLoop)
(1)默认是cpu核心数 * 2
(2)源码解释
(3)断点调试验证
(4)修改bossGroup的子线程个数(NioEventLoop) == 1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
(5)NioEventLoop包含什么重要的信息
2.查看Channel和Pipeline的关系以及Ctx(ChannelHandlerContext)
1.Channel和Pipeline的关系: 我中有你,你中有我
2.Ctx(ChannelHandlerContext)包含了丰富的信息
//NettyServerHandler.java
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程: " + Thread.currentThread().getName());
System.out.println("server ctx: " + ctx);
System.out.println("看看channel和pipeline的关系");
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();//底层是一个双向链表,出栈入栈
}
3.任务队列中的Task有3种典型使用场景
1.用户自定义的普通任务 ------------> taskQueue
1.NioEventLoop(一个线程)其实就是线程池的实现(类似newSingleThreadExecutor),顶层实现了ExecutorService接口
2.任务队列"入队出队"取出任务执行,线程一个只能执行一个任务,其他放在阻塞队列,按顺序执行
2.用户自定义的定时任务 ----------> scheduledTaskQueue
(1)定時任务是延迟7s执行,但是前面的任务顺序执行占用了4s,则在过3s,定时任务就会执行
(2)并不是顺序执行到定时任务的时候,对定时任务延迟7s再执行
(3)这里可以理解为定时任务和自定义任务是并行的
// 二.用户自定义的定时任务
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,服务器耗时长的操作完成,喵4...", CharsetUtil.UTF_8));
System.out.println("taskQueue中处理的线程名:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println("发生异常: " + e.getMessage());
}
}
},7, TimeUnit.SECONDS);// 延迟5s执行这个任务(前面的任务顺序执行占用了4s,则在过3s,定时任务就会执行)
System.out.println("go on...");
3.系统向多用户推送消息
1.非当前Reactor线程调用Channel的方法
2.推送系统可以根据用户表示找到对应的Channel引用,然后调用通道的eventLoop的execute方法提交到任务队列执行
1.可以用一个集合管理SocketChannel
2.推送消息,可以将业务加入到各个channel对应的NioEventLoop的taskQueue或者scheduledTaskQueue
ctx.channel().eventLoop().execute(new Runnable());
ctx.channel().eventLoop().schedule(Runnable);
4.总结
1.Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作
2.Netty模型基础知识
(1)NioEventLoopGroup 下包含多个 NioEventLoop,每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
(2)NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定在其上的 socket 网络通道(监听多个 NioChannel)每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
2.1 NioEventLoop 内部采用串行化设计,从消息的读取->解码-><u>处理</u>->编码->发送,始终由 IO 线程 NioEventLoop 负责
2.2 处理的操作耗时较长,则会交给任务队列,异步执行
(3)每个 NioChannel 都绑定有一个自己的 ChannelPipeline(互相包含关系)
十、异步模型
1.基本介绍
(1)异步调用之后,调用者不能马上得到结果,实际上是在处理调用方法之后,通过通知来回调方法
(2)Netty 中的 I/O 操作是都是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。
ChannelFuture cf = bootstrap.bind(6668).sync();
(3)Future-Listener机制,用户可以方便的获取IO操作结果
(4)Netty异步模型建立在future和callback之上的
callback容易理解,就是回调
future:举个栗子,调用一个非常耗时的方法fun,等待结果显示不合适,这时先返回一个Future对象,后续用Future对象监控fun方法的执行状态(Future-Listener机制),获取状态作出相应的处理(回调方法去处理)
2.深入理解Future-Listener机制
#1.ChannelFuture源码
The result of an asynchronous {@link Channel} I/O operation.
解释:是一个异步IO操作的结果
#2.ChannelFuture和JDK1.8新加个FutureTask都是继承与JUC的Future接口,异步回调
(1)ChannelFuture 是一个接口 : public interface ChannelFuture extends Future<Void>
(2)future可以添加监听器,当监听的事件发生时,就会通知到监听器.
#3.前端的Ajax也是异步回调,加上Future都是观察者模式(监听--通知---回调)