如何使用异步Servlet来提高性能

这篇文章将描述一种性能优化技术,适用于与现代Web应用程序相关的常见问题。 如今的应用程序不再只是被动地等待浏览器发起请求,而是希望自己开始通信。 一个典型的示例可能涉及聊天应用程序,拍卖行等–共同点是以下事实:大多数时间与浏览器的连接处于空闲状态并等待某个事件被触发。

这类应用程序已经开发出自己的问题类别,尤其是在面对重负载时。 症状包括线程不足,用户交互受苦,陈旧性问题等。

根据最近在加载此类应用程序方面的经验,我认为现在是演示简单解决方案的好时机。 在Servlet API 3.0实现成为主流之后,该解决方案就变得真正简单,标准化和优雅。

但是在进入演示解决方案之前,我们应该更详细地了解问题。 对于我们的读者–在某些源代码的帮助下,比解释问题更容易的是:

@WebServlet(urlPatterns = "/BlockingServlet")
public class BlockingServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {try {long start = System.currentTimeMillis();Thread.sleep(2000);String name = Thread.currentThread().getName();long duration = System.currentTimeMillis() - start;response.getWriter().printf("Thread %s completed the task in %d ms.", name, duration);} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);}}

上面的servlet是上面描述的应用程序看起来像的示例:

  • 请求到达,宣布有兴趣监视某些事件
  • 线程被阻塞,直到事件到达
  • 收到事件后,响应将被编译并发送回客户端

为了简单起见,我们将等待部分替换为对Thread.sleep()的调用。

现在,您可能会认为这是一个完全正常的servlet。 在许多情况下,您是完全正确的–在应用程序面临大量负载之前,代码没有错。

为了模拟此负载,我在JMeter的帮助下创建了一个相当简单的测试,在该测试中,我启动了2,000个线程,每个线程运行10次迭代,以对/ BlockedServlet的请求轰炸应用程序。 在现成的Tomcat 7.0.42上使用已部署的servlet运行测试,结果如下:

  • 平均响应时间:19,324毫秒
  • 最小响应时间:2,000毫秒
  • 最大响应时间:21,869 ms
  • 吞吐量:97个请求/秒

Tomcat的默认配置有200个工作线程,再加上将模拟工作替换为2,000ms睡眠周期这一事实很好地说明了最小和最大响应时间– 200秒中的每个线程应该能够完成100个睡眠周期,每个2秒。 最重要的是,加上上下文切换成本,达到的97个请求/秒的吞吐量非常接近我们的预期。

对于99.9%的应用程序而言,吞吐量本身看起来不会太差。 从最大响应时间(尤其是平均响应时间)来看,问题似乎开始变得更加严重。 在20秒(而不是预期的2秒)内获得响应是确定惹恼用户的肯定方法。

现在让我们看一下利用Servlet API 3.0异步支持的替代实现:

@WebServlet(asyncSupported = true, value = "/AsyncServlet")
public class AsyncServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {Work.add(request.startAsync());}
}
public class Work implements ServletContextListener {private static final BlockingQueue queue = new LinkedBlockingQueue();private volatile Thread thread;public static void add(AsyncContext c) {queue.add(c);}@Overridepublic void contextInitialized(ServletContextEvent servletContextEvent) {thread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {try {Thread.sleep(2000);AsyncContext context;while ((context = queue.poll()) != null) {try {ServletResponse response = context.getResponse();response.setContentType("text/plain");PrintWriter out = response.getWriter();out.printf("Thread %s completed the task", Thread.currentThread().getName());out.flush();} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);} finally {context.complete();}}} catch (InterruptedException e) {return;}}}});thread.start();}@Overridepublic void contextDestroyed(ServletContextEvent servletContextEvent) {thread.interrupt();}
}

这部分代码稍微复杂一点,所以也许在我们开始深入研究解决方案细节之前,我可以概述一下,该解决方案在延迟方面的性能提高约75倍,在吞吐量方面的性能提高了约20倍 。 掌握了此类结果的知识后,您应该更加有动力去理解第二个示例中的实际情况。

Servlet本身看起来确实很简单。 但是,有两个事实值得概述,第一个事实声明了该Servlet支持异步方法调用:

@WebServlet(asyncSupported = true, value = "/AsyncServlet")

第二个重要方面隐藏在以下行中

Work.add(request.startAsync());

