Scalable IO in Java

http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

基本上所有的网络处理程序都有以下基本的处理过程:
Read request
Decode request
Process service
Encode reply
Send reply

Classic Service Designs

简单的代码实现:

  1. class Server implements Runnable {
  2. public void run() {
  3. try {
  4. ServerSocket ss = new ServerSocket(PORT);
  5. while (!Thread.interrupted())
  6. new Thread(new Handler(ss.accept())).start(); //创建新线程来handle
  7. // or, single-threaded, or a thread pool
  8. } catch (IOException ex) { /* ... */ }
  9. }
  10.  
  11. static class Handler implements Runnable {
  12. final Socket socket;
  13. Handler(Socket s) { socket = s; }
  14. public void run() {
  15. try {
  16. byte[] input = new byte[MAX_INPUT];
  17. socket.getInputStream().read(input);
  18. byte[] output = process(input);
  19. socket.getOutputStream().write(output);
  20. } catch (IOException ex) { /* ... */ }
  21. }
  22. private byte[] process(byte[] cmd) { /* ... */ }
  23. }
  24. }

对于每一个请求都分发给一个线程,每个线程中都独自处理上面的流程。

这种模型由于IO在阻塞时会一直等待,因此在用户负载增加时,性能下降的非常快。

server导致阻塞的原因:

1、serversocket的accept方法,阻塞等待client连接,直到client连接成功。

2、线程从socket inputstream读入数据,会进入阻塞状态,直到全部数据读完。

3、线程向socket outputstream写入数据,会阻塞直到全部数据写完。

client导致阻塞的原因:

1、client建立连接时会阻塞,直到连接成功。

2、线程从socket输入流读入数据,如果没有足够数据读完会进入阻塞状态,直到有数据或者读到输入流末尾。

3、线程从socket输出流写入数据,直到输出所有数据。

4、socket.setsolinger()设置socket的延迟时间,当socket关闭时,会进入阻塞状态,直到全部数据都发送完或者超时。

改进:采用基于事件驱动的设计,当有事件触发时,才会调用处理器进行数据处理。

Basic Reactor Design

 代码实现:

  1. class Reactor implements Runnable {
  2. final Selector selector;
  3. final ServerSocketChannel serverSocket;
  4. Reactor(int port) throws IOException { //Reactor初始化
  5. selector = Selector.open();
  6. serverSocket = ServerSocketChannel.open();
  7. serverSocket.socket().bind(new InetSocketAddress(port));
  8. serverSocket.configureBlocking(false); //非阻塞
  9. SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT); //分步处理,第一步,接收accept事件
  10. sk.attach(new Acceptor()); //attach callback object, Acceptor
  11. }
  12.  
  13. public void run() {
  14. try {
  15. while (!Thread.interrupted()) {
  16. selector.select();
  17. Set selected = selector.selectedKeys();
  18. Iterator it = selected.iterator();
  19. while (it.hasNext())
  20. dispatch((SelectionKey)(it.next()); //Reactor负责dispatch收到的事件
  21. selected.clear();
  22. }
  23. } catch (IOException ex) { /* ... */ }
  24. }
  25.  
  26. void dispatch(SelectionKey k) {
  27. Runnable r = (Runnable)(k.attachment()); //调用之前注册的callback对象
  28. if (r != null)
  29. r.run();
  30. }
  31.  
  32. class Acceptor implements Runnable { // inner
  33. public void run() {
  34. try {
  35. SocketChannel c = serverSocket.accept();
  36. if (c != null)
  37. new Handler(selector, c);
  38. }
  39. catch(IOException ex) { /* ... */ }
  40. }
  41. }
  42. }
  43.  
  44. final class Handler implements Runnable {
  45. final SocketChannel socket;
  46. final SelectionKey sk;
  47. ByteBuffer input = ByteBuffer.allocate(MAXIN);
  48. ByteBuffer output = ByteBuffer.allocate(MAXOUT);
  49. static final int READING = 0, SENDING = 1;
  50. int state = READING;
  51.  
  52. Handler(Selector sel, SocketChannel c) throws IOException {
  53. socket = c; c.configureBlocking(false);
  54. // Optionally try first read now
  55. sk = socket.register(sel, 0);
  56. sk.attach(this); //将Handler作为callback对象
  57. sk.interestOps(SelectionKey.OP_READ); //第二步,接收Read事件
  58. sel.wakeup();
  59. }
  60. boolean inputIsComplete() { /* ... */ }
  61. boolean outputIsComplete() { /* ... */ }
  62. void process() { /* ... */ }
  63.  
  64. public void run() {
  65. try {
  66. if (state == READING) read();
  67. else if (state == SENDING) send();
  68. } catch (IOException ex) { /* ... */ }
  69. }
  70.  
  71. void read() throws IOException {
  72. socket.read(input);
  73. if (inputIsComplete()) {
  74. process();
  75. state = SENDING;
  76. // Normally also do first write now
  77. sk.interestOps(SelectionKey.OP_WRITE); //第三步,接收write事件
  78. }
  79. }
  80. void send() throws IOException {
  81. socket.write(output);
  82. if (outputIsComplete()) sk.cancel(); //write完就结束了, 关闭select key
  83. }
  84. }
  85.  
  86. //上面 的实现用Handler来同时处理Read和Write事件, 所以里面出现状态判断
  87. //我们可以用State-Object pattern来更优雅的实现
  88. class Handler { // ...
  89. public void run() { // initial state is reader
  90. socket.read(input);
  91. if (inputIsComplete()) {
  92. process();
  93. sk.attach(new Sender()); //状态迁移, Read后变成write, 用Sender作为新的callback对象
  94. sk.interest(SelectionKey.OP_WRITE);
  95. sk.selector().wakeup();
  96. }
  97. }
  98. class Sender implements Runnable {
  99. public void run(){ // ...
  100. socket.write(output);
  101. if (outputIsComplete()) sk.cancel();
  102. }
  103. }
  104. }

