[Java] IO模型

同步 异步

  • 调用者是否主动等待函数返回值(站在调用者角度)
  • 多线程是异步的一种实现方式,nodejs单线程也可实现异步
  • 描述两个线程间的关系,关注双方消息通信机制,同步--步调一致,异步--分别行动
  • 两个线程之间要么是同步的,要么是异步的
  • 同步操作时,调用者需要等待被调用者返回结果,才会进行下一步操作
  • 异步操作时,调用者不需要等待被调用者返回调用,即可进行下一步操作
  • 被调用者通常依靠事件、回调等机制来通知调用者结果

同步

1 public static void main() {
2     int result = doSomeThing();
3 }
View Code

异步

1 public static void main() {
2     new Thread(() -> {
3         int result = doSomeThing();    
4     })
5 }
View Code

阻塞 非阻塞

  • 函数在等待某一事件结果时,是将线程挂起,还是立即返回未就绪消息(站在执行者角度)
  • 描述线程在CPU中的执行方式
  • while循环一直等条件满足就是阻塞,if判断条件不满足就立即返回是非阻塞
  • 同一个线程在某个时刻,线程要么处于阻塞状态,要么处于非阻塞状态
  • 关注的是程序在等待调用结果(消息,返回值)时的状态
  • 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

阻塞

1 public void int read(byte[] buffer) {
2     while(磁盘未就绪) {
3         将当前线程挂起并让出 CPU;
4     }
5     // 此时磁盘已就绪
6     真正去读数据到 buffer 中
7     return 读到的字节数;
8 }
View Code

非阻塞

1 public void int read(byte[] buffer) {
2     if(磁盘未就绪) {
3         // 立刻返回
4         return -1;
5     }
6     真正去读数据到 buffer 中
7     return 读到的字节数;
8 }
View Code

组合模式

  • 一个函数是非阻塞的,用另一个函数把它包起来,对外提供一个阻塞的函数
 1 // 这是个非阻塞的函数
 2 public void int read(byte[] buffer) {
 3     if(磁盘未就绪) {
 4         // 立刻返回
 5         return -1;
 6     }
 7     真正去读数据到 buffer 中
 8     return 读到的字节数;
 9 }
10 
11 // 包一层,变成阻塞的
12 public void int read2(byte[] buffer) {
13     int result;
14     while((result = read(buffer)) == -1) {
15         将线程挂起并让出 CPU 资源
16     }
17     // 此时已读到数据
18     return result;
19 }
View Code

类比1

  • 打电话向旅馆前台预定房间
  • 第一次打占线(阻塞)
  • 第二次打接通,前台说“先别挂电话,我在系统里查一下”(同步)
  • 我可以一直等着(阻塞),也可以放下电话去喝个水再回来(非阻塞)
  • 前台说“我先查一下,待会找到了通知你”(异步)
  • 我肯定不会一直等了(非阻塞)

类比2

  • 小明周日在家打扫卫生,工作有烧水、洗衣服、扫地
  • 同步阻塞:小明一个人,先烧水,再洗衣服,最后扫地
  • 同步非阻塞:小明一个人,先把水烧上,再把衣服泡上,然后扫地,水开了关水,扫完地了洗衣服
  • 异步阻塞:小明烧水,叫来小红帮忙洗衣服,小红洗完衣服问小明还有什么能干的,小明说你帮我扫地吧
  • 异步非阻塞:小明烧水,叫来小红帮忙洗衣服,扫地,小红先把衣服泡上,然后扫地