其中整个请求处理都委派给Work类。 使用AsyncContext实例存储请求的上下文,该实例保存由容器提供的请求和响应。

现在,第二个更复杂的类–以ServletContextListener实现的Work开始看起来更简单。 传入的请求只是在实现中排队等待通知-这可能是受监控拍卖的更新出价,也可能是群聊中所有请求都在等待的下一条消息。

通知到达时-再次简化为在Thread.sleep()中等待2,000ms,队列中所有被阻止的任务都由一个负责编译和发送响应的工作线程处理。 我们没有阻塞数百个线程来等待外部通知,而是以更简单,更简洁的方式实现了这一点–将兴趣组批处理在一起,并在单个线程中处理请求。

结果不言而喻–在具有默认配置的相同Tomcat 7.0.24上进行的相同测试导致以下结果:

  • 平均响应时间:265毫秒
  • 最小响应时间:6毫秒
  • 最长响应时间:2,058毫秒
  • 吞吐量:1,965请求/秒

此处的具体情况很小且综合,但在实际应用中可以实现类似的改进。

现在,在您将所有servlet重写为异步servlet之前-稍等片刻。 该解决方案可以完美地用于部分用例,例如群聊通知和拍卖行价格警报。 对于请求在唯一数据库查询完成后等待的情况,您很可能不会从中受益。 因此,与往常一样,我必须重申我最喜欢的与性能相关的建议–衡量所有事情。 什么都不要猜。

但是,当问题确实适合解决方案形式时,我只能称赞它。 除了对吞吐量和延迟的明显改进之外,我们还优雅地避免了在高负载下可能出现的线程不足问题。

另一个重要方面–异步请求处理方法最终实现了标准化。 独立于您最喜欢的Servlet API 3.0 –兼容应用程序服务器(例如Tomcat 7 , JBoss 6或Jetty 8),您可以确定该方法有效。 不再为不同的Comet实现或与平台相关的解决方案(例如Weblogic FutureResponseServlet)而费力

参考: Plumbr Blog博客上的JCG合作伙伴 Nikita Salnikov Tarnovski 如何使用异步Servlet来提高性能 。

翻译自: https://www.javacodegeeks.com/2013/10/how-to-use-asynchronous-servlets-to-improve-performance.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366376.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我理解中的“大前端”/“大无线”

本文内容较长,大概需要15分钟时间阅读。 内容包含五部分:前言,NodeJS职能变化,ReactNative的大规模应用,专门的架构组职能,总结。主要是介绍我所在团队最近的一些变化和思考。 更多信息可以加入我的小密圈关…

STM32之Systick(系统时钟滴答定时器)

systick定时器有两个可选的时钟源,一个是外部时钟源(STCLK,等于HCLK/8),另一个是内核时钟(FCLK,等于HCLK)。假若你选择内核时钟,并将HCLK频率设置为72MHz的话&#xff0c…

Dirichlet分布

1.预备知识 Beta分布函数是一种定义在实数区间[0,1]的特殊函数,它是二项式分布的共轭分布;与Beta分布相同,Dirichlet分布也是定义在实数区间[0,1]的概率度量函数,Dirichlet分布是多项式分布的共轭分布,Dirichlet分布的…

python编程制作接金币游戏_pygame学习笔记(6):完成一个简单的游戏

学了这么长时间的Pygame,一直想写个游戏实战一下。看起来很简单的游戏,写其来怎么这么难。最初想写个俄罗斯方块,想了很长时间如何实现,想来想去,也没写出来,于是干脆下载别人的代码来读。后来,…

Spring:使基于Java的配置更加优雅

大家好,我很久没有写新文章了。 积累了很多资料,需要在不久的将来在我的博客中发布。 但是现在我想谈谈Spring MVC应用程序配置。 确切地说,我想谈谈基于Java的Spring配置。 尽管在3.0版本中引入了基于Spring Java的配置,但是许多…

用观察者模式编写一个可被其他对象拓展复用自定义事件系统

观察者模式 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知 什么是观察者模式? 发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态…

布局定位

布局与定位 摆放元素 1,使用流 流实际上就是浏览器在页面上摆放HTML元素所用的方法。浏览器从HTML文件最上面开始,从上到下沿着元素流逐个显示所遇到的各个元素。 每个块元素会按它在HTML标记中出现的顺序放置在页面上。每个新的块元素会带来一个换行。并…

