前言
《Scalable IO in Java》 是大师Doug Lea的一篇经典文章,本文是看了网上的一篇译文做的学习笔记
网络服务
在一般的网络或者分布式服务系统中,大都具备相同的处理流程
- 读取请求数据
- 对请求数据进行解码
- 对数据进行业务处理
- 对回复数据进行编码
- 发送编码后的数据
当然,在实际的应用中,每一步的处理效率都是不同的,例如其中可能涉及到的xml解析、文件传输、web页面的加载、计算服务等等
传统的服务设计模式
每一个连接会开启一个新的线程处理
这种就是传统的BIO,这种线程模型有如下缺点
- 每个客户端的请求都需要创建单独的线程处理,当并发数较大时,服务端需要创建大量的线程,占用大量的资源
- 连接建立后,如果当前线程没有数据可读,线程会阻塞在read操作上,造成资源浪费
构建高性能可伸缩的IO服务
在构建高性能可伸缩IO服务的过程中,我们希望达成以下的目标:
- 能够在海量负载连接情况下优雅降级
- 能够随着硬件资源的增加,性能持续改进
- 具备低延迟、高吞吐量、可调节的服务质量等特点
而分发处理就是实现上述目标的一个最佳方式
分发模式
分发模式具有以下几个机制:
- 将一个完整处理过程分解为一个个细小的任务
- 每个任务执行相关的动作且不产生阻塞
- 在任务执行状态被触发时才会被执行
在一般的服务开发当中,IO事件通常被当做任务执行状态的触发器使用,在hander处理过程中主要针对的也就是IO事件;
java.nio包就很好的实现了上述的机制:
- 非阻塞的读和写
- 通过感知IO事件分发任务的执行
所以结合一系列基于事件驱动模式的设计,给高性能IO服务的架构与设计带来丰富的可扩展性;
Reactor模式
Reactor模式也称为反应器模式
单线程模式
在这张图中,有三个对象,分别是Reactor、acceptor、handler(read、decode、compute、encode、send,具体干活的)
- Reactor负责监听和分发事件
- acceptor负责获取连接
- handler负责处理业务
这个图的处理流程是
- acceptor会在服务器启动时将相关的事件注册到组件中,用于处理连接
- Reactor对象会监听事件,收到事件后根据事件的类型分发给对应的对象,如果是连接事件,分发给acceptor,如果不是连接事件,则分发给对应handler进行具体的处理
这个模式全部工作都在同一个线程中进行,会有几个问题
- 假如有某一个handler执行时间过长,会拖慢整个Reactor的执行速度,无法响应其它客户端
- 无法利用多cpu的优势,在连接数过多时,单线程会处理不过来,性能上无法支撑
多线程模式
这个模型与单线程模型的不一样之处在于增加了Worker线程处理非IO的操作,handler对象只负责数据的接受和发送,这样做可以充分利用多cpu的优势,但是也带来了几个问题
- 共享资源的竞争问题,缓冲区作为共享空间,不同线程如何保证缓冲区的线程安全
- 任务之间的协调与控制
- 只使用一个线程处理连接和响应,在连接数过多时,仍然会有性能问题
基于多个反应器的多线程模式
这个模式跟多线程模式的不一样之处在于将Reactor基于功能进行了拆分,mainReactor只负责连接,subReactor负责后续的业务处理
这个图的处理流程是
- mainReactor负责监听连接事件,在连接建立后,会将创建的SocketChannel注册到subReactor上
- 如果该连接有新的事件产生,subReactor会调用已注册在当前连接上的handler进行响应
- subReactor处理整个业务流程
netty框架正是采用了这个模式