介绍:
- 异步事件驱动NIO框架
- Netty是由JBOSS提供的一个开源框架,是业界最流行的NIO框架,整合了多种协议(FTP、SMTP、HTTP等各种二进制文本文件)
- 优点
1)API使用简单
2)成熟、稳定
3)社区活跃 有很多种NIO框架 如mina
4)经过大规模的验证(互联网、大数据、网络游戏、电信通信行业)
4.那些主流框架产品在用?
1)搜索引擎框架 ElasticSerach
2) Hadopp子项目Avro项目,使用Netty作为底层通信框架
3)阿里巴巴开源的RPC框架 Dubbo
BIO(同步阻塞io,一客户端一线程)
流的关闭在finally中关闭
优点:模型简单、编码简单
缺点:请求数和线程数 N:N关系,高并发情况下,CPU切换线程上下文损耗大。
同步:执行一个操作之后,进程触发IO操作并等待(也就是我们说的阻塞)或者轮询的去查看IO操作(也就是我们说的非阻塞)是否完成,等待结果,然后才继续执行后续的操作。
异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
非阻塞:进程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。
我认为
简单理解:
同步和异步:请求发出后,是否需要等待请求结果,才能继续执行其他操作(针对返回消息)
同步:一直等待结果的返回(轮询等待结果也是)
异步:去执行其他事情,结果返回时,唤醒执行之前的消息
阻塞非阻塞(针对线程)
阻塞:在调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会被唤醒执行后续的操作。
非阻塞:在结果没有返回之前,该调用不会阻塞住当前线程。
public class BioServer {
private static final int PORT = 8080;
public static void main(String[] args)throws IOException {
ServerSocket server = null;
try {
server = new ServerSocket(PORT);
System.out.println("the time server is start in port :"+PORT);
Socket socket = null;
while (true){
socket = server.accept();
new Thread(new TimeServerHandler(socket)).start();
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(server != null){
System.out.println("the time server close");
server.close();
}
}
}
}
public class TimeServerHandler implements Runnable{
private Socket socket;
public TimeServerHandler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader( new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(),true);
String body = null;
while (( body = in.readLine())!= null && body.length()!=0){
System.out.println("the time server receive msg :"+body);
out.println(new Date().toString());
}
} catch (Exception e){
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(this.socket != null){
try{
this.socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
启动后在cmd上输入:curl localhost:8080 测试连接
五种IO模型,理解
1.阻塞I/O模型
老李去火车站买票,排队三天买到一张退票。
耗费:在车站吃喝拉撒睡 3天,其他事一件没干。
2.非阻塞I/O模型
老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。耗费:往返车站6次,路上6小时,其他时间做了好多事。
3.I/O复用模型
1.select/poll
老李去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次
2.epoll
老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话
4.信号驱动I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话
5.异步I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。
耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话
多路复用(高并发编程必备知识IO多路复用技术select、poll、epoll)
I/O多路复用,I/O是指网络I/O, 多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。
简单来说:就是使用一个或者几个线程处理多个TCP连接
最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程
select:
基本原理:
监视文件3类描述符: writefds、readfds、和exceptfds
调用后select函数会阻塞住,等有数据 可读、可写、出异常 或者 超时 就会返回
select函数正常返回后,通过遍历fdset整个数组才能发现哪些句柄发生了事件,来找到就绪的描述符fd,然后进行对应的IO操作
几乎在所有的平台上支持,跨平台支持性好
缺点:
1)select采用轮询的方式扫描文件描述符,全部扫描,随着文件描述符FD数量增多而性能下降
2)每次调用 select(),需要把 fd 集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用户空间)
2)最大的缺陷就是单个进程打开的FD有限制,默认是1024 (可修改宏定义,但是效率仍然慢)
static final int MAX_FD = 1024
poll:
基本流程:
select() 和 poll() 系统调用的大体一样,处理多个描述符也是使用轮询的方式,根据描述符的状态进行处理
一样需要把 fd 集合从用户态拷贝到内核态,并进行遍历。
最大区别是: poll没有最大文件描述符限制(使用链表的方式存储fd)
epoll:
基本原理:
在2.6内核中提出的,对比select和poll,epoll更加灵活,没有描述符限制,用户态拷贝到内核态只需要一次
使用事件通知,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用callback的回调机制来激活对应的fd
优点:
1)没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
2)效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降
2)通过callback机制通知,内核和用户空间mmap同一块内存实现
Linux内核核心函数
1)epoll_create() 在Linux内核里面申请一个文件系统 B+树,返回epoll对象,也是一个fd
2)epoll_ctl() 操作epoll对象,在这个对象里面修改添加删除对应的链接fd, 绑定一个callback函数
3)epoll_wait() 判断并完成对应的IO操作
缺点:
编程模型比select/poll 复杂
例子:100万个连接,里面有1万个连接是活跃,在 select、poll、epoll分别是怎样的表现
select:不修改宏定义,则需要 1000个进程才可以支持 100万连接
poll:100万个链接,遍历都响应不过来了,还有空间的拷贝消耗大量的资源
epoll: 不用遍历fd信息,不用内核空间和用户空间相互拷贝(比另两个好)
Java的I/O演进历史
简介:讲解java的IO演进历史
1、jdk1.4之前是采用同步阻塞模型,也就是BIO
大型服务一般采用C或者C++, 因为可以直接操作系统提供的异步IO,AIO
2、jdk1.4推出NIO,支持非阻塞IO.
3、jdk1.7升级,推出NIO2.0,提供AIO的功能,支持文件和网络套接字的异步IO
Netty线程模型(Reactor模式)
Reactor模式基于事件驱动,适合处理海量的I/O事件,属于同步非阻塞IO(NIO)
设计模式——Reactor模式(反应器设计模式),是一种基于事件驱动的设计模式,在事件驱动的应用中,将一个或多个客户的服务请求分离(demultiplex)和调度(dispatch)给应用程序。在事件驱动的应用中,同步地、有序地处理同时接收的多个服务请求
一般出现在高并发系统中,比如Netty,Redis等
优点
1)响应快,不会因为单个同步而阻塞,虽然Reactor本身依然是同步的;
2)编程相对简单,最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
缺点
1)相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
2)Reactor模式需要系统底层的的支持,比如Java中的Selector支持,操作系统的select系统调用支持
Reactor单线程模型(比较少用)
所有的I/O操作都在同一个NIO线程上面完成,(建立连接,读取或写入消息)
Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。
使用场景:
对应小业务则适合,编码简单;对于高负载、大并发的应用场景不适合,一个NIO线程处理太多请求,则负载过高,并且可能响应变慢,导致大量请求超时,而且万一线程挂了,则不可用了
Reactor多线程模型
- 有一个专门的NIO线程--acceptor新城用于监听服务端,接收客户端的TCP连接请求;
- 网络I/O操作--读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;
- 1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。
使用场景:
可满足大多数场景,但是当Acceptor需要做复杂操作的时候,比如认证等耗时操作,再高并发情况下则也会有性能问题
Reactor主从线程模型
服务端用于接收客户端连接的不再是1个单独的NIO线程,而是一个独立的NIO线程池(MainReactor)。Acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到I/O线程池(sub reactor线程池)(SubReactor)的某个I/O线程上,由它负责SocketChannel的读写和编解码工作。
使用场景:
满足目前的大部分场景,也是Netty推荐使用的线程模型(BossGroup、WorkGroup)
为什么Netty使用NIO而不是AIO,是同步非阻塞还是异步非阻塞?
在Linux系统上,AIO的底层实现仍使用EPOLL,与NIO相同,因此在性能上没有明显的优势
Netty整体架构是reactor模型,采用epoll机制,所以往深的说,还是IO多路复用模式,所以也可说netty是同步非阻塞模型(看的层次不一样)
很多人说这是netty是基于Java NIO 类库实现的异步通讯框架
特点:异步非阻塞、基于事件驱动,性能高,高可靠性和高可定制性。