导读
在前面的小节中,我们对于长轮询有了一个基本的认知,那么具体长轮询在代码上怎么实现呢?这一节我们会使用Servlet的异步处理。
具体点来说,我们会使用Spring Boot构建一个基本框架,然后使用Servlet的异步处理来实现长轮询。
长轮询系列:
(1)✅《什么是轮询、长轮询、长连接一篇文章让你不在懵懂》
(2)✅《Spring Boot使用Servlet居然也可以实现长轮询》
(3)「待定」《Spring Boot使用DeferredResult实现长轮询》
(4)「待拟定」…
这一节我们先来看看《Spring Boot使用Servlet居然也可以实现长轮询》。
一、悟纤十万个疑问❓
悟纤:师傅,有点疑问,想问下。
师傅:有问题,不错,说明你思考了。
悟纤:同步的Servlet是否可以实现长轮询?
师傅:可以,我们在前一节讲过了长轮询就是hold住连接了。可能有些人对于hold住连接不是很理解。Hold的意思有持有、抓住、拿着… ,那么hold住连接,就是一直持有连接的意思了。也就是对于对于客户端和服务端一直保持着连接,至于如何一直保持连接呢?往下看就懂了。所以对于长轮询只要能够Hold住连接就可以了跟是否是异步实现没有关系。
悟纤:既然同步的Servlet可以实现长轮询,那为啥不使用同步的方式呐?
师傅:占用了容器的线程了,比如容器支持100个线程并发,那么都是长轮询最多就支持100的连接了,再加上我们还有其它的业务处理需要建立连接,要是这样子的系统基本不可用了。
悟纤:为什么用Servlet异步处理实现长轮询?
师傅:这样子就能够释放掉容器的线程,开启新的线程以此来提高Web容器的响应能力。
悟纤:最后一个问题哦,师傅看你这里要使用Spring Boot要使用来实现,这是Servlet的异步处理和Spring Boot关系有关系吗?
师傅:这里不要误解了哦,Servlet的异步处理是Servlet3.0本身就具备的处理能力,和使用什么框架无关哦,你使用java web构建一个普通的web项目也是可以实现的,这里使用Spring Boot是因为写例子的时候比较方便,比较快速。
悟纤:师傅,你是我的偶像呢,那我没啥问题了,可以开始我们的快乐之旅了。
二、为什么需要异步处理请求
在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用。在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。
Servlet 3.0 开始提供了AsyncContext用来支持异步处理请求,那么异步处理请求到底能够带来哪些好处?
Web容器一般来说处理请求的方式是:为每个request分配一个thread。我们都知道thread的创建不是没有代价的,Web容器的thread pool都是有上限的。
那么一个很容易预见的问题就是,在高负载情况下,thread pool都被占着了,那么后续的request就只能等待,如果运气不好客户端会报等待超时的错误。
在AsyncContext出现之前,解决这个问题的办法就是扩充Web容器的thread pool。
但是这样依然有一个问题,考虑以下场景:
有一个web容器,线程池大小200。有一个web app,它有两个servlet,Servlet-A处理单个请求的时间是10s,Servlet-B处理单个请求的时间是1s。
现在遇到了高负载,有超过200个request到Servlet-A,如果这个时候请求Servlet-B就会等待,因为所有HTTP thread都已经被Servlet-A占用了。
这个时候工程师发现了问题,扩展了线程池大小到400,但是负载依然持续走高,现在有400个request到Servlet-A,Servlet-B依然无法响应。
看到问题了没有,因为HTTP thread和Worker thread耦合在了一起(就是同一个thread),所以导致了当大量request到一个耗时操作时,就会将HTTP thread占满,导致整个Web容器就会无法响应。
但是如果使用AsyncContext,我们就可以将耗时的操作交给另一个thread去做,这样HTTP thread就被释放出来了,可以去处理其他请求了。
三、Servlet异步处理请求
3.1 例子说明
接下里我们会使用一个小栗子来演示使用Servelt的异步处理来实现长轮询。
对于这个例子先总体的说明下:
(1)有一个页面会使用ajax定时的请求后台,5秒一请求,看是否有新的信息发布。
(2)后端接收到请求之后会使用Servlet的异步处理请求,如果此时没有新的信息,那么使用while(true)的方式hold住连接。
(3)打开新的一个窗口,调用发布新的消息的请求发布新消息。
(4)此时ajax定时请求的页面,应该会及时的显示新的信息。
3.2 环境说明
(1)OS:Mac OS
(2)开发工具:IntelliJ Idea
(3)JDK:1.8
(4)Spring Boot:2.6.1
。。。。。。。。。。。。。。。。。
版权原因,完整文章,请参考如下:Spring Boot使用Servlet居然也可以实现长轮询,敲了5年代码,我居然不知道 - 第413篇