长轮询在配置平台的工程化实践与性能权衡
长轮询在配置平台的应用 1. 配置平台简介 略 2. 长轮询简介 传统的短轮询方式存在一个严重缺陷:程序在每次请求时都会新建一个HTTP请求,然而并不是每次都能返回所需的新数据。当同时发起的请求达到一定数目时,会对服务器造成较大负担。这时我们可以采用长轮询方式解决这个问题。 长轮询的基本思想是在每次客户端发出请求后,服务器检查上次返回的数据与此次请求时的数据之间是否有更新,如果有更新则返回新数据并结束此次连接,否则服务器“hold”住此次连接,直到有新数据时再返回相应。而这种长时间的保持连接可以通过设置一个较大的HTTP timeout实现。在服务端消息推送方面,长轮询有着广泛的应用。 3. 请求模型 3.1 同步请求模型 这是我们日常最常用同步请求模型,所有动作都交给同一个 Tomcat 线程处理,所有动作处理完成,线程才会被释放回线程池。 如果业务需要较长时间处理,那么这个 Tomcat 线程其实一直在被占用,随着请求越来越多,可用 I/O 线程越来越少,直到被耗尽。这时后续请求只能等待空闲 Tomcat 线程,这将会加长了请求执行时间,或者直接被拒绝,客户端会报connect refused异常。 如果客户端不关心返回业务结果,这时我们可以自定义线程池,将请求任务提交给线程池,然后立刻返回。 3.2 异步请求模型 Servlet3 引入异步 Servelt 新特性,可以完美解决上面的需求。 异步 Servelt 执行请求流程: 将请求信息解析为 HttpServletRequest 分发到具体 Servlet 处理,将业务提交给自定义业务线程池,请求立刻返回,Tomcat 线程立刻被释放 当业务线程将任务执行结束,将会将结果转交给 Tomcat 线程 通过 HttpServletResponse 将响应结果返回给等待客户端 引入异步 Servelt3 整体流程如下: 3.3 应用场景 1、增加系统吞吐量 拿Tomcat作为Servlet容器来说,无论是计算型请求还是IO型请求,都是交给Tomcat容器线程来建立连接和负责业务逻辑处理,如果将IO型请求或者RT(响应时间)比较高的请求业务逻辑处理,通过异步请求来实现,可以尽早地释放连接线程,业务逻辑交由业务线程池处理,那么连接线程池可以接收更多的请求,从而提高了系统吞吐量。 2、服务端消息推送 消息推送,对于一些服务端发生变更,需要向客户端发送消息通知的场景,可以通过异步请求来实现。 3.4 异步请求实现 1、 AsyncContext HttpServletRequest#startAsync 获取 AsyncContext 异步上下文对象 使用自定义的业务线程池处理业务逻辑 业务线程处理结束,通过 AsyncContext#complete 返回响应结果 ExecutorService executorService = Executors.newFixedThreadPool(10); @RequestMapping("/hello") public void hello(HttpServletRequest request) { AsyncContext asyncContext = request.startAsync(); // 超时时间 asyncContext.setTimeout(10000); executorService.submit(() -> { try { // 休眠 5s,模拟业务操作 TimeUnit.SECONDS.sleep(5); // 输出响应结果 asyncContext.getResponse().getWriter().println("hello world"); log.info("异步线程处理结束"); } catch (Exception e) { e.printStackTrace(); } finally { asyncContext.complete(); } }); log.info("servlet 线程处理结束"); } 2、 DeferredResult Servlet3.0 提供了异步处理请求的特性,DeferredResult 是Spring基于 Servlet 3.0 对异步请求的支持实现,SpringMVC 3.2 之后引入新的类 DeferredResult,目的是对于请求提供异步处理方式,释放容器连接,支持更多的并发。或者基于它的超时机制来做一些长轮询相关的事情。 ...