ServletRequest startAsync()的有用性有限

使容器调度线程(可能从托管线程池中)运行指定的Runnable
提醒大家, AsyncContext是Servlet 3.0规范中定义的一种标准方式,用于异步处理HTTP请求。 基本上,HTTP请求不再绑定到HTTP线程,这使我们以后可以使用更少的线程来处理它。 事实证明,该规范提供了一个API,用于处理其他不同线程池中的异步线程。 首先,我们将了解该功能在Tomcat和Jetty中是如何被完全破坏和无用的,然后我们将讨论为什么该功能的用途普遍存在疑问。
我们的测试servlet只会在给定的时间内睡眠。 在正常情况下,这是可伸缩性的杀手,因为即使休眠的Servlet不会消耗CPU,但是与该特定请求绑定的休眠的HTTP线程也会消耗内存,并且其他传入请求都无法使用该线程。 在我们的测试设置中,我将HTTP工作线程的数量限制为10个,这意味着即使应用程序本身几乎完全处于空闲状态,也只有10个并发请求完全阻塞了该应用程序(外部没有响应)。 显然,睡眠是可扩展性的敌人。
@WebServlet(urlPatterns = Array("/*"))
class SlowServlet extends HttpServlet with Logging {

  protected override def doGet(req: HttpServletRequest, resp: HttpServletResponse) {
    logger.info("Request received")
    val sleepParam = Option(req.getParameter("sleep")) map {_.toLong}
    TimeUnit.MILLISECONDS.sleep(sleepParam getOrElse 10)
    logger.info("Request done")
  }
}
对这段代码进行基准测试可以发现,只要并发连接数低于HTTP线程数,平均响应时间就会接近sleep参数。 毫不奇怪,一旦我们超过HTTP线程数,响应时间就会开始增加。 第十一连接必须等待任何其他请求完成并释放工作线程。 当并发级别超过100时,Tomcat开始断开连接-太多的客户端已排队。
那么花哨的AsyncContext.start()方法又如何呢(不要与ServletRequest.startAsync()混淆)? 根据JavaDoc,我可以提交任何Runnable ,并且容器将使用某些托管线程池来处理它。 这将在一定程度上有所帮助,因为我不再阻止HTTP工作线程(但仍使用servlet容器中某个位置的另一个线程)。 快速切换到异步servlet:
@WebServlet(urlPatterns = Array("/*"), asyncSupported = true)
class SlowServlet extends HttpServlet with Logging {

