Hello World
- 让我们从最简单的单线程阻塞模型开始
ServerSocket server = new ServerSocket(8080);
Socket socket = server.accept();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from client: " + sb);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello World".getBytes("UTF-8"));
outputStream.close();
inputStream.close();
socket.close();
server.close();
-
这个初始版本只能单线程处理,其中的IO会堵塞,性能很差
单线程.jpg
tomcat
- tomcat是我们比较熟悉的传统web容器。提供了多线程的线程模型和NIO多路复用
- 用一个Acceptor线程统一处理连接
- 连上后交给Poller处理底层协议和一般性的IO,Poller线程数为CPU个数
- Poller线程处理完底层的协议之后把servlet相关的请求交给worker处理, worker线程数默认是200个
- 但是tomcat的异步处理只是占一部分, 并不彻底
tomcat io 对比
tomcat_io.jpg
- 另外servlet模型本身对异步的支持到3.1才开始。按照我的理解servlet3.1本质上是提供了脱离tomcat线程的回调语法。[这篇文件写得极好](https://www.cnblogs.com/mianteno/p/10780257.html)
netty
- netty提供了更高效率的线程模型。当然他也使用了DirectBuffer, JCTools的高性能无锁队列。这里只介绍线程模型:
- EventLoop类似于tomcat中的Poller,主要处理连接之外的全部IO。线程数是CPU*2
- 另外Eventloop也会处理协议和业务回调。参考源码:NIOEventLoop#run
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys(); // 处理IO
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
// 用相同的时间处理回调业务
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
- IO处理和任务处理的默认比例是1:1
- Netty这样做有几个好处
- 使用较少的线程意味着减少上下文切换
- 使用较少内存,java每个线程栈默认1M开销
- 一个EventLoop绑定多个Channel和对应的业务处理。从channel的角度看是单线程的,不存在线程切换,不需要处理并发问题。
- 当然Netty的线程处理有个大前提。不能block Eventloop!!!
- 但是传统的业务处理都是阻塞型的,必须开另外的线程池处理,这就和tomcat比优势不明显了
vert.x
- vert.x是对netty的高级别封装,其核心还是EventLoop
- 他通过全异步的方式使得IO全异步处理而通过少量线程性能最大化
- 这个例子里通过Vert.x的EventLoop接管http/redis的IO和回调
- 理论上所有的代码都可以异步,vert.x提供了大量的常用框架的异步IO适配。
- 对于不能或不方便的阻塞代码, 通过worker线程池处理,幷提供异步回调接口(例子中的mysql调用)
网友评论