记得大学的《网络工程》有一个课后作业:用Java实现一个web服务器,当时想的是为了提高吞吐量,可以用多线程实现,即对于每一个客户端请求连接,都启动一个线程来处理,处理逻辑大概就是从里面读取http请求,解析执行请求,执行完把写回,线程结束销毁。用多线程实现确实提高了吞吐量,但是也有一些问题:1)不断的线程创建销毁需要耗费大量的开销;2)线程之间的切换需要耗费很多开销;3)Java有创建最多线程数量的限制,具体可参考
。
如果要优化上面的实现,可以怎么做呢?其实参考的发展进程,就是优化的方向。
处理用户请求的入口组件叫做,其有两个主要的实现:BIO( io)和NIO(non- io)。
简单讲,BIO的实现就是对上面多线程版本的一个改进,主要点在于把“每来一个连接启动一个线程处理”改成“每来一个连接都提交给线程池处理”。虽然线程池根据不同的配置,其工作行为会有所不同,但一般来讲,使用线程池的原则是:只需创建少量的线程就可以完成大量任务的执行,由于同时至多只有固定量的线程执行,剩余的任务会被放进queue里面缓冲起来,从这个角度看,这是一个典型的生产者-消费者模型。回到 BIO,不断的接收连接,然后提交给线程池执行,就是生产者;线程池的每一个线程就是消费者,负责处理请求。
由于连接是长连接,连接的创建销毁也是很耗资源的,于是http协议增加了一个keep-alive ,这个的意思是提示服务器端,在返回http 之后,不要断开,继续处理后续http请求,这样做的目的就是为了提高资源的可重用性。那么,对于 BIO的实现,在keep-alive场景下,会有什么问题呢?如果一个线程处理的需要保持keep-alive,其在执行完一个http请求之后,需要阻塞在那里以等待下一个http请求,不能马上结束(直到);在某些情况下,这样就可能存在大量的阻塞线程,新的连接不能被处理。
基于此,NIO就可以解决这个问题。NIO和BIO在请求处理部分的实现是一致的,都是基于线程池;不同的地方是:NIO的基于jdk nio实现,在收到一个连接之后,会把注册到的上面,当有数据可读时,就把此连接提交给线程池处理。回到上面keep-alive的场景,当一个线程处理完一个http请求之后,就可以马上结束,当前连接则回到继续监听接下来的http请求。所以,基于NIO的执行线程就不会出现基于BIO的阻塞情况。
NIO的核心在于,可以识别到已经ready的连接和没有ready的连接;在之前的一篇多线程文章(对比Java和.NET多线程编程)里面提到过,jdk的 API有一个类,就有点类似于nio的原理。
由于NIO天生的优势,从8.0版本开始就把NIO设成默认的,而从8.5版本开始直接就把BIO去掉了。
在的官网有下面一段关于如何高并发处理请求的描述:
Each a for the of that . If more are than can be by the , will be up to the (the value of the ). If still more are , they are up the by the , up to the (the value of the ). Any will ” ” , until are to them.
个人觉得其没有反映出这个参数的作用,所以应该是:如果小于,最大创建的线程数就是的值,最大连接数也是的值;但是如果大于,最大创建的线程数就是的值,最大连接数则是的值。
由于BIO和NIO底层实现的区别,配置的值也需要区别考虑,这在的默认值中就有所体现(对于BIO,的默认值是的值;而对于NIO,的默认值则是10000):
上面有提到,接收处理请求的过程其实就是一个生产者-消费者模型,影响高并发的配置也可以首先分别从这两个方面考虑:
生产者
消费者
Queue
小结一下:
本文分享自微信公众号 – 天马行空布鲁斯()。
———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,永久会员只需109元,全站资源免费下载 点击查看详情
站 长 微 信: nanadh666