BIO NIO AIO

  • IO(同步阻塞)
    • 面向流,单向传输
    • 阻塞IO和非阻塞IO的区别在于第一步发起IO请求是否会被阻塞
    • 同步IO和异步IO的区别就在于第二个步骤是否阻塞(操作系统做完IO操作再将结果返回给你
    • 两个阶段
      • 数据准备阶段
      • 内核空间复制数据到用户进程缓冲区(用户空间)阶段
  • BIO(同步阻塞)
    • 基于 IO+Socket 就可以编写一个最基本的BIO服务器
    • 一个连接一个线程
    • 通常有一个acceptor(消费者) 去负责监听客户端的连接
    • 它接收到客户端的连接请求之后为每个客户端创建一个线程进行链路处理,处理完成之后,线程销毁
    • 并发量比较大的情况下,系统会创建大量的线程
    • 从而导致服务器线程暴增,性能急剧下降,甚至宕机
    • JDK1.4之前,用Java编写网络请求,都是建立一个ServerSocket
    • 客户端建立 Socket 时就会询问是否有线程可以处理,如果没有,要么等待,要么被拒绝
  • NIO(同步非阻塞)
    • 单线程处理,多路复用
    • 基于事件驱动思想,解决BIO的大并发问题
    • IO操作准备好时再通知线程
    • 客户端同时和多个服务器进行通讯,必须使用多线程处理
    • 即将每一个客户端请求分配给一个线程来单独处理
    • 由缓冲区(Buffers)、通道(Channels)和非阻塞I/O的核心类组成
    • 客户端发送的连接请求都会注册到多路复用器上
    • 多路复用器轮询到连接有I/O请求时才启动一个线程进行处理
  • AIO(异步非阻塞)
    • 多线程处理,一个请求一个线程
    • IO操作已经完成后,再向线程发出通知(触发回调函数)
    • 异步IO采用“订阅-通知”模式
      • 应用程序向操作系统注册IO监听,然后继续做自己的事情
      • 操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数
    • 读写操作
      • 当进行读写操作时,只须直接调用API的read或write方法
      • 对于读操作,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序
      • 对于写操作,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序
      • read/write方法都是异步的,完成后会主动调用回调函数

零拷贝

  • Linux
    • 传统方式
    • 用户态直接IO:CPU和磁盘间存在性能差异,造成资源浪费,不常用
    • mmap+write:减少1次CPU拷贝
    • sendFile:用户程序无法修改数据
    • sendFile + DMA gather copy:用户程序无法修改数据,需要硬件支持,仅适用于将数据从文件拷贝到socket套接字的传输过程
    • splice:用户程序无法修改数据

  

  

  

  

  

  • Java NIO
    • FileChanel:native方法transferTo0(),依赖底层sendfile()方法
    • MappedByteBuffer:基于内存映射mmap实现
  • Netty
    • 在用户态层面,偏向数据优化操作
    • CompositeByteBuf, wrap, slice:数据优化,减少不必要copy
    • FileRegion:基于transferTo()
  • RocketMQ:mmap+write,适用于小文件持久化和传输
  • kafka
    • 存储:Producer产生数据持久化到broker,采用mmap文件映射,实现顺序快速写入
    • 读取:Customer从broker读取数据,采用sendfile将磁盘文件读到OS缓冲区,转到socket buffer进行网络发送  

五种 I/O 模型

  • 调用马上返回就是非阻塞
  • 存在阻塞就是同步
  • linux下异步I/O(aio)不成熟
  • 1--socket
  • 3--nio

Java NIO

  • Buffer:内存中一块,可将数据写入
    • position:下一个可以操作的位置(读写模式需手动切换(flip))
    • limit:最大能写入的数据
    • capacity:容量
  • Channel:数据的来源或写入目的地(TCP、文件等)
    • 读操作:Channel到Buffer
    • 写操作:Buffer到Channel
  • Selector:连接的注册机制,检查一个或多个Channel的状态是否可读/可写,以实现单线程管理多个Channel
    • Channel注册到Selector,选择关注的事件
    • 类比:售票员报站,叫醒乘客下车
  • NIO的缺点
    • 对于并发度不高(<1000),局域网环境下不如BIO
    • 基于各OS平台的IO系统实现
    • 离散的事件驱动模型,编程困难
  • NIO库
    • C:Libev,Libevent
    • Java:Netty

   

Reactor模型

  • Reactor模型 = IO多路复用+线程池
  • 角色
    • Reactor:负责监听和分配事件,将IO事件分派给对应的Handler
    • Acceptor:处理客户端新连接,并分派请求到处理器中
    • Handler:与事件绑定,执行非阻塞读/写任务,完成channel读入,业务处理逻辑,结果写入channel
  • Douglas(1995)
    • Handle:事件处理器,相当于Channel
    • SED:同步事件多路复用分离器,相当于Selector
    • ID:分发器,相当于EventLoop

  •  Doug Lea(2000)
    • 按Reactor数量和处理资源池线程的数量不同可分为
      • 单线程Reactor:一个selector,压力太大,无法充分利用资源
      • 多线程Reactor:Reactor负责接收,线程池负责计算
      • 主从Reactor:一个Reactor负责接收连接,一个Reactor负责读写,线程池负责计算

 

 

 

Proactor模型

  • 属于异步IO,需要操作系统支持

Netty

  • 介绍
    • 异步、基于事件驱动的网络应用框架
    • netty中所有的IO操作都是异步的,用户使用Channel进行IO操作,会立即返回ChannelFuture,IO任务提交给底层的Nio处理
    • 主要针对TCP协议下,面向Clinent端的高并发应用,或P2P场景下的大量数据持续传输应用
  • 应用
    • 互联网:分布式系统中,各节点需要远程服务调用,采用Netty作为RPC框架的基础通信组件
    • 游戏:基于TCP/UDP/HTTP协议栈,定制开发私有协议栈;地图服务器间通信
    • 大数据:Hadoop通信组件Avro的RPC框架采用Netty进行跨节点通信

  • 组件
    • Transport Channel:对应NIO中Channel
    • EventLoop:对应NIO中while循环
    • ChannelHandler/ChannelPipeline:对应NIO中handleRead/handleWrite,实现业务逻辑
    • ByteBuf:对应NIO中ByteBuffer
    • Bootstrap/ServerBootstrap:对应NIO中Selector、Channel等的创建、配置、启动等
  • 分工
    • bossGroup负责接客,workerGroup负责干活(银行的大堂经理和柜台员工)
    • worker是多线程的,每个线程对应一个eventLoop,每个eventLoop对应一个selector,可注册多个channel
    • 每个EventLoopGroup对应一个线程池,包含多个EventLoop,多个EventLoop间不交互,每个连接只能注册到一个EventLoop中
    • NioEventLoop是Netty的Reactor线程,采用串行无锁化设计,消息读取、编码、后续Handler执行,都由NioEventLoop执行,整个流程不会进行线程上下文切换,数据没有被并发修改的风险
    • ServerBootstrap中包含Boss EventLoopGroup(负责Accept事件) 和 Worker EventLoopGroup(负责读写事件)
    • ChannelPipline对应一系列ChannelHandler
    • ByteBuf:相比JDK ByteBuffer更加易用,为读/写分别维护单独的指针,不需要flip()进行模式切换,容量自动伸缩,链式调用
    • 每个Channel都有一个ChannelPipline,ChannelPipline是ChannelHandler的容器,可通过动态添加、删除ChannelHandler修改ChannelPipeline,ChannelHandlerContext 表示 ChannelHandler 和 ChannelPipline 间的关联
    • Channel的生命周期,ChannelInBoundHandler的方法
    • 通过ChannelFuture注册IO事件监听,执行成功或失败后自动触发监听事件
  • EventLoop
    • 每个Reactor线程模型包含步骤:连接注册、事件轮询、事件分发、任务处理
    • EventExecutorGroup数组,保存多个EventExecutor
    • EventExecutorGroup收到请求,调用next()获取EventExecutor,再调用executor方法
    • next() 定义选择EventExecutor策略
  • Channel、ChannelHandler、ChannelHandlerContext、ChannelPipeline
    • Channel是对Socket的封装,简化了与Socket进行操作的复杂性
    • 一个连接到达时,Netty 就会注册一个 Channel,然后从 EventLoopGroup 中分配一个 EventLoop 绑定到这个Channel上,在该Channel的整个生命周期中都是由这个绑定的 EventLoop 来服务的
    • netty 网络操作抽象类,使用了Facade模式聚合了基本IO操作及其他功能
    • ChannelHandler属于业务的核心接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline
    • ChannelPipeline是一个Handler 的集合,负责处理和拦截 inbound 或者 outbound 的事件和操作,相当于一个贯穿 Channel 的链
    • 客户端和服务端都有各自的 ChannelPipeline
    • ChannelPipeline中包含入站ChannelInboundHandler和出站ChannelOutboundHandler两种处理器
    • 以客户端为例,数据从客户端发向服务端,该过程称为出站,反之则称为入站
    • 数据入站会由一系列 InBoundHandler 处理,然后再以相反方向的 OutBoundHandler 处理后完成出站
    • 服务端接收到客户端数据后,需要先经过 Decoder 入站处理后,再通过 Encoder 出站通知客户端
    • 客户端和服务端一次完整的请求应答过程可以分为三个步骤:客户端出站(请求数据)、服务端入站(解析数据并执行业务逻辑)、服务端出站(响应结果)
    • 结合ChannelHandler生命周期,在每个回调事件完成后,使用ChannelHandlerContext的fireChannelXXX方法传递给下一个ChannelHandler

 

ChannelHandler

 

chanelHandlerContext

channel生命周期

buffer

  •  Reactor实现
    • 下图对应模式2、3
    • boss单线程,worker线程池,默认创建CPU*2线程

EventLoop

 

参考

彻底理解同步 异步 阻塞 非阻塞

https://www.cnblogs.com/loveer/p/11479249.html

漫谈Java IO之普通IO流与BIO服务器

https://www.cnblogs.com/xing901022/p/8666147.html

Java BIO、NIO与AIO的介绍

https://www.cnblogs.com/wobushitiegan/p/12596351.html

Java核心(五)深入理解BIO、NIO、AIO

http://www.imooc.com/article/265871

BIO和NIO了解多少呢?一起从实践角度重新理解下吧

https://developer.51cto.com/art/201910/604706.htm

理解linux下sendfile()系统调用

https://www.iteye.com/blog/xuliangyong-620694

上下文切换

https://zhuanlan.zhihu.com/p/94321910

JAVA原生NIO问题

https://www.163.com/dy/article/G3ML979D05371TOK.html

零拷贝

https://zhuanlan.zhihu.com/p/78869158?utm_source=wechat_timeline

深入理解Java 8 Lambda

http://zh.lucida.me/blog/java-8-lambdas-insideout-language-features/

https://www.cnblogs.com/haixiang/p/11029639.html

Java NIO

https://blog.csdn.net/weixin_30621919/article/details/97093636

Netty组件关系

https://www.cnblogs.com/duanxz/p/3724247.html

https://www.it610.com/article/1297221044884545536.htm

netty demo

https://blog.csdn.net/eric520zenobia/article/details/99946307

netty自定义任务

https://blog.csdn.net/baidu_39414557/article/details/113058493

netty异步任务和定时任务

https://www.cnblogs.com/zpKang/p/13374238.html

概念辨析

https://mp.weixin.qq.com/s/llNV9hnVQslBcnufQ4gqzg

异步与多线程

https://zhuanlan.zhihu.com/p/350816301

https://www.cnblogs.com/ckym/p/11438497.html

java异步编程

http://ifeve.com/%E4%B8%80%E6%96%87%E5%B8%A6%E4%BD%A0%E5%BD%BB%E5%BA%95%E4%BA%86%E8%A7%A3java%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/

https://www.jianshu.com/p/2086154ae5cb

https://blog.csdn.net/qq_31865983/article/details/106137777

JAVA 语言异步非阻塞设计模式(原理篇)

https://xie.infoq.cn/article/61ec03d27bbaa654df0814012

netty的Future异步回调

https://cloud.tencent.com/developer/article/1582976

netty中的同步与异步

https://blog.csdn.net/weixin_41954254/article/details/106414746

netty EventLoop

https://www.w3cschool.cn/essential_netty_in_action/essential_netty_in_action-acxh28et.html

netty Inbound Outbound

https://www.cnblogs.com/tianzhiliang/p/11739372.html

posted @ 2020-03-29 11:26  cxc1357  阅读(178)  评论(0编辑  收藏  举报