T - Memory and Trident CodeForces - 712B( 注意:* ++ = 的优先级

题意:有四种命令:U代表上移一个单位,D代表下移一个单位,R代表右移一个单位,L代表左移一个单位。 现在给出一串命令,问怎样修改命令中的任意一条命令,使得命令结束后重新返回原点,并且…

python语言精通_Python语言基础从入门到精通

1、python关键字False await else import passNone break except in raiseTrue class finally is returnand continue for lambda tryas def from nonlocal whileassert del global not withasync elif if or yield2、命令行参数williamdeMBP-2:~ william$ python -c "imp…

休眠事实:了解刷新操作顺序很重要

Hibernate将开发人员的思维方式从思考SQL转变为思考对象状态转换。 根据Hibernate Docs,实体可能处于以下状态之一: new / transient:实体不与持久性上下文关联,因为它是数据库不知道的新创建的对象。 持久性:实体与…

[HNOI2012]排队

题目描述 某中学有 n 名男同学,m 名女同学和两名老师要排队参加体检。他们排成一条直线,并且任意两名女同学不能相邻,两名老师也不能相邻,那么一共有多少种排法呢?(注意:任意两个人都是不同的&a…

声速的测量的实验原理和应用_声速的测定实验报告心得体会

测量声速的实验报告1。提出问题如何测出声音的速度?2。猜想与假设如果在一定距离内听到声音要多少时间?3。实验步骤步骤应该就是实施实验,第三是实验器材的话,就是要秒表。4。实施实验在一个山谷中,站在距离峭壁680M的地方大叫一声&#xf…

FreeNAS:创建 CIFS 共享(权限)

第一部分:新建账户与指定数据集权限 简单起见,本教程主要介绍带基本身份验证的 CIFS 共享,即只有输入正确的用户名和密码才可以访问共享目录。关于创建匿名共享、多用户权限管理以及域控制器相关内容,我们会另外发布教程专门介绍。…

使用序列化查找对象中的脏字段

假设您正在开发一个将对象自动保存到数据库中的框架。 您需要检测两次保存之间所做的更改,以便仅保存修改过的字段。 如何检测脏场。 最简单的方法是遍历原始数据和当前数据,并分别比较每个字段。 代码如下: public static void getDirtyFie…

js操作table中tr的顺序,实现上移下移一行的效果

总体思路是在table外部加个div,修改div的innerHtml实现改变tr顺序的效果 具体思路是 获取当前要移动tr行的rowIndex,在table中删除掉,然后循环table的rows,到了目标行再直接加进去,最后把整体的html赋值给div完成效果…

oracle日记账单据编号未生成_商管财务数据平台Oracle与共享未付池差异如何核对、解决?...

‍‍近期,总部新上线财务数据平台啦!各个系统间的差异异常数据清晰可见,随时可查,今天就和小伙伴们一起分享一下Oracle与共享未付池差异如何核对、解决。首先,将Oracle与共享未付池差异数据导出。由于导出的数据包括本…

python (六)函数

一、函数的形成 需求1:来测试一下‘hello word’ 的长度 # 在没有函数的时候,我们可以用for循环实现 s1 "hello world" length 0 for i in s1:length length1 print(length) 再增加一个需求2:再来测试一下另外一个字符串的长度&…

Java方法中的参数太多,第4部分:重载

期望将过多的参数传递给Java方法的问题之一是,该方法的客户端很难确定它们是否以适当的顺序传递了适当的值。 在以前的文章中,我描述了如何使用自定义类型 , 参数对象和构建器来解决此问题。 解决此问题的另一种方法(也是本文的主…

android paint 圆角 绘制_[BOT] 一种android中实现“圆角矩形”的方法

内容简介文章介绍ImageView(方法也可以应用到其它View)圆角矩形(包括圆形)的一种实现方式,四个角可以分别指定为圆角。思路是利用“Xfermode Path”来进行Bitmap的裁剪。背景圆角矩形实现的方法应该很多,网上一大堆。很怀疑为啥安卓的控件不内置这样的属…

解决高度塌陷问题

所谓高度塌陷就是在文档流中,父元素的高度默认是被子元素撑开的,也就是子元素多高,父元素就多高。但是当为子元素设置浮动以后,子元素会完全脱离文档流,此时将会导致子元素无法撑起父元素的高度,导致父元素…