这里用到了Reactor模式。

关于Reactor模式的一些概念:

Reactor:负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理。

Handler:负责处理非阻塞的行为,标识系统管理的资源;同时将handler与事件绑定。

Reactor为单个线程,需要处理accept连接,同时发送请求到处理器中。

由于只有单个线程,所以处理器中的业务需要能够快速处理完。

改进:使用多线程处理业务逻辑。

Worker Thread Pools

 参考代码:

  1. class Handler implements Runnable {
  2. // uses util.concurrent thread pool
  3. static PooledExecutor pool = new PooledExecutor(...);
  4. static final int PROCESSING = 3;
  5. // ...
  6. synchronized void read() { // ...
  7. socket.read(input);
  8. if (inputIsComplete()) {
  9. state = PROCESSING;
  10. pool.execute(new Processer()); //使用线程pool异步执行
  11. }
  12. }
  13.  
  14. synchronized void processAndHandOff() {
  15. process();
  16. state = SENDING; // or rebind attachment
  17. sk.interest(SelectionKey.OP_WRITE); //process完,开始等待write事件
  18. }
  19.  
  20. class Processer implements Runnable {
  21. public void run() { processAndHandOff(); }
  22. }
  23. }

将处理器的执行放入线程池,多线程进行业务处理。但Reactor仍为单个线程。

继续改进:对于多个CPU的机器,为充分利用系统资源,将Reactor拆分为两部分。

Using Multiple Reactors

参考代码:

  1. Selector[] selectors; //subReactors集合, 一个selector代表一个subReactor
  2. int next = 0;
  3. class Acceptor { // ...
  4. public synchronized void run() { ...
  5. Socket connection = serverSocket.accept(); //主selector负责accept
  6. if (connection != null)
  7. new Handler(selectors[next], connection); //选个subReactor去负责接收到的connection
  8. if (++next == selectors.length) next = 0;
  9. }
  10. }

mainReactor负责监听连接,accept连接给subReactor处理,为什么要单独分一个Reactor来处理监听呢?因为像TCP这样需要经过3次握手才能建立连接,这个建立连接的过程也是要耗时间和资源的,单独分一个Reactor来处理,可以提高性能。

参考:

http://www.cnblogs.com/fxjwind/p/3363329.html

《Scalable IO in Java》笔记的更多相关文章

  1. HTML+CSS笔记 CSS笔记集合

    HTML+CSS笔记 表格,超链接,图片,表单 涉及内容:表格,超链接,图片,表单 HTML+CSS笔记 CSS入门 涉及内容:简介,优势,语法说明,代码注释,CSS样式位置,不同样式优先级,选择器, ...

  2. CSS笔记--选择器

    CSS笔记--选择器 mate的使用 <meta charset="UTF-8"> <title>Document</title> <me ...

  3. HTML+CSS笔记 CSS中级 一些小技巧

    水平居中 行内元素的水平居中 </a></li> <li><a href="#">2</a></li> &l ...

  4. HTML+CSS笔记 CSS中级 颜色&长度值

    颜色值 在网页中的颜色设置是非常重要,有字体颜色(color).背景颜色(background-color).边框颜色(border)等,设置颜色的方法也有很多种: 1.英文命令颜色 语法: p{co ...

  5. HTML+CSS笔记 CSS中级 缩写入门

    盒子模型代码简写 回忆盒模型时外边距(margin).内边距(padding)和边框(border)设置上下左右四个方向的边距是按照顺时针方向设置的:上右下左. 语法: margin:10px 15p ...

  6. HTML+CSS笔记 CSS进阶再续

    CSS的布局模型 清楚了CSS 盒模型的基本概念. 盒模型类型, 我们就可以深入探讨网页布局的基本模型了.布局模型与盒模型一样都是 CSS 最基本. 最核心的概念. 但布局模型是建立在盒模型基础之上, ...

  7. HTML+CSS笔记 CSS进阶续集

    元素分类 在CSS中,html中的标签元素大体被分为三种不同的类型:块状元素.内联元素(又叫行内元素)和内联块状元素. 常用的块状元素有: <div>.<p>.<h1&g ...

  8. HTML+CSS笔记 CSS进阶

    文字排版 字体 我们可以使用css样式为网页中的文字设置字体.字号.颜色等样式属性. 语法: body{font-family:"宋体";} 这里注意不要设置不常用的字体,因为如果 ...

  9. HTML+CSS笔记 CSS入门续集

    继承 CSS的某些样式是具有继承性的,那么什么是继承呢?继承是一种规则,它允许样式不仅应用于某个特定html标签元素,而且应用于其后代(标签). 语法: p{color:red;} <p> ...

  10. HTML+CSS笔记 CSS入门

    简介: </span>年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的<span>脚本解释程序</span>,作为ABC语言的一种继承. & ...

随机推荐

  1. fedora自带OpenJDK,所以如果安装官方的JDK的话要先删除OpenJDK

    1:rpm -qa|grep jdk 查看当前的jdk情况. 2:yum -y remove java java-1.7.0-openjdk* 卸载openjdk,这个过程中因为依赖原因可能会卸载一些 ...

  2. RabbitMQ学习系列(五): RPC 远程过程调用

    前面讲过一些RabbitMQ的安装和用法,也说了说RabbitMQ在一般的业务场景下如何使用.不知道的可以看我前面的博客,http://www.cnblogs.com/zhangweizhong/ca ...

  3. photoshop常见抠图方法

    1.多边形套索:这种工具是用于抠图的边界比较平直,但颜色比较复杂类的图像,它也是最笨最无奈的方法,只能是利用鼠标一点一点去点击抠选.2.磁性套索工具:分为三种:套索,多边形,磁性.这类工具一般只用于边 ...

  4. centos环境搭建

    1.php -v 与phpinfo();    不符,查看centos是否有自带的php:更改centos环境变量 /etc/profile,source /etc/profile生效: 2.pecl ...

  5. npm 发布到远程资源库

    一.npm 发布到远程资源库 1.创建package.json Package.json 属性说明 name - 包名. version - 包的版本号. description - 包的描述. ho ...

  6. 学习MySQL之多表操作(三)

    ##多表查询 ##使用数据库 mytest USE mytest; ##删除,并重新创建表 t_dept DROP TABLE t_dept; CREATE TABLE t_dept ( deptno ...

  7. 如何修改geditor的配置文件 -好像geditor没有文本格式的配置文件? 要使用dconf-editor来配置- geditor自己配置编码格式

    好像geditor没有文本格式的配置文件? 好像是通过一个程序, 叫 dconf-editor 来配置geditor的? 以前是通过gconf-editor来配置的, 但是gconf-editor的配 ...

  8. logback日志写入数据库(mysql)配置

    如题  建议将日志级别设置为ERROR.这样可以避免存储过多的数据到数据中. 1  logback 配置文件(如下) <?xml version="1.0" encoding ...

  9. 第3月第19天 cxx_destruct dispatch_get_main_queue()死锁

    1. http://blog.jobbole.com/65028/ 2. - (void)viewDidLoad { [super viewDidLoad]; NSLog("); dispa ...

  10. 他山之石——vs2013 安装与部署及程序打包

    C#打包需要这个:InstallShield 2013 Limited Edition for Visual Studio  .下载地址: InstallShield 2013 Limited Edi ...