  protected override def doGet(req: HttpServletRequest, resp: HttpServletResponse) {
    logger.info("Request received")
    val asyncContext = req.startAsync()
    asyncContext.setTimeout(TimeUnit.MINUTES.toMillis(10))
    asyncContext.start(new Runnable() {
      def run() {
        logger.info("Handling request")
        val sleepParam = Option(req.getParameter("sleep")) map {_.toLong}
        TimeUnit.MILLISECONDS.sleep(sleepParam getOrElse 10)
        logger.info("Request done")
        asyncContext.complete()
      }
    })
  }
}
我们首先启用异步处理,然后简单地将sleep()移至Runnable并希望移至其他线程池中,从而释放HTTP线程池。 快速压力测试揭示了一些出乎意料的结果(此处:响应时间与并发连接数):
猜猜是什么,响应时间与根本没有异步支持的响应时间完全相同 (!)。仔细检查后,我发现当调用AsyncContext.start() ,Tomcat将给定的任务提交回……HTTP工作线程池,即用于所有HTTP请求! 基本上,这意味着我们发布了一个HTTP线程,只是为了在稍后的一毫秒内使用(甚至可能是同一线程)。 在Tomcat中调用AsyncContext.start()绝对没有好处。 我不知道这是错误还是功能。 一方面,这显然不是API设计人员想要的。 假定servlet容器管理单独的独立线程池,因此HTTP工作线程池仍然可用。 我的意思是,异步处理的全部目的是逃避HTTP池。 Tomcat假装将我们的工作委托给另一个线程,而它仍然使用原始的工作线程池。
那么,为什么我认为这是一个功能? 因为Jetty以完全相同的方式“破坏”了……无论它是按设计运行还是仅是不良的API实现,因此在Tomcat和Jetty中使用AsyncContext.start()都是没有意义的,并且只会不必要地使代码复杂化。 它不会给您任何好处,该应用程序在高负载下的工作原理完全相同,就好像根本没有异步逻辑一样。
但是,如何在正确的实现(例如IBM WAS)上使用此API功能呢? 更好,但API仍然没有像在扩展性方面给我们带来太多好处。 再次说明:异步处理的全部要点是能够将HTTP请求与基础线程分离,最好通过使用同一线程处理多个连接来实现。
AsyncContext.start()将在单独的线程池中运行提供的Runnable 。 您的应用程序仍然可以响应,可以处理普通的请求,而您决定异步处理的长期运行的请求则在单独的线程池中处理。 更好的是,不幸的是线程池和每个连接线程的成语仍然是瓶颈。 对于JVM,启动什么类型的线程都没有关系–它们仍然占用内存。 因此,我们不再阻塞HTTP工作线程,但就我们可以支持的并发长期运行任务而言,我们的应用程序具有更大的可伸缩性。
在这个带有休眠servlet的简单,不现实的示例中,实际上,我们可以使用Servlet 3.0异步支持(只有一个额外的线程)并且不使用AsyncContext.start()来支持数千个并发(等待)连接。 你知道如何? 提示: ScheduledExecutorService
后记:斯卡拉善良
我差点忘了。 尽管示例是用Scala编写的,但我还没有使用任何出色的语言功能。 这是一个:隐式转换。 使它在您的范围内可用:
implicit def blockToRunnable[T](block: => T) = new Runnable {
 def run() {
  block
 }
}
突然之间,您可以使用代码块来代替手动和显式实例化Runnable
asyncContext start {
 logger.info("Handling request")
 val sleepParam = Option(req.getParameter("sleep")) map { _.toLong}
 TimeUnit.MILLISECONDS.sleep(sleepParam getOrElse 10)
 logger.info("Request done")
 asyncContext.complete()
}
甜!
参考: Javax servlet 社区的 JCG合作伙伴 Tomasz Nurkiewicz提供的javax.servlet.ServletRequest.startAsync()用途有限

翻译自: https://www.javacodegeeks.com/2012/05/servletrequest-startasync-limited.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ServletRequestJava Servlet API中的一个接口,它是所有Servlet请求对象的超类。ServletRequest提供了一些方法,用于获取HTTP请求的属、参数、输入流等信息。开发人员可以利用ServletRequest来处理用户的请求,并根据请求的不同参数和内容来生成响应结果。 除了HttpServletRequest之外,还有其他的子类实现了ServletRequest接口,例如: - javax.servlet.AsyncContext - javax.servlet.ServletRequestWrapper - javax.servlet.http.HttpServletRequest - javax.servlet.http.HttpServletRequestWrapper - javax.websocket.Session 以下是一些常用的ServletRequest的方法: - `getAttribute(String name)`:获取请求属的值,参数name指定属名。 - `getAttributeNames()`:获取所有请求属名的枚举类型。 - `getCharacterEncoding()`:获取请求的字符编码。 - `getContentLength()`:获取请求体的长度。 - `getContentType()`:获取请求体的类型。 - `getInputStream()`:获取请求体的输入流。 - `getParameter(String name)`:获取请求参数的值,参数名由name指定。 - `getParameterMap()`:获取请求参数的Map对象,以参数名作为键,参数值作为值。 - `getProtocol()`:获取请求所使用的协议,如HTTP/1.1。 - `getReader()`:获取请求体的字符输入流。 - `getRemoteAddr()`:获取客户端的IP地址。 - `getScheme()`:获取请求所使用的协议类型,如http、https。 - `getServerName()`:获取服务器的名称。 - `getServerPort()`:获取服务器的端口号。 - `getLocale()`:获取客户端的语言环境。 - `isSecure()`:判断是否是安全传输,如HTTPS。 - `removeAttribute(String name)`:删除请求属,参数name指定属名。 - `setAttribute(String name, Object value)`:设置请求属,参数name指定属名,value指定